@delightstack/components 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/SKILL.md +149 -0
- package/bin/agents.js +63 -0
- package/dist/actions/Alert.svelte +202 -0
- package/dist/actions/Alert.svelte.d.ts +36 -0
- package/dist/actions/Alert.svelte.d.ts.map +1 -0
- package/dist/actions/Button.svelte +1450 -0
- package/dist/actions/Button.svelte.d.ts +56 -0
- package/dist/actions/Button.svelte.d.ts.map +1 -0
- package/dist/actions/ButtonGroup.svelte +111 -0
- package/dist/actions/ButtonGroup.svelte.d.ts +41 -0
- package/dist/actions/ButtonGroup.svelte.d.ts.map +1 -0
- package/dist/actions/CommandPalette.svelte +939 -0
- package/dist/actions/CommandPalette.svelte.d.ts +37 -0
- package/dist/actions/CommandPalette.svelte.d.ts.map +1 -0
- package/dist/actions/ContextMenu.svelte +138 -0
- package/dist/actions/ContextMenu.svelte.d.ts +54 -0
- package/dist/actions/ContextMenu.svelte.d.ts.map +1 -0
- package/dist/actions/Modal.svelte +474 -0
- package/dist/actions/Modal.svelte.d.ts +28 -0
- package/dist/actions/Modal.svelte.d.ts.map +1 -0
- package/dist/actions/Popover.svelte +1214 -0
- package/dist/actions/Popover.svelte.d.ts +31 -0
- package/dist/actions/Popover.svelte.d.ts.map +1 -0
- package/dist/actions/Portal.svelte +80 -0
- package/dist/actions/Portal.svelte.d.ts +17 -0
- package/dist/actions/Portal.svelte.d.ts.map +1 -0
- package/dist/actions/ThemeToggle.svelte +345 -0
- package/dist/actions/ThemeToggle.svelte.d.ts +15 -0
- package/dist/actions/ThemeToggle.svelte.d.ts.map +1 -0
- package/dist/actions/index.d.ts +13 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +10 -0
- package/dist/actions/scrollbar.d.ts +48 -0
- package/dist/actions/scrollbar.d.ts.map +1 -0
- package/dist/actions/scrollbar.js +404 -0
- package/dist/display/Accordion.svelte +586 -0
- package/dist/display/Accordion.svelte.d.ts +41 -0
- package/dist/display/Accordion.svelte.d.ts.map +1 -0
- package/dist/display/Avatar.svelte +527 -0
- package/dist/display/Avatar.svelte.d.ts +22 -0
- package/dist/display/Avatar.svelte.d.ts.map +1 -0
- package/dist/display/AvatarGroup.svelte +298 -0
- package/dist/display/AvatarGroup.svelte.d.ts +31 -0
- package/dist/display/AvatarGroup.svelte.d.ts.map +1 -0
- package/dist/display/Calendar.svelte +1366 -0
- package/dist/display/Calendar.svelte.d.ts +58 -0
- package/dist/display/Calendar.svelte.d.ts.map +1 -0
- package/dist/display/Chart.svelte +1426 -0
- package/dist/display/Chart.svelte.d.ts +35 -0
- package/dist/display/Chart.svelte.d.ts.map +1 -0
- package/dist/display/Code.svelte +780 -0
- package/dist/display/Code.svelte.d.ts +19 -0
- package/dist/display/Code.svelte.d.ts.map +1 -0
- package/dist/display/Comparison.svelte +686 -0
- package/dist/display/Comparison.svelte.d.ts +22 -0
- package/dist/display/Comparison.svelte.d.ts.map +1 -0
- package/dist/display/Counter.svelte +285 -0
- package/dist/display/Counter.svelte.d.ts +21 -0
- package/dist/display/Counter.svelte.d.ts.map +1 -0
- package/dist/display/Expand.svelte +48 -0
- package/dist/display/Expand.svelte.d.ts +9 -0
- package/dist/display/Expand.svelte.d.ts.map +1 -0
- package/dist/display/List.svelte +294 -0
- package/dist/display/List.svelte.d.ts +40 -0
- package/dist/display/List.svelte.d.ts.map +1 -0
- package/dist/display/ListContextReset.svelte +19 -0
- package/dist/display/ListContextReset.svelte.d.ts +7 -0
- package/dist/display/ListContextReset.svelte.d.ts.map +1 -0
- package/dist/display/ListItem.svelte +834 -0
- package/dist/display/ListItem.svelte.d.ts +22 -0
- package/dist/display/ListItem.svelte.d.ts.map +1 -0
- package/dist/display/QR.svelte +1193 -0
- package/dist/display/QR.svelte.d.ts +23 -0
- package/dist/display/QR.svelte.d.ts.map +1 -0
- package/dist/display/SplitPane.svelte +744 -0
- package/dist/display/SplitPane.svelte.d.ts +25 -0
- package/dist/display/SplitPane.svelte.d.ts.map +1 -0
- package/dist/display/Stat.svelte +439 -0
- package/dist/display/Stat.svelte.d.ts +24 -0
- package/dist/display/Stat.svelte.d.ts.map +1 -0
- package/dist/display/Table.svelte +4654 -0
- package/dist/display/Table.svelte.d.ts +249 -0
- package/dist/display/Table.svelte.d.ts.map +1 -0
- package/dist/display/TableCellEditor.svelte +935 -0
- package/dist/display/TableCellEditor.svelte.d.ts +58 -0
- package/dist/display/TableCellEditor.svelte.d.ts.map +1 -0
- package/dist/display/Timeline.svelte +1258 -0
- package/dist/display/Timeline.svelte.d.ts +43 -0
- package/dist/display/Timeline.svelte.d.ts.map +1 -0
- package/dist/display/Tree.svelte +1740 -0
- package/dist/display/Tree.svelte.d.ts +74 -0
- package/dist/display/Tree.svelte.d.ts.map +1 -0
- package/dist/display/Typewriter.svelte +338 -0
- package/dist/display/Typewriter.svelte.d.ts +22 -0
- package/dist/display/Typewriter.svelte.d.ts.map +1 -0
- package/dist/display/index.d.ts +24 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +18 -0
- package/dist/feedback/Callout.svelte +529 -0
- package/dist/feedback/Callout.svelte.d.ts +24 -0
- package/dist/feedback/Callout.svelte.d.ts.map +1 -0
- package/dist/feedback/Confetti.svelte +631 -0
- package/dist/feedback/Confetti.svelte.d.ts +90 -0
- package/dist/feedback/Confetti.svelte.d.ts.map +1 -0
- package/dist/feedback/Progress.svelte +382 -0
- package/dist/feedback/Progress.svelte.d.ts +25 -0
- package/dist/feedback/Progress.svelte.d.ts.map +1 -0
- package/dist/feedback/Toast.svelte +967 -0
- package/dist/feedback/Toast.svelte.d.ts +54 -0
- package/dist/feedback/Toast.svelte.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +7 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/index.js +4 -0
- package/dist/form/Checkbox.svelte +449 -0
- package/dist/form/Checkbox.svelte.d.ts +27 -0
- package/dist/form/Checkbox.svelte.d.ts.map +1 -0
- package/dist/form/Fieldset.svelte +410 -0
- package/dist/form/Fieldset.svelte.d.ts +22 -0
- package/dist/form/Fieldset.svelte.d.ts.map +1 -0
- package/dist/form/FileUpload.svelte +934 -0
- package/dist/form/FileUpload.svelte.d.ts +41 -0
- package/dist/form/FileUpload.svelte.d.ts.map +1 -0
- package/dist/form/Form.svelte +530 -0
- package/dist/form/Form.svelte.d.ts +120 -0
- package/dist/form/Form.svelte.d.ts.map +1 -0
- package/dist/form/Input.svelte +2858 -0
- package/dist/form/Input.svelte.d.ts +66 -0
- package/dist/form/Input.svelte.d.ts.map +1 -0
- package/dist/form/Radio.svelte +507 -0
- package/dist/form/Radio.svelte.d.ts +39 -0
- package/dist/form/Radio.svelte.d.ts.map +1 -0
- package/dist/form/Range.svelte +912 -0
- package/dist/form/Range.svelte.d.ts +33 -0
- package/dist/form/Range.svelte.d.ts.map +1 -0
- package/dist/form/Rating.svelte +429 -0
- package/dist/form/Rating.svelte.d.ts +28 -0
- package/dist/form/Rating.svelte.d.ts.map +1 -0
- package/dist/form/Select.svelte +1933 -0
- package/dist/form/Select.svelte.d.ts +54 -0
- package/dist/form/Select.svelte.d.ts.map +1 -0
- package/dist/form/Toggle.svelte +645 -0
- package/dist/form/Toggle.svelte.d.ts +50 -0
- package/dist/form/Toggle.svelte.d.ts.map +1 -0
- package/dist/form/index.d.ts +15 -0
- package/dist/form/index.d.ts.map +1 -0
- package/dist/form/index.js +10 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/layout/README.md +172 -0
- package/dist/media/Carousel.svelte +2424 -0
- package/dist/media/Carousel.svelte.d.ts +47 -0
- package/dist/media/Carousel.svelte.d.ts.map +1 -0
- package/dist/media/Gallery.svelte +2881 -0
- package/dist/media/Gallery.svelte.d.ts +82 -0
- package/dist/media/Gallery.svelte.d.ts.map +1 -0
- package/dist/media/Image.svelte +389 -0
- package/dist/media/Image.svelte.d.ts +33 -0
- package/dist/media/Image.svelte.d.ts.map +1 -0
- package/dist/media/PDF.svelte +1793 -0
- package/dist/media/PDF.svelte.d.ts +44 -0
- package/dist/media/PDF.svelte.d.ts.map +1 -0
- package/dist/media/Panorama.svelte +1391 -0
- package/dist/media/Panorama.svelte.d.ts +47 -0
- package/dist/media/Panorama.svelte.d.ts.map +1 -0
- package/dist/media/Video.svelte +2501 -0
- package/dist/media/Video.svelte.d.ts +58 -0
- package/dist/media/Video.svelte.d.ts.map +1 -0
- package/dist/media/carousel.d.ts +211 -0
- package/dist/media/carousel.d.ts.map +1 -0
- package/dist/media/carousel.js +408 -0
- package/dist/media/index.d.ts +11 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +5 -0
- package/dist/navigation/BottomSheet.svelte +636 -0
- package/dist/navigation/BottomSheet.svelte.d.ts +27 -0
- package/dist/navigation/BottomSheet.svelte.d.ts.map +1 -0
- package/dist/navigation/Breadcrumbs.svelte +611 -0
- package/dist/navigation/Breadcrumbs.svelte.d.ts +28 -0
- package/dist/navigation/Breadcrumbs.svelte.d.ts.map +1 -0
- package/dist/navigation/Pagination.svelte +641 -0
- package/dist/navigation/Pagination.svelte.d.ts +27 -0
- package/dist/navigation/Pagination.svelte.d.ts.map +1 -0
- package/dist/navigation/Steps.svelte +965 -0
- package/dist/navigation/Steps.svelte.d.ts +43 -0
- package/dist/navigation/Steps.svelte.d.ts.map +1 -0
- package/dist/navigation/Tabs.svelte +698 -0
- package/dist/navigation/Tabs.svelte.d.ts +41 -0
- package/dist/navigation/Tabs.svelte.d.ts.map +1 -0
- package/dist/navigation/index.d.ts +8 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +5 -0
- package/package.json +139 -0
|
@@ -0,0 +1,2881 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Component } from 'svelte';
|
|
3
|
+
import type { CarouselItem } from './carousel';
|
|
4
|
+
|
|
5
|
+
/** A single action button that can be shown on a gallery item or in the carousel header. */
|
|
6
|
+
export interface GalleryItemAction {
|
|
7
|
+
/** The icon component to show for the action */
|
|
8
|
+
icon?: Component<Record<string, unknown>>;
|
|
9
|
+
|
|
10
|
+
/** The main action text - e.g. 'Download' or 'Pay now' */
|
|
11
|
+
name?: string;
|
|
12
|
+
|
|
13
|
+
/** A short descriptor of the action - e.g. file size or filename */
|
|
14
|
+
tooltip?: string;
|
|
15
|
+
|
|
16
|
+
/** The link that the button should go to */
|
|
17
|
+
href?: string;
|
|
18
|
+
|
|
19
|
+
/** Called when the button is clicked */
|
|
20
|
+
click?: (event: Event) => unknown;
|
|
21
|
+
|
|
22
|
+
/** Anchor target (only used if href is provided) */
|
|
23
|
+
target?: '_blank' | '_self';
|
|
24
|
+
|
|
25
|
+
/** The list of subactions (shown in a context menu) */
|
|
26
|
+
actions?: GalleryItemAction[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type GalleryDisplay =
|
|
30
|
+
| 'grid'
|
|
31
|
+
| 'masonry'
|
|
32
|
+
| 'masonry-row'
|
|
33
|
+
| 'list'
|
|
34
|
+
| 'slider'
|
|
35
|
+
| 'slideshow'
|
|
36
|
+
| 'lightbox';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Thumbnail size on the delightstack numeric scale: `'1'` is the standard,
|
|
40
|
+
* lower (`'0'`, `'00'`) is smaller, higher (`'2'`, `'3'`) is larger.
|
|
41
|
+
*/
|
|
42
|
+
export type GallerySize = '00' | '0' | '1' | '2' | '3';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gap between gallery items on the delightstack numeric scale: `'0'` removes
|
|
46
|
+
* the gap, `'1'` is the standard, `'2'`/`'3'` are progressively larger.
|
|
47
|
+
*/
|
|
48
|
+
export type GallerySpacing = '0' | '1' | '2' | '3';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Corner radius of gallery items on the delightstack numeric scale: `'0'` is
|
|
52
|
+
* square, `'1'` is the standard, `'2'`/`'3'` are progressively rounder.
|
|
53
|
+
*/
|
|
54
|
+
export type GalleryRadius = '0' | '1' | '2' | '3';
|
|
55
|
+
|
|
56
|
+
export type GalleryItem = string | (Partial<CarouselItem> & { favorite?: boolean });
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<script lang="ts">
|
|
60
|
+
import { type TransitionConfig, fade } from 'svelte/transition';
|
|
61
|
+
import { circInOut } from 'svelte/easing';
|
|
62
|
+
import { onDestroy, onMount, untrack, type Snippet } from 'svelte';
|
|
63
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
64
|
+
import { focusTrap, intersectionObserver, ripple } from '@delightstack/utilities';
|
|
65
|
+
|
|
66
|
+
// Minimal subset of the focus-trap instance we use, declared locally so the
|
|
67
|
+
// 'focus-trap' package doesn't need to be a direct dep of this package.
|
|
68
|
+
interface FocusTrapInstance {
|
|
69
|
+
active: boolean;
|
|
70
|
+
deactivate: () => void;
|
|
71
|
+
}
|
|
72
|
+
import Button from '../actions/Button.svelte';
|
|
73
|
+
import List from '../display/List.svelte';
|
|
74
|
+
import ListItem from '../display/ListItem.svelte';
|
|
75
|
+
import Portal from '../actions/Portal.svelte';
|
|
76
|
+
import { contextMenu } from '../actions/ContextMenu.svelte';
|
|
77
|
+
import Carousel from './Carousel.svelte';
|
|
78
|
+
import {
|
|
79
|
+
decodeThumbHash,
|
|
80
|
+
getItemThumbnailSrc,
|
|
81
|
+
isResponsiveSrcset,
|
|
82
|
+
normalizeCarouselItem,
|
|
83
|
+
pickLargestSrc,
|
|
84
|
+
} from './carousel';
|
|
85
|
+
|
|
86
|
+
let {
|
|
87
|
+
/**
|
|
88
|
+
* How the gallery should be displayed - whether a grid, slideshow, etc.
|
|
89
|
+
*
|
|
90
|
+
* Use `'lightbox'` for a headless mode: Gallery renders no thumbnails of its
|
|
91
|
+
* own, and you provide your own trigger elements (buttons, images, cards) that
|
|
92
|
+
* open the carousel by setting `slide` to the desired index (`-1` keeps it
|
|
93
|
+
* closed). For a nice open animation from your trigger element, call the
|
|
94
|
+
* exported `open(index, fromElement)` method instead of setting `slide`
|
|
95
|
+
* directly.
|
|
96
|
+
*/
|
|
97
|
+
display = 'masonry' as GalleryDisplay,
|
|
98
|
+
|
|
99
|
+
/** The size of the thumbnails in the gallery (`'00'`–`'3'`, default `'1'`) */
|
|
100
|
+
size = '1' as GallerySize,
|
|
101
|
+
|
|
102
|
+
/** The size of the spacing between thumbnails in the gallery (`'0'`–`'3'`, default `'1'`) */
|
|
103
|
+
spacing = '1' as GallerySpacing,
|
|
104
|
+
|
|
105
|
+
/** The border radius of the gallery items (`'0'`–`'3'`, default `'1'`) */
|
|
106
|
+
radius = '1' as GalleryRadius,
|
|
107
|
+
|
|
108
|
+
/** The currently displayed item index. -1 closes the modal/slider. */
|
|
109
|
+
slide = $bindable(display === 'slider' || display === 'slideshow' ? 0 : -1) as number,
|
|
110
|
+
|
|
111
|
+
/** The object-fit attribute for all items in the gallery */
|
|
112
|
+
fit = 'contain' as 'cover' | 'contain',
|
|
113
|
+
|
|
114
|
+
/** The list of items to display. Strings are treated as image URLs. */
|
|
115
|
+
items = [] as GalleryItem[],
|
|
116
|
+
|
|
117
|
+
/** The duration (in ms) between slide auto-transitions */
|
|
118
|
+
duration = 8000,
|
|
119
|
+
|
|
120
|
+
/** Whether the gallery should auto transition between slides */
|
|
121
|
+
autoplay = false,
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Whether a video should start playing automatically when the modal/lightbox
|
|
125
|
+
* is launched onto it (e.g. clicking a video thumbnail, or `open()`). Only
|
|
126
|
+
* the slide the lightbox opens to auto-plays, and only when it's a video.
|
|
127
|
+
* Navigating/swiping between slides does NOT auto-play. Because the launch is
|
|
128
|
+
* a user gesture, the browser permits playback, with sound.
|
|
129
|
+
*/
|
|
130
|
+
autoplay_video = false,
|
|
131
|
+
|
|
132
|
+
/** The css aspect ratio the gallery should be forced into (only when not a modal) */
|
|
133
|
+
aspect_ratio = undefined as string | undefined,
|
|
134
|
+
|
|
135
|
+
/** Whether the full screen button should be disabled */
|
|
136
|
+
disable_fullscreen = false,
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Whether the gallery is 'inline' in the page - not a modal or fullscreen.
|
|
140
|
+
* If 'inline', vertical gestures & mouse wheel are disabled.
|
|
141
|
+
* @default true when display is 'slider' and not in fullscreen
|
|
142
|
+
*/
|
|
143
|
+
inline = undefined as boolean | undefined,
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* How the slider controls should be displayed.
|
|
147
|
+
* - inline: the controls sit below the slideshow element
|
|
148
|
+
* - overlay: the controls overlay on top of the slideshow element
|
|
149
|
+
* - disable: the controls are not shown at all
|
|
150
|
+
* - default: 'inline' when the carousel is inline, 'overlay' when modal
|
|
151
|
+
*/
|
|
152
|
+
controls = 'default' as 'default' | 'inline' | 'overlay' | 'disable',
|
|
153
|
+
|
|
154
|
+
/** The currently displayed page (a vertical carousel within the current slide - used for pdf pages) */
|
|
155
|
+
page = $bindable(0) as number,
|
|
156
|
+
|
|
157
|
+
/** The amount of pages available in the current slide (applies to PDFs) */
|
|
158
|
+
num_pages = $bindable(1) as number,
|
|
159
|
+
|
|
160
|
+
/** The display style of the metadata (name, description, etc) for each item */
|
|
161
|
+
meta_display = 'hover' as 'none' | 'always' | 'hover',
|
|
162
|
+
|
|
163
|
+
/** How file names should be displayed in the fullscreen/carousel view */
|
|
164
|
+
meta_display_fullscreen = 'none' as 'none' | 'always',
|
|
165
|
+
|
|
166
|
+
/** The display style of the actions (download buttons, etc) for each item */
|
|
167
|
+
action_display = 'hover' as 'none' | 'always' | 'hover',
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The list of potential actions a user can take on each gallery item.
|
|
171
|
+
* Each gallery item can have multiple actions.
|
|
172
|
+
*/
|
|
173
|
+
actions = [] as GalleryItemAction[][],
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Snippet used to render `type: 'custom'` items in the carousel.
|
|
177
|
+
* Pass-through to Carousel. See Carousel's `custom` prop for the
|
|
178
|
+
* full signature.
|
|
179
|
+
*/
|
|
180
|
+
custom = undefined as
|
|
181
|
+
| Snippet<
|
|
182
|
+
[
|
|
183
|
+
{
|
|
184
|
+
item: CarouselItem;
|
|
185
|
+
onload: () => void;
|
|
186
|
+
onerror: (err: unknown) => void;
|
|
187
|
+
active: boolean;
|
|
188
|
+
gesture_disabled: boolean;
|
|
189
|
+
},
|
|
190
|
+
]
|
|
191
|
+
>
|
|
192
|
+
| undefined,
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Called when an item's thumbnail/text is clicked.
|
|
196
|
+
* If the function returns false, the default behavior (opening the modal/slider) is prevented.
|
|
197
|
+
*/
|
|
198
|
+
onclick = undefined as
|
|
199
|
+
| undefined
|
|
200
|
+
| ((event: MouseEvent | KeyboardEvent, index: number) => void | false),
|
|
201
|
+
|
|
202
|
+
/** The css style string added to the component from the parent */
|
|
203
|
+
style = '',
|
|
204
|
+
} = $props();
|
|
205
|
+
|
|
206
|
+
/** The percent (0-1) of how 'closed' the gallery is - while swiping/dismissing the gallery away */
|
|
207
|
+
let dismissing = $state(0);
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Item IDs whose `<img>` was *not* loaded at mount time and therefore needs
|
|
211
|
+
* the fade-in transition when its `onload` eventually fires. On SSR this
|
|
212
|
+
* stays empty so the rendered HTML has the main image at its default
|
|
213
|
+
* (visible) opacity — cached images then paint instantly without waiting
|
|
214
|
+
* for hydration.
|
|
215
|
+
*/
|
|
216
|
+
const fadingKeys = new SvelteSet<string>();
|
|
217
|
+
function thumbnailKey(item: { id?: string; src?: string }) {
|
|
218
|
+
return item.id || item.src || '';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** The instance of the focus trap class - used to programmatically deactivate the focus trap */
|
|
222
|
+
let focusTrapInstance: FocusTrapInstance | undefined;
|
|
223
|
+
|
|
224
|
+
/** The element that the carousel item will be animated from */
|
|
225
|
+
let animationTarget = $state<HTMLElement | undefined>(undefined);
|
|
226
|
+
|
|
227
|
+
/** The instance of the carousel (can be used to control it) */
|
|
228
|
+
let carousel = $state<ReturnType<typeof Carousel> | undefined>(undefined);
|
|
229
|
+
|
|
230
|
+
/** Whether or not the gallery is being viewed in fullscreen */
|
|
231
|
+
let fullscreenActive = $state(false);
|
|
232
|
+
|
|
233
|
+
/** Whether the slider element is currently visible on screen */
|
|
234
|
+
let intersected = $state(false);
|
|
235
|
+
|
|
236
|
+
const list = $derived(
|
|
237
|
+
items
|
|
238
|
+
.map((v) => normalizeCarouselItem(v as string | Partial<CarouselItem>))
|
|
239
|
+
.filter((v): v is CarouselItem => !!v)
|
|
240
|
+
.map((item, i) => {
|
|
241
|
+
const original = items[i] as Partial<CarouselItem> & { favorite?: boolean };
|
|
242
|
+
return {
|
|
243
|
+
...item,
|
|
244
|
+
favorite: typeof original === 'object' ? !!original?.favorite : false,
|
|
245
|
+
};
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const sliderActive = $derived(
|
|
250
|
+
display === 'slider' || display === 'slideshow' || slide >= 0,
|
|
251
|
+
);
|
|
252
|
+
const isModal = $derived(
|
|
253
|
+
fullscreenActive || (display !== 'slider' && display !== 'slideshow' && slide >= 0),
|
|
254
|
+
);
|
|
255
|
+
$effect(() => {
|
|
256
|
+
if (typeof window !== 'undefined') {
|
|
257
|
+
if (isModal) {
|
|
258
|
+
window.document.body.style.overflow = 'hidden';
|
|
259
|
+
} else {
|
|
260
|
+
window.document.body.style.overflow = '';
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Prevent the modal from automatically being active when switching between display modes
|
|
266
|
+
let previousDisplay = undefined as typeof display | undefined;
|
|
267
|
+
$effect.pre(() => {
|
|
268
|
+
const wasSliderLike = previousDisplay === 'slider' || previousDisplay === 'slideshow';
|
|
269
|
+
const isSliderLike = display === 'slider' || display === 'slideshow';
|
|
270
|
+
if (wasSliderLike) {
|
|
271
|
+
if (!isSliderLike) slide = -1;
|
|
272
|
+
} else {
|
|
273
|
+
if (isSliderLike) slide = 0;
|
|
274
|
+
}
|
|
275
|
+
if (previousDisplay) untrack(() => pause());
|
|
276
|
+
previousDisplay = display;
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
/** Autoplay state */
|
|
280
|
+
let autoplayPaused = $state(false);
|
|
281
|
+
const autoplayTransitionInterval = 300; // ms between progress ticks
|
|
282
|
+
let autoplayTransitionStart = $state<number | undefined>(undefined);
|
|
283
|
+
let autoplayTransitionProgress = $state<number | undefined>(undefined);
|
|
284
|
+
let autoplayTransitionTimer = $state<ReturnType<typeof setInterval> | undefined>(
|
|
285
|
+
undefined,
|
|
286
|
+
);
|
|
287
|
+
$effect.pre(() => {
|
|
288
|
+
if (autoplay && intersected && !autoplayTransitionTimer && !autoplayPaused) play();
|
|
289
|
+
});
|
|
290
|
+
onDestroy(() => pause());
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Opens the gallery modal at the given item index.
|
|
294
|
+
*
|
|
295
|
+
* Primarily intended for `display="lightbox"`, where the developer renders
|
|
296
|
+
* their own thumbnails: pass `event.currentTarget` (or another element) as
|
|
297
|
+
* `from` to anchor the open animation to that element. Equivalent to setting
|
|
298
|
+
* `slide = index` directly, except it also captures the animation origin.
|
|
299
|
+
*/
|
|
300
|
+
export function open(index: number, from?: HTMLElement) {
|
|
301
|
+
if (!list[index]) return;
|
|
302
|
+
dismissing = 0;
|
|
303
|
+
animationTarget = from;
|
|
304
|
+
slide = index;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Closes the gallery modal */
|
|
308
|
+
export function close() {
|
|
309
|
+
if (fullscreenActive) return closeFullscreen();
|
|
310
|
+
if (!sliderActive) return;
|
|
311
|
+
if ((display === 'slider' || display === 'slideshow') && isModal)
|
|
312
|
+
return closeFullscreen();
|
|
313
|
+
if (focusTrapInstance?.active) {
|
|
314
|
+
focusTrapInstance.deactivate();
|
|
315
|
+
} else {
|
|
316
|
+
slide = -1;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Navigates to the item at the given index */
|
|
321
|
+
export function goto(i: number) {
|
|
322
|
+
if (!sliderActive || !list[i]) return;
|
|
323
|
+
pause();
|
|
324
|
+
slide = i;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Navigates to the next item */
|
|
328
|
+
export function next(amount = 1) {
|
|
329
|
+
if (!sliderActive) return;
|
|
330
|
+
pause();
|
|
331
|
+
const target = Math.floor(slide + amount) % list.length;
|
|
332
|
+
slide = target;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Navigates to the previous item */
|
|
336
|
+
export function prev(amount = 1) {
|
|
337
|
+
if (!sliderActive) return;
|
|
338
|
+
pause();
|
|
339
|
+
const target = Math.floor(slide - amount + list.length) % list.length;
|
|
340
|
+
slide = target;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Starts the slideshow */
|
|
344
|
+
export function play() {
|
|
345
|
+
if (!sliderActive || autoplayTransitionTimer) return;
|
|
346
|
+
autoplayPaused = false;
|
|
347
|
+
autoplayTransitionStart = Date.now();
|
|
348
|
+
autoplayTransitionTimer = setInterval(() => {
|
|
349
|
+
if (!autoplayTransitionStart) return clearInterval(autoplayTransitionTimer);
|
|
350
|
+
const now = Date.now();
|
|
351
|
+
if (!intersected) {
|
|
352
|
+
autoplayTransitionStart = Math.min(
|
|
353
|
+
now,
|
|
354
|
+
Math.floor(
|
|
355
|
+
now -
|
|
356
|
+
duration * (autoplayTransitionProgress || 0) +
|
|
357
|
+
autoplayTransitionInterval,
|
|
358
|
+
),
|
|
359
|
+
);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
autoplayTransitionProgress = (now - autoplayTransitionStart) / duration;
|
|
363
|
+
if (autoplayTransitionProgress >= 1) {
|
|
364
|
+
autoplayTransitionStart = now;
|
|
365
|
+
setTimeout(() => (autoplayTransitionProgress = 0), 10);
|
|
366
|
+
const target = Math.floor(slide + 1) % list.length;
|
|
367
|
+
slide = target;
|
|
368
|
+
}
|
|
369
|
+
}, autoplayTransitionInterval);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Pauses the slideshow */
|
|
373
|
+
export function pause() {
|
|
374
|
+
if (!autoplayTransitionTimer) return;
|
|
375
|
+
clearInterval(autoplayTransitionTimer);
|
|
376
|
+
autoplayTransitionTimer = undefined;
|
|
377
|
+
autoplayTransitionStart = undefined;
|
|
378
|
+
autoplayTransitionProgress = undefined;
|
|
379
|
+
autoplayPaused = true;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** Handles when a grid item is clicked (opens the modal) */
|
|
383
|
+
function onItemClick(i: number, evt: MouseEvent | KeyboardEvent) {
|
|
384
|
+
if (onclick) {
|
|
385
|
+
const result = onclick(evt, i);
|
|
386
|
+
if (result === false) {
|
|
387
|
+
evt.preventDefault();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
let target = evt.target as HTMLElement;
|
|
392
|
+
let isActionButton = false;
|
|
393
|
+
while (target && !isActionButton && !target.classList.contains('gallery-item')) {
|
|
394
|
+
isActionButton =
|
|
395
|
+
target.classList.contains('actions') || target.classList.contains('button');
|
|
396
|
+
target = target.parentElement as HTMLElement;
|
|
397
|
+
}
|
|
398
|
+
if (isActionButton) return;
|
|
399
|
+
dismissing = 0;
|
|
400
|
+
animationTarget = (evt.target as HTMLElement) || undefined;
|
|
401
|
+
slide = i;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Returns the gallery item element that triggered this carousel open so focus can be returned to it */
|
|
405
|
+
function focusTrapSetReturnFocus(
|
|
406
|
+
elFocusedBeforeActivation: HTMLElement | SVGElement,
|
|
407
|
+
): HTMLElement | false {
|
|
408
|
+
const items = Array.from(document.querySelectorAll('.gallery.grid .gallery-item'));
|
|
409
|
+
const target = items[slide] as HTMLElement;
|
|
410
|
+
return target || elFocusedBeforeActivation;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** Opens the media player in full screen mode */
|
|
414
|
+
async function openFullscreen() {
|
|
415
|
+
const promise =
|
|
416
|
+
document?.documentElement?.requestFullscreen() ||
|
|
417
|
+
(document?.documentElement as any)?.mozRequestFullScreen() ||
|
|
418
|
+
(document?.documentElement as any)?.webkitRequestFullscreen() ||
|
|
419
|
+
(document?.documentElement as any)?.msRequestFullscreen();
|
|
420
|
+
if (!promise) return;
|
|
421
|
+
fullscreenActive = true;
|
|
422
|
+
await promise.catch(() => {
|
|
423
|
+
fullscreenActive = false;
|
|
424
|
+
});
|
|
425
|
+
if (document.fullscreenElement === null && fullscreenActive) fullscreenActive = false;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Closes the fullscreen mode */
|
|
429
|
+
function closeFullscreen() {
|
|
430
|
+
document?.exitFullscreen() ||
|
|
431
|
+
(document as any)?.mozCancelFullScreen() ||
|
|
432
|
+
(document as any)?.webkitExitFullscreen() ||
|
|
433
|
+
(document as any)?.msExitFullscreen();
|
|
434
|
+
if (carousel) carousel.reset();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Toggles fullscreen mode */
|
|
438
|
+
export function toggleFullscreen() {
|
|
439
|
+
if (fullscreenActive) {
|
|
440
|
+
closeFullscreen();
|
|
441
|
+
} else {
|
|
442
|
+
openFullscreen();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
onMount(() => {
|
|
447
|
+
const listener = () => {
|
|
448
|
+
if (document.fullscreenElement === null) fullscreenActive = false;
|
|
449
|
+
};
|
|
450
|
+
document.addEventListener('fullscreenchange', listener);
|
|
451
|
+
document.addEventListener('webkitfullscreenchange', listener);
|
|
452
|
+
document.addEventListener('mozfullscreenchange', listener);
|
|
453
|
+
document.addEventListener('msfullscreenchange', listener);
|
|
454
|
+
return () => {
|
|
455
|
+
document.removeEventListener('fullscreenchange', listener);
|
|
456
|
+
document.removeEventListener('webkitfullscreenchange', listener);
|
|
457
|
+
document.removeEventListener('mozfullscreenchange', listener);
|
|
458
|
+
document.removeEventListener('msfullscreenchange', listener);
|
|
459
|
+
};
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
/** Animates the scale of the gallery item on dismiss */
|
|
463
|
+
function carouselCloseTransition(node: HTMLElement): () => TransitionConfig {
|
|
464
|
+
return () => {
|
|
465
|
+
const duration = 300;
|
|
466
|
+
const start = 0.5;
|
|
467
|
+
const el = node.querySelector<HTMLElement>('.carousel .item.active');
|
|
468
|
+
if (!el) {
|
|
469
|
+
const parentOpacity = +getComputedStyle(node).opacity;
|
|
470
|
+
return { duration: 150, css: (_t, u) => `opacity: ${parentOpacity - u}` };
|
|
471
|
+
}
|
|
472
|
+
const activePageEl = node.querySelector<HTMLElement>(
|
|
473
|
+
'.carousel .item.active > *.active:not(.preview)',
|
|
474
|
+
);
|
|
475
|
+
let inactivePageEls: HTMLElement[] = [];
|
|
476
|
+
if (activePageEl) {
|
|
477
|
+
inactivePageEls = Array.from(
|
|
478
|
+
node.querySelectorAll<HTMLElement>('.carousel .item.active > *:not(.active)'),
|
|
479
|
+
);
|
|
480
|
+
} else {
|
|
481
|
+
inactivePageEls = Array.from(
|
|
482
|
+
node.querySelectorAll<HTMLElement>(
|
|
483
|
+
'.carousel .item.active > *:not(:first-child)',
|
|
484
|
+
),
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
inactivePageEls.forEach((el) => {
|
|
488
|
+
el.style.opacity = '0';
|
|
489
|
+
});
|
|
490
|
+
const style = getComputedStyle(el);
|
|
491
|
+
const targetOpacity = +style.opacity;
|
|
492
|
+
const transform = style.transform === 'none' ? '' : style.transform;
|
|
493
|
+
return {
|
|
494
|
+
duration,
|
|
495
|
+
easing: circInOut,
|
|
496
|
+
tick: (_t, u) => {
|
|
497
|
+
const sd = 1 - start;
|
|
498
|
+
el.style.transformOrigin = 'center center';
|
|
499
|
+
el.style.opacity = `${targetOpacity - u}`;
|
|
500
|
+
el.style.transform = `${transform} perspective(100px) translate3d(0px, ${
|
|
501
|
+
500 * u
|
|
502
|
+
}px, ${sd * u * -300}px)`;
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** The full set of context-menu actions for the slide at the given index */
|
|
509
|
+
function flattenActions(itemActions: GalleryItemAction[] | undefined) {
|
|
510
|
+
return (
|
|
511
|
+
itemActions?.flatMap((parentAction) =>
|
|
512
|
+
[parentAction, ...(parentAction.actions || [])]
|
|
513
|
+
.filter((action) => !!action?.name)
|
|
514
|
+
.map((action) => ({
|
|
515
|
+
label: action.name,
|
|
516
|
+
icon: action.icon || parentAction.icon,
|
|
517
|
+
href: action.href,
|
|
518
|
+
target: action.target,
|
|
519
|
+
onclick: action.click
|
|
520
|
+
? (event: PointerEvent) => {
|
|
521
|
+
action.click?.(event);
|
|
522
|
+
}
|
|
523
|
+
: undefined,
|
|
524
|
+
})),
|
|
525
|
+
) || []
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
</script>
|
|
529
|
+
|
|
530
|
+
<!-- Inline icons (kept terse to avoid pulling in an icon dep) -->
|
|
531
|
+
{#snippet iconPlay()}
|
|
532
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
533
|
+
<path d="M8 5v14l11-7L8 5z" />
|
|
534
|
+
</svg>
|
|
535
|
+
{/snippet}
|
|
536
|
+
{#snippet iconPause()}
|
|
537
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
538
|
+
<path d="M6 5h4v14H6zM14 5h4v14h-4z" />
|
|
539
|
+
</svg>
|
|
540
|
+
{/snippet}
|
|
541
|
+
{#snippet iconDocument()}
|
|
542
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
543
|
+
<path
|
|
544
|
+
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zm0 7V3.5L19.5 9H14z" />
|
|
545
|
+
</svg>
|
|
546
|
+
{/snippet}
|
|
547
|
+
{#snippet iconEmbed()}
|
|
548
|
+
<svg
|
|
549
|
+
viewBox="0 0 24 24"
|
|
550
|
+
fill="none"
|
|
551
|
+
stroke="currentColor"
|
|
552
|
+
stroke-width="2"
|
|
553
|
+
stroke-linecap="round"
|
|
554
|
+
stroke-linejoin="round"
|
|
555
|
+
aria-hidden="true">
|
|
556
|
+
<polyline points="16 18 22 12 16 6" />
|
|
557
|
+
<polyline points="8 6 2 12 8 18" />
|
|
558
|
+
</svg>
|
|
559
|
+
{/snippet}
|
|
560
|
+
{#snippet iconPanorama()}
|
|
561
|
+
<!-- Panoramic photo: a wide frame with curved (barrel) top/bottom edges
|
|
562
|
+
and a mountain scene — reads more clearly as "360 panorama" than the
|
|
563
|
+
previous globe/grid ellipse. -->
|
|
564
|
+
<svg
|
|
565
|
+
viewBox="0 0 24 24"
|
|
566
|
+
fill="none"
|
|
567
|
+
stroke="currentColor"
|
|
568
|
+
stroke-width="2"
|
|
569
|
+
stroke-linecap="round"
|
|
570
|
+
stroke-linejoin="round"
|
|
571
|
+
aria-hidden="true">
|
|
572
|
+
<path d="M2 6c6.5 1.6 13.5 1.6 20 0v12c-6.5-1.6-13.5-1.6-20 0V6z" />
|
|
573
|
+
<path d="M6 15l3.5-4 2.5 3 3-4 3 5" />
|
|
574
|
+
</svg>
|
|
575
|
+
{/snippet}
|
|
576
|
+
{#snippet iconChevronLeft()}
|
|
577
|
+
<svg
|
|
578
|
+
viewBox="0 0 24 24"
|
|
579
|
+
fill="none"
|
|
580
|
+
stroke="currentColor"
|
|
581
|
+
stroke-width="2"
|
|
582
|
+
stroke-linecap="round"
|
|
583
|
+
stroke-linejoin="round"
|
|
584
|
+
aria-hidden="true">
|
|
585
|
+
<polyline points="15 18 9 12 15 6" />
|
|
586
|
+
</svg>
|
|
587
|
+
{/snippet}
|
|
588
|
+
{#snippet iconChevronRight()}
|
|
589
|
+
<svg
|
|
590
|
+
viewBox="0 0 24 24"
|
|
591
|
+
fill="none"
|
|
592
|
+
stroke="currentColor"
|
|
593
|
+
stroke-width="2"
|
|
594
|
+
stroke-linecap="round"
|
|
595
|
+
stroke-linejoin="round"
|
|
596
|
+
aria-hidden="true">
|
|
597
|
+
<polyline points="9 18 15 12 9 6" />
|
|
598
|
+
</svg>
|
|
599
|
+
{/snippet}
|
|
600
|
+
{#snippet iconFullscreen()}
|
|
601
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
602
|
+
<path
|
|
603
|
+
d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" />
|
|
604
|
+
</svg>
|
|
605
|
+
{/snippet}
|
|
606
|
+
{#snippet iconFullscreenExit()}
|
|
607
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
608
|
+
<path
|
|
609
|
+
d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" />
|
|
610
|
+
</svg>
|
|
611
|
+
{/snippet}
|
|
612
|
+
{#snippet iconClose()}
|
|
613
|
+
<svg
|
|
614
|
+
viewBox="0 0 24 24"
|
|
615
|
+
fill="none"
|
|
616
|
+
stroke="currentColor"
|
|
617
|
+
stroke-width="2"
|
|
618
|
+
stroke-linecap="round"
|
|
619
|
+
stroke-linejoin="round"
|
|
620
|
+
aria-hidden="true">
|
|
621
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
622
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
623
|
+
</svg>
|
|
624
|
+
{/snippet}
|
|
625
|
+
{#snippet iconDownload()}
|
|
626
|
+
<svg
|
|
627
|
+
viewBox="0 0 24 24"
|
|
628
|
+
fill="none"
|
|
629
|
+
stroke="currentColor"
|
|
630
|
+
stroke-width="2"
|
|
631
|
+
stroke-linecap="round"
|
|
632
|
+
stroke-linejoin="round"
|
|
633
|
+
aria-hidden="true">
|
|
634
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
635
|
+
<polyline points="7 10 12 15 17 10" />
|
|
636
|
+
<line x1="12" y1="15" x2="12" y2="3" />
|
|
637
|
+
</svg>
|
|
638
|
+
{/snippet}
|
|
639
|
+
|
|
640
|
+
{#snippet itemThumbnail(item: (typeof list)[number], sizesFallback: string)}
|
|
641
|
+
{@const key = thumbnailKey(item)}
|
|
642
|
+
{@const eager = !!item.priority}
|
|
643
|
+
{@const isImage = !item.type || item.type === 'image'}
|
|
644
|
+
{@const thumbSrc = getItemThumbnailSrc(item)}
|
|
645
|
+
{#if item.thumbhash}
|
|
646
|
+
<img
|
|
647
|
+
class="thumbnail-blur"
|
|
648
|
+
src={decodeThumbHash(item.thumbhash)}
|
|
649
|
+
alt=""
|
|
650
|
+
aria-hidden="true"
|
|
651
|
+
draggable="false" />
|
|
652
|
+
{/if}
|
|
653
|
+
{#if thumbSrc}
|
|
654
|
+
{@const responsive = isImage && isResponsiveSrcset(item.src)}
|
|
655
|
+
<!--
|
|
656
|
+
Match the Carousel's image attributes for single-URL sources so the
|
|
657
|
+
lightbox <img> hits this thumbnail's memory-cached pixels instead of
|
|
658
|
+
re-fetching. Mismatched srcset/sizes attributes between the two <img>
|
|
659
|
+
elements make Chrome treat them as separate "responsive selections"
|
|
660
|
+
and bypass the memory cache when devtools "Disable cache" is on.
|
|
661
|
+
-->
|
|
662
|
+
<img
|
|
663
|
+
class="thumbnail-img"
|
|
664
|
+
class:fading={fadingKeys.has(key)}
|
|
665
|
+
class:no-blur={!item.thumbhash}
|
|
666
|
+
src={thumbSrc}
|
|
667
|
+
srcset={responsive ? item.src : undefined}
|
|
668
|
+
sizes={responsive ? (eager ? sizesFallback : `auto, ${sizesFallback}`) : undefined}
|
|
669
|
+
alt={item.alt ?? item.name ?? ''}
|
|
670
|
+
loading={eager ? 'eager' : 'lazy'}
|
|
671
|
+
fetchpriority={eager ? 'high' : undefined}
|
|
672
|
+
draggable="false"
|
|
673
|
+
onload={() => fadingKeys.delete(key)}
|
|
674
|
+
onerror={() => fadingKeys.delete(key)}
|
|
675
|
+
{@attach (el: HTMLImageElement) => {
|
|
676
|
+
if (!el.complete || el.naturalWidth === 0) {
|
|
677
|
+
fadingKeys.add(key);
|
|
678
|
+
}
|
|
679
|
+
}} />
|
|
680
|
+
{:else}
|
|
681
|
+
<!-- No thumbnail available — render a styled placeholder so the type-icon
|
|
682
|
+
overlay has a background and the layout slot still has its expected
|
|
683
|
+
aspect ratio. -->
|
|
684
|
+
<div class="thumbnail-placeholder" aria-hidden="true"></div>
|
|
685
|
+
{/if}
|
|
686
|
+
{/snippet}
|
|
687
|
+
|
|
688
|
+
{#snippet galleryItemAction(
|
|
689
|
+
index: number,
|
|
690
|
+
style: 'overlay' | 'transparent' = 'overlay',
|
|
691
|
+
button_size: '00' | '0' | '1' = '00',
|
|
692
|
+
)}
|
|
693
|
+
{@const itemActions = actions?.[index]}
|
|
694
|
+
{#if itemActions?.length}
|
|
695
|
+
<div class="actions" class:hover-only={action_display === 'hover'}>
|
|
696
|
+
{#each itemActions as action (action)}
|
|
697
|
+
{#if action?.actions?.length}
|
|
698
|
+
<Button
|
|
699
|
+
icon
|
|
700
|
+
overlay={style === 'overlay'}
|
|
701
|
+
transparent={style === 'transparent'}
|
|
702
|
+
dense
|
|
703
|
+
size={button_size}
|
|
704
|
+
tooltip={action.tooltip || action.name}>
|
|
705
|
+
{#if action.icon}
|
|
706
|
+
<action.icon></action.icon>
|
|
707
|
+
{:else}
|
|
708
|
+
{@render iconDownload()}
|
|
709
|
+
{/if}
|
|
710
|
+
{#snippet menu()}
|
|
711
|
+
<List>
|
|
712
|
+
{#each action?.actions || [] as subAction (subAction)}
|
|
713
|
+
<ListItem
|
|
714
|
+
onclick={(e) => {
|
|
715
|
+
if (subAction.click) subAction.click(e);
|
|
716
|
+
}}
|
|
717
|
+
href={subAction.href}
|
|
718
|
+
target={subAction.target}>
|
|
719
|
+
<span class="list-item-icon">
|
|
720
|
+
{#if subAction.icon}
|
|
721
|
+
<subAction.icon></subAction.icon>
|
|
722
|
+
{:else if action.icon}
|
|
723
|
+
<action.icon></action.icon>
|
|
724
|
+
{:else}
|
|
725
|
+
{@render iconDownload()}
|
|
726
|
+
{/if}
|
|
727
|
+
</span>
|
|
728
|
+
{subAction.name}
|
|
729
|
+
</ListItem>
|
|
730
|
+
{/each}
|
|
731
|
+
</List>
|
|
732
|
+
{/snippet}
|
|
733
|
+
</Button>
|
|
734
|
+
{:else}
|
|
735
|
+
<Button
|
|
736
|
+
icon
|
|
737
|
+
overlay={style === 'overlay'}
|
|
738
|
+
transparent={style === 'transparent'}
|
|
739
|
+
dense
|
|
740
|
+
size={button_size}
|
|
741
|
+
tooltip={action.tooltip || action.name}
|
|
742
|
+
href={action.href}
|
|
743
|
+
target={action.target}
|
|
744
|
+
onclick={(e) => {
|
|
745
|
+
if (action.click) action.click(e);
|
|
746
|
+
}}>
|
|
747
|
+
{#if action.icon}
|
|
748
|
+
<action.icon></action.icon>
|
|
749
|
+
{:else}
|
|
750
|
+
{@render iconDownload()}
|
|
751
|
+
{/if}
|
|
752
|
+
</Button>
|
|
753
|
+
{/if}
|
|
754
|
+
{/each}
|
|
755
|
+
</div>
|
|
756
|
+
{/if}
|
|
757
|
+
{/snippet}
|
|
758
|
+
|
|
759
|
+
{#snippet galleryItem(item: (typeof list)[number], index: number)}
|
|
760
|
+
<div
|
|
761
|
+
class="gallery-item"
|
|
762
|
+
role="button"
|
|
763
|
+
tabindex="0"
|
|
764
|
+
{@attach ripple({ zIndex: 1, opacity: 0.2, color: 'white' })}
|
|
765
|
+
class:favorite={item.favorite}
|
|
766
|
+
style:--ratio={(display === 'masonry-row' || display === 'masonry') &&
|
|
767
|
+
item.width &&
|
|
768
|
+
item.height
|
|
769
|
+
? item.width / item.height
|
|
770
|
+
: undefined}
|
|
771
|
+
{@attach contextMenu({ actions: flattenActions(actions?.[index]) })}
|
|
772
|
+
onclick={(e) => onItemClick(index, e)}
|
|
773
|
+
onkeydown={(e) => e.key !== 'Enter' || onItemClick(index, e)}>
|
|
774
|
+
<div class="image">
|
|
775
|
+
{@render itemThumbnail(item, '100vw')}
|
|
776
|
+
</div>
|
|
777
|
+
{#if item.type !== 'image' || item.panorama}
|
|
778
|
+
<div class="icon">
|
|
779
|
+
{#if item.type === 'video'}
|
|
780
|
+
{@render iconPlay()}
|
|
781
|
+
{:else if item.type === 'pdf'}
|
|
782
|
+
{@render iconDocument()}
|
|
783
|
+
{:else if item.type === 'embed'}
|
|
784
|
+
{@render iconEmbed()}
|
|
785
|
+
{:else if item.panorama}
|
|
786
|
+
{@render iconPanorama()}
|
|
787
|
+
{/if}
|
|
788
|
+
</div>
|
|
789
|
+
{/if}
|
|
790
|
+
{#if meta_display === 'always' || meta_display === 'hover'}
|
|
791
|
+
{#if item.name}
|
|
792
|
+
<div class="name" class:hover-only={meta_display === 'hover'}>{item.name}</div>
|
|
793
|
+
{/if}
|
|
794
|
+
{/if}
|
|
795
|
+
{#if action_display === 'always' || action_display === 'hover'}
|
|
796
|
+
{@render galleryItemAction(index, 'overlay')}
|
|
797
|
+
{/if}
|
|
798
|
+
</div>
|
|
799
|
+
{/snippet}
|
|
800
|
+
|
|
801
|
+
{#if display === 'grid' || display === 'masonry' || display === 'masonry-row'}
|
|
802
|
+
<div
|
|
803
|
+
class="gallery display-{display} size-{size} spacing-{spacing} radius-{radius}"
|
|
804
|
+
role="group"
|
|
805
|
+
{style}>
|
|
806
|
+
{#each list as item, i (i)}
|
|
807
|
+
{@render galleryItem(item, i)}
|
|
808
|
+
{/each}
|
|
809
|
+
</div>
|
|
810
|
+
{/if}
|
|
811
|
+
|
|
812
|
+
{#if display === 'list'}
|
|
813
|
+
<div
|
|
814
|
+
class="gallery display-list size-{size} spacing-{spacing} radius-{radius}"
|
|
815
|
+
role="group"
|
|
816
|
+
{style}>
|
|
817
|
+
{#each list as item, index (index)}
|
|
818
|
+
<div class="list-item">
|
|
819
|
+
<div
|
|
820
|
+
class="info"
|
|
821
|
+
role="button"
|
|
822
|
+
tabindex="0"
|
|
823
|
+
{@attach ripple({
|
|
824
|
+
zIndex: 1,
|
|
825
|
+
opacity: 0.2,
|
|
826
|
+
color: 'var(--color-text, currentColor)',
|
|
827
|
+
})}
|
|
828
|
+
onclick={(e) => onItemClick(index, e)}
|
|
829
|
+
onkeydown={(e) => e.key !== 'Enter' || onItemClick(index, e)}
|
|
830
|
+
{@attach contextMenu({ actions: flattenActions(actions?.[index]) })}>
|
|
831
|
+
<div class="thumbnail">
|
|
832
|
+
{@render itemThumbnail(item, '64px')}
|
|
833
|
+
{#if item.type !== 'image' || item.panorama}
|
|
834
|
+
<div class="icon">
|
|
835
|
+
{#if item.type === 'video'}
|
|
836
|
+
{@render iconPlay()}
|
|
837
|
+
{:else if item.type === 'pdf'}
|
|
838
|
+
{@render iconDocument()}
|
|
839
|
+
{:else if item.type === 'embed'}
|
|
840
|
+
{@render iconEmbed()}
|
|
841
|
+
{:else if item.panorama}
|
|
842
|
+
{@render iconPanorama()}
|
|
843
|
+
{/if}
|
|
844
|
+
</div>
|
|
845
|
+
{/if}
|
|
846
|
+
</div>
|
|
847
|
+
<div class="name">{item.name || ''}</div>
|
|
848
|
+
</div>
|
|
849
|
+
{#if action_display === 'always' || action_display === 'hover'}
|
|
850
|
+
{@render galleryItemAction(index, 'transparent')}
|
|
851
|
+
{/if}
|
|
852
|
+
</div>
|
|
853
|
+
{/each}
|
|
854
|
+
</div>
|
|
855
|
+
{/if}
|
|
856
|
+
|
|
857
|
+
{#snippet sliderControls()}
|
|
858
|
+
<div
|
|
859
|
+
class="controls"
|
|
860
|
+
in:fade={{ duration: 150 }}
|
|
861
|
+
out:fade={{ duration: 150 }}
|
|
862
|
+
style:opacity={1 - dismissing}>
|
|
863
|
+
<!-- Always rendered (and positioned absolutely) so it never re-mounts as
|
|
864
|
+
`num_pages` settles while a PDF loads its pages — that remounting was
|
|
865
|
+
replaying the scale-in transition repeatedly. Visibility is toggled
|
|
866
|
+
purely with CSS instead. -->
|
|
867
|
+
<nav class="pages" class:shown={num_pages > 1} aria-hidden={num_pages <= 1}>
|
|
868
|
+
<Button
|
|
869
|
+
icon
|
|
870
|
+
transparent
|
|
871
|
+
size="0"
|
|
872
|
+
disabled={page <= 0}
|
|
873
|
+
onclick={() => (page = Math.max(0, page - 1))}
|
|
874
|
+
tooltip="Previous page">
|
|
875
|
+
<span class="visuallyhidden">Previous page</span>
|
|
876
|
+
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
877
|
+
<path
|
|
878
|
+
d="M10 3L5 8L10 13"
|
|
879
|
+
stroke="currentColor"
|
|
880
|
+
stroke-width="1.8"
|
|
881
|
+
stroke-linecap="round"
|
|
882
|
+
stroke-linejoin="round" />
|
|
883
|
+
</svg>
|
|
884
|
+
</Button>
|
|
885
|
+
<span class="page-counter" aria-live="polite">
|
|
886
|
+
<span class="page-current">{page + 1}</span>
|
|
887
|
+
<span class="page-separator">/</span>
|
|
888
|
+
<span class="page-total">{num_pages}</span>
|
|
889
|
+
</span>
|
|
890
|
+
<Button
|
|
891
|
+
icon
|
|
892
|
+
transparent
|
|
893
|
+
size="0"
|
|
894
|
+
disabled={page >= num_pages - 1}
|
|
895
|
+
onclick={() => (page = Math.min(num_pages - 1, page + 1))}
|
|
896
|
+
tooltip="Next page">
|
|
897
|
+
<span class="visuallyhidden">Next page</span>
|
|
898
|
+
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
899
|
+
<path
|
|
900
|
+
d="M6 3L11 8L6 13"
|
|
901
|
+
stroke="currentColor"
|
|
902
|
+
stroke-width="1.8"
|
|
903
|
+
stroke-linecap="round"
|
|
904
|
+
stroke-linejoin="round" />
|
|
905
|
+
</svg>
|
|
906
|
+
</Button>
|
|
907
|
+
</nav>
|
|
908
|
+
{#if isModal}
|
|
909
|
+
<Button icon transparent dense size="1" class="close" onclick={() => close()}>
|
|
910
|
+
<span class="visuallyhidden">Close</span>
|
|
911
|
+
{@render iconClose()}
|
|
912
|
+
</Button>
|
|
913
|
+
{#if actions?.length}
|
|
914
|
+
{@render galleryItemAction(slide, 'transparent', '1')}
|
|
915
|
+
{/if}
|
|
916
|
+
{:else}
|
|
917
|
+
{#if disable_fullscreen === false}
|
|
918
|
+
<Button
|
|
919
|
+
icon
|
|
920
|
+
transparent
|
|
921
|
+
dense
|
|
922
|
+
size="0"
|
|
923
|
+
class="fullscreen"
|
|
924
|
+
tooltip="Toggle Fullscreen"
|
|
925
|
+
onclick={() => toggleFullscreen()}>
|
|
926
|
+
<span class="visuallyhidden">Fullscreen</span>
|
|
927
|
+
{#if fullscreenActive}
|
|
928
|
+
{@render iconFullscreenExit()}
|
|
929
|
+
{:else}
|
|
930
|
+
{@render iconFullscreen()}
|
|
931
|
+
{/if}
|
|
932
|
+
</Button>
|
|
933
|
+
{/if}
|
|
934
|
+
{#if list.length > 1}
|
|
935
|
+
<Button
|
|
936
|
+
icon
|
|
937
|
+
transparent
|
|
938
|
+
dense
|
|
939
|
+
size="0"
|
|
940
|
+
class="play"
|
|
941
|
+
tooltip={autoplayTransitionTimer ? 'Pause Slideshow' : 'Start Slideshow'}
|
|
942
|
+
onclick={() => (autoplayTransitionTimer ? pause() : play())}>
|
|
943
|
+
<span class="visuallyhidden">Start Slideshow</span>
|
|
944
|
+
{#if autoplayTransitionTimer}
|
|
945
|
+
{@render iconPause()}
|
|
946
|
+
{:else}
|
|
947
|
+
{@render iconPlay()}
|
|
948
|
+
{/if}
|
|
949
|
+
{#if autoplayTransitionTimer}
|
|
950
|
+
{@const progress = autoplayTransitionProgress || 0}
|
|
951
|
+
<svg
|
|
952
|
+
class="progress"
|
|
953
|
+
viewBox="0 0 56 56"
|
|
954
|
+
style:--progress={progress}
|
|
955
|
+
style:--speed="{autoplayTransitionInterval}ms"
|
|
956
|
+
style:transition={progress >= 0.99 || progress < 0.01 ? 'none' : null}>
|
|
957
|
+
<circle cx="28" cy="28" r="26" />
|
|
958
|
+
</svg>
|
|
959
|
+
{/if}
|
|
960
|
+
</Button>
|
|
961
|
+
{/if}
|
|
962
|
+
{/if}
|
|
963
|
+
<div class="spacer"></div>
|
|
964
|
+
{#if list.length > 1}
|
|
965
|
+
<div class="pagination">{Math.max(0, slide) + 1} / {list.length}</div>
|
|
966
|
+
{/if}
|
|
967
|
+
{#if list.length > 1}
|
|
968
|
+
<Button
|
|
969
|
+
icon
|
|
970
|
+
transparent
|
|
971
|
+
dense
|
|
972
|
+
size={isModal ? '1' : '0'}
|
|
973
|
+
class="prev"
|
|
974
|
+
onclick={() => prev()}
|
|
975
|
+
tooltip="Previous Item">
|
|
976
|
+
<span class="visuallyhidden">Previous Item</span>
|
|
977
|
+
{@render iconChevronLeft()}
|
|
978
|
+
</Button>
|
|
979
|
+
<Button
|
|
980
|
+
icon
|
|
981
|
+
transparent
|
|
982
|
+
dense
|
|
983
|
+
size={isModal ? '1' : '0'}
|
|
984
|
+
class="next"
|
|
985
|
+
onclick={() => next()}
|
|
986
|
+
tooltip="Next Item">
|
|
987
|
+
<span class="visuallyhidden">Next Item</span>
|
|
988
|
+
{@render iconChevronRight()}
|
|
989
|
+
</Button>
|
|
990
|
+
{/if}
|
|
991
|
+
</div>
|
|
992
|
+
{/snippet}
|
|
993
|
+
|
|
994
|
+
{#snippet slider()}
|
|
995
|
+
{#if sliderActive}
|
|
996
|
+
<div
|
|
997
|
+
class="gallery slider size-{size} radius-{radius}"
|
|
998
|
+
class:modal={isModal}
|
|
999
|
+
class:controls-inline={controls === 'inline' ||
|
|
1000
|
+
(controls === 'default' && !isModal)}
|
|
1001
|
+
class:controls-overlay={controls === 'overlay' ||
|
|
1002
|
+
(controls === 'default' && isModal)}
|
|
1003
|
+
class:fullscreen={fullscreenActive}
|
|
1004
|
+
style={!isModal && (display === 'slider' || display === 'slideshow') ? style : null}
|
|
1005
|
+
style:--aspect-ratio={isModal || !aspect_ratio ? null : aspect_ratio}
|
|
1006
|
+
aria-label="Media Gallery Carousel"
|
|
1007
|
+
out:carouselCloseTransition
|
|
1008
|
+
{@attach intersectionObserver({
|
|
1009
|
+
enabled: true,
|
|
1010
|
+
onintersectchange: (event) => (intersected = event.isIntersecting),
|
|
1011
|
+
})}
|
|
1012
|
+
{@attach focusTrap({
|
|
1013
|
+
preventScroll: true,
|
|
1014
|
+
onPostDeactivate: () => (slide = -1),
|
|
1015
|
+
allowOutsideClick: true,
|
|
1016
|
+
enabled: isModal,
|
|
1017
|
+
escapeDeactivates: (e) => {
|
|
1018
|
+
e.stopPropagation();
|
|
1019
|
+
return true;
|
|
1020
|
+
},
|
|
1021
|
+
setReturnFocus: focusTrapSetReturnFocus,
|
|
1022
|
+
oninit: (instance) => (focusTrapInstance = instance),
|
|
1023
|
+
initialFocus: false,
|
|
1024
|
+
})}
|
|
1025
|
+
{@attach contextMenu({ actions: flattenActions(actions?.[slide]) })}>
|
|
1026
|
+
<div
|
|
1027
|
+
class="bg"
|
|
1028
|
+
in:fade={{ duration: 350 }}
|
|
1029
|
+
out:fade={{ duration: 350 }}
|
|
1030
|
+
style:opacity={1 - dismissing}>
|
|
1031
|
+
</div>
|
|
1032
|
+
<Carousel
|
|
1033
|
+
items={list}
|
|
1034
|
+
bind:dismissing
|
|
1035
|
+
bind:this={carousel}
|
|
1036
|
+
bind:slide
|
|
1037
|
+
bind:page
|
|
1038
|
+
bind:num_pages
|
|
1039
|
+
animation={(display === 'slider' || display === 'slideshow') &&
|
|
1040
|
+
autoplayTransitionTimer &&
|
|
1041
|
+
list.length > 1
|
|
1042
|
+
? 'zoom'
|
|
1043
|
+
: 'none'}
|
|
1044
|
+
transition={(display === 'slider' || display === 'slideshow') &&
|
|
1045
|
+
autoplayTransitionTimer
|
|
1046
|
+
? 'fade'
|
|
1047
|
+
: 'none'}
|
|
1048
|
+
inline={inline ??
|
|
1049
|
+
((display === 'slider' || display === 'slideshow') && !fullscreenActive)}
|
|
1050
|
+
{autoplay_video}
|
|
1051
|
+
dismissable={isModal}
|
|
1052
|
+
disable_entry_exit_animation={display === 'slider' || display === 'slideshow'}
|
|
1053
|
+
animation_target={animationTarget}
|
|
1054
|
+
{fit}
|
|
1055
|
+
{custom}
|
|
1056
|
+
oninteraction={() => pause()}
|
|
1057
|
+
onclose={() => {
|
|
1058
|
+
if (fullscreenActive) return closeFullscreen();
|
|
1059
|
+
slide = -1;
|
|
1060
|
+
}} />
|
|
1061
|
+
{#if meta_display_fullscreen === 'always' && isModal && (list[slide]?.caption || list[slide]?.name)}
|
|
1062
|
+
<div class="fullscreen-name" style:opacity={1 - dismissing}>
|
|
1063
|
+
{list[slide]?.caption || list[slide]?.name}
|
|
1064
|
+
</div>
|
|
1065
|
+
{/if}
|
|
1066
|
+
{#if controls !== 'disable'}
|
|
1067
|
+
{@render sliderControls()}
|
|
1068
|
+
{/if}
|
|
1069
|
+
<div class="visuallyhidden" aria-live="polite" aria-atomic="true" inert>
|
|
1070
|
+
Media Item {slide + 1} of {list.length}
|
|
1071
|
+
</div>
|
|
1072
|
+
</div>
|
|
1073
|
+
{/if}
|
|
1074
|
+
{/snippet}
|
|
1075
|
+
|
|
1076
|
+
{#if (display !== 'slider' && display !== 'slideshow') || isModal}
|
|
1077
|
+
<Portal>
|
|
1078
|
+
{@render slider()}
|
|
1079
|
+
</Portal>
|
|
1080
|
+
{:else}
|
|
1081
|
+
{@render slider()}
|
|
1082
|
+
{/if}
|
|
1083
|
+
|
|
1084
|
+
<style>
|
|
1085
|
+
.visuallyhidden {
|
|
1086
|
+
border: 0;
|
|
1087
|
+
clip: rect(0 0 0 0);
|
|
1088
|
+
clip-path: inset(50%);
|
|
1089
|
+
height: 1px;
|
|
1090
|
+
margin: -1px;
|
|
1091
|
+
overflow: hidden;
|
|
1092
|
+
padding: 0;
|
|
1093
|
+
position: absolute;
|
|
1094
|
+
width: 1px;
|
|
1095
|
+
white-space: nowrap;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
.pagination {
|
|
1099
|
+
white-space: nowrap;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
.list-item-icon {
|
|
1103
|
+
display: inline-flex;
|
|
1104
|
+
align-items: center;
|
|
1105
|
+
padding-right: 0.5rem;
|
|
1106
|
+
font-size: 1.1rem;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
.list-item-icon :global(svg),
|
|
1110
|
+
.icon :global(svg) {
|
|
1111
|
+
width: 1em;
|
|
1112
|
+
height: 1em;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
.gallery {
|
|
1116
|
+
/* Galleries are the largest surface and reach --radius-3xl at their
|
|
1117
|
+
biggest size by design, so they clamp each radius tier to that ceiling
|
|
1118
|
+
rather than the smaller shared --radius-cap: an over-rounded radius
|
|
1119
|
+
token can't blob a gallery, while the shipped looks (incl. the 3xl
|
|
1120
|
+
tier) are never clipped. Private (--_cap) so the raised ceiling doesn't
|
|
1121
|
+
leak to nested components. Both radius systems funnel through these:
|
|
1122
|
+
the slider .bg/.carousel/.controls use them directly, and the grid/
|
|
1123
|
+
masonry size remaps assign them to --radius-lg. */
|
|
1124
|
+
--_cap: var(--radius-3xl, 60px);
|
|
1125
|
+
--_rxl: min(var(--radius-xl, 0.75rem), var(--_cap));
|
|
1126
|
+
--_r2xl: min(var(--radius-2xl, 1rem), var(--_cap));
|
|
1127
|
+
--_r3xl: min(var(--radius-3xl, 1.5rem), var(--_cap));
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
.gallery-item {
|
|
1131
|
+
position: relative;
|
|
1132
|
+
display: grid;
|
|
1133
|
+
grid-template-rows: 1fr;
|
|
1134
|
+
grid-template-columns: 1fr;
|
|
1135
|
+
cursor: pointer;
|
|
1136
|
+
border-radius: var(--radius-lg);
|
|
1137
|
+
@supports (corner-shape: squircle) {
|
|
1138
|
+
corner-shape: squircle;
|
|
1139
|
+
border-radius: calc(var(--radius-lg) * var(--squircle-ratio, 2));
|
|
1140
|
+
}
|
|
1141
|
+
isolation: isolate;
|
|
1142
|
+
overflow: hidden;
|
|
1143
|
+
transition:
|
|
1144
|
+
box-shadow 150ms ease,
|
|
1145
|
+
scale 150ms ease;
|
|
1146
|
+
box-shadow: var(--shadow-sm);
|
|
1147
|
+
background-color: var(--color-bg-muted, var(--bg-high));
|
|
1148
|
+
|
|
1149
|
+
.image {
|
|
1150
|
+
position: absolute;
|
|
1151
|
+
inset: 0;
|
|
1152
|
+
transition: transform 150ms ease;
|
|
1153
|
+
transform: scale(1);
|
|
1154
|
+
will-change: transform;
|
|
1155
|
+
overflow: hidden;
|
|
1156
|
+
}
|
|
1157
|
+
.thumbnail-blur,
|
|
1158
|
+
.thumbnail-img,
|
|
1159
|
+
.thumbnail-placeholder {
|
|
1160
|
+
position: absolute;
|
|
1161
|
+
inset: 0;
|
|
1162
|
+
width: 100%;
|
|
1163
|
+
height: 100%;
|
|
1164
|
+
object-fit: cover;
|
|
1165
|
+
display: block;
|
|
1166
|
+
}
|
|
1167
|
+
.thumbnail-blur {
|
|
1168
|
+
z-index: 0;
|
|
1169
|
+
filter: blur(24px) saturate(1.2) contrast(1.05);
|
|
1170
|
+
transform: scale(1.2);
|
|
1171
|
+
pointer-events: none;
|
|
1172
|
+
user-select: none;
|
|
1173
|
+
}
|
|
1174
|
+
.thumbnail-placeholder {
|
|
1175
|
+
z-index: 0;
|
|
1176
|
+
background: linear-gradient(
|
|
1177
|
+
135deg,
|
|
1178
|
+
light-dark(rgba(0, 0, 0, 0.04), rgba(255, 255, 255, 0.04)),
|
|
1179
|
+
light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1))
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
.thumbnail-img {
|
|
1183
|
+
z-index: 1;
|
|
1184
|
+
opacity: 1;
|
|
1185
|
+
transform: scale(1);
|
|
1186
|
+
transition:
|
|
1187
|
+
opacity 400ms ease,
|
|
1188
|
+
transform 700ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
1189
|
+
&.no-blur {
|
|
1190
|
+
transition:
|
|
1191
|
+
opacity 250ms ease,
|
|
1192
|
+
transform 400ms ease;
|
|
1193
|
+
}
|
|
1194
|
+
/* JS adds .fading after mount only when the image isn't already loaded.
|
|
1195
|
+
When .fading is removed (on `onload`), the default transition above
|
|
1196
|
+
smoothly fades the image in over the blur. */
|
|
1197
|
+
&.fading {
|
|
1198
|
+
opacity: 0;
|
|
1199
|
+
transform: scale(1.04);
|
|
1200
|
+
transition: none;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
&:active {
|
|
1205
|
+
scale: 0.98;
|
|
1206
|
+
}
|
|
1207
|
+
&:hover {
|
|
1208
|
+
box-shadow: var(--shadow-md);
|
|
1209
|
+
.image {
|
|
1210
|
+
opacity: 0.97;
|
|
1211
|
+
transform: scale(1.018);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
&:focus {
|
|
1215
|
+
outline: solid 4px var(--color-text, var(--text));
|
|
1216
|
+
}
|
|
1217
|
+
&:focus:not(:focus-visible) {
|
|
1218
|
+
outline: none;
|
|
1219
|
+
}
|
|
1220
|
+
> :global(*) {
|
|
1221
|
+
grid-row: 1 / 1;
|
|
1222
|
+
grid-column: 1 / 1;
|
|
1223
|
+
position: relative;
|
|
1224
|
+
}
|
|
1225
|
+
.icon {
|
|
1226
|
+
display: flex;
|
|
1227
|
+
align-items: center;
|
|
1228
|
+
justify-content: center;
|
|
1229
|
+
justify-self: center;
|
|
1230
|
+
align-self: center;
|
|
1231
|
+
color: white;
|
|
1232
|
+
background-color: rgba(0, 0, 0, 0.6);
|
|
1233
|
+
border-radius: 100%;
|
|
1234
|
+
width: min(max(20%, 3rem), 7rem);
|
|
1235
|
+
aspect-ratio: 1 / 1;
|
|
1236
|
+
padding: 0.5rem;
|
|
1237
|
+
backdrop-filter: blur(10px);
|
|
1238
|
+
:global(svg) {
|
|
1239
|
+
width: max(2rem, 70%);
|
|
1240
|
+
height: max(2rem, 70%);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
.name {
|
|
1244
|
+
position: absolute;
|
|
1245
|
+
bottom: 0;
|
|
1246
|
+
left: 0;
|
|
1247
|
+
right: 0;
|
|
1248
|
+
width: 100%;
|
|
1249
|
+
color: white;
|
|
1250
|
+
background-color: rgba(0, 0, 0, 0.6);
|
|
1251
|
+
padding: max(0.5rem, calc(var(--radius-lg, 0px) / 2))
|
|
1252
|
+
max(1rem, var(--radius-lg, 0px));
|
|
1253
|
+
text-overflow: ellipsis;
|
|
1254
|
+
overflow: hidden;
|
|
1255
|
+
white-space: nowrap;
|
|
1256
|
+
backdrop-filter: blur(5px);
|
|
1257
|
+
&.hover-only {
|
|
1258
|
+
transition: transform 250ms ease;
|
|
1259
|
+
backdrop-filter: none;
|
|
1260
|
+
transform: translate3d(0, 100%, 0);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
.actions {
|
|
1264
|
+
position: absolute;
|
|
1265
|
+
top: max(4px, min(16px, var(--radius-lg, 0px)));
|
|
1266
|
+
right: max(4px, min(16px, var(--radius-lg, 0px)));
|
|
1267
|
+
z-index: 2;
|
|
1268
|
+
display: flex;
|
|
1269
|
+
gap: 0.25rem;
|
|
1270
|
+
&.hover-only {
|
|
1271
|
+
opacity: 0;
|
|
1272
|
+
transition: opacity 250ms ease;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
&:focus-visible,
|
|
1276
|
+
&:has(.actions:focus-within) {
|
|
1277
|
+
.actions {
|
|
1278
|
+
opacity: 1;
|
|
1279
|
+
}
|
|
1280
|
+
.name {
|
|
1281
|
+
transform: translate3d(0px, 0px, 0px);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
@media (hover: hover) and (pointer: fine) {
|
|
1286
|
+
&:hover {
|
|
1287
|
+
.name.hover-only {
|
|
1288
|
+
transform: translate3d(0px, 0px, 0px);
|
|
1289
|
+
}
|
|
1290
|
+
.actions.hover-only {
|
|
1291
|
+
opacity: 1;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
@media not ((hover: hover) and (pointer: fine)) {
|
|
1296
|
+
.actions.hover-only {
|
|
1297
|
+
display: none;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
.gallery.slider {
|
|
1303
|
+
z-index: 1;
|
|
1304
|
+
perspective: 100px;
|
|
1305
|
+
perspective-origin: center center;
|
|
1306
|
+
user-select: none;
|
|
1307
|
+
-webkit-user-select: none;
|
|
1308
|
+
-webkit-tap-highlight-color: transparent;
|
|
1309
|
+
transform: translateZ(0px);
|
|
1310
|
+
position: relative;
|
|
1311
|
+
margin: 0 auto;
|
|
1312
|
+
|
|
1313
|
+
:global(.carousel) {
|
|
1314
|
+
position: relative;
|
|
1315
|
+
z-index: 1;
|
|
1316
|
+
height: 100%;
|
|
1317
|
+
}
|
|
1318
|
+
.bg {
|
|
1319
|
+
position: absolute;
|
|
1320
|
+
top: 0;
|
|
1321
|
+
left: 0;
|
|
1322
|
+
right: 0;
|
|
1323
|
+
bottom: 0;
|
|
1324
|
+
z-index: -1;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
.controls {
|
|
1328
|
+
display: flex;
|
|
1329
|
+
position: absolute;
|
|
1330
|
+
align-items: center;
|
|
1331
|
+
bottom: 0;
|
|
1332
|
+
left: 0;
|
|
1333
|
+
right: 0;
|
|
1334
|
+
width: 100%;
|
|
1335
|
+
height: 3.5rem;
|
|
1336
|
+
gap: 1rem;
|
|
1337
|
+
padding: 0 1rem;
|
|
1338
|
+
pointer-events: none;
|
|
1339
|
+
:global(.button) {
|
|
1340
|
+
pointer-events: all;
|
|
1341
|
+
}
|
|
1342
|
+
.spacer {
|
|
1343
|
+
flex: 1;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
nav.pages {
|
|
1348
|
+
position: absolute;
|
|
1349
|
+
z-index: 2;
|
|
1350
|
+
display: flex;
|
|
1351
|
+
align-items: center;
|
|
1352
|
+
gap: 0.125rem;
|
|
1353
|
+
padding-inline: 0.25rem;
|
|
1354
|
+
/* Show/hide via CSS (no remount) so the entry animation can't replay
|
|
1355
|
+
as num_pages settles during PDF load. */
|
|
1356
|
+
transform-origin: center center;
|
|
1357
|
+
opacity: 0;
|
|
1358
|
+
scale: 0.6;
|
|
1359
|
+
visibility: hidden;
|
|
1360
|
+
transition:
|
|
1361
|
+
opacity 300ms cubic-bezier(0.34, 1.56, 0.64, 1),
|
|
1362
|
+
scale 300ms cubic-bezier(0.34, 1.56, 0.64, 1),
|
|
1363
|
+
visibility 0s linear 300ms;
|
|
1364
|
+
&.shown {
|
|
1365
|
+
opacity: 1;
|
|
1366
|
+
scale: 1;
|
|
1367
|
+
visibility: visible;
|
|
1368
|
+
transition:
|
|
1369
|
+
opacity 300ms cubic-bezier(0.34, 1.56, 0.64, 1),
|
|
1370
|
+
scale 300ms cubic-bezier(0.34, 1.56, 0.64, 1),
|
|
1371
|
+
visibility 0s linear 0s;
|
|
1372
|
+
}
|
|
1373
|
+
/* Force a high-contrast white pill so the page numbers are
|
|
1374
|
+
readable on top of any media (dark images, videos, PDFs,
|
|
1375
|
+
panoramas) inside a modal. The inherited button colors
|
|
1376
|
+
pick this up via --color-text. */
|
|
1377
|
+
color: #1e293b;
|
|
1378
|
+
--color-text: #1e293b;
|
|
1379
|
+
--color-action: #3b82f6;
|
|
1380
|
+
background-color: #ffffff;
|
|
1381
|
+
border-radius: 9999px;
|
|
1382
|
+
box-shadow:
|
|
1383
|
+
0 4px 12px rgb(0 0 0 / 0.18),
|
|
1384
|
+
0 1px 3px rgb(0 0 0 / 0.12);
|
|
1385
|
+
border: none;
|
|
1386
|
+
outline: none;
|
|
1387
|
+
font-weight: 500;
|
|
1388
|
+
|
|
1389
|
+
.page-counter {
|
|
1390
|
+
display: inline-flex;
|
|
1391
|
+
align-items: baseline;
|
|
1392
|
+
gap: 0.25rem;
|
|
1393
|
+
min-width: 4rem;
|
|
1394
|
+
justify-content: center;
|
|
1395
|
+
padding-inline: 0.5rem;
|
|
1396
|
+
font-variant-numeric: tabular-nums;
|
|
1397
|
+
font-size: 1.35rem;
|
|
1398
|
+
line-height: 1;
|
|
1399
|
+
user-select: none;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
.page-separator {
|
|
1403
|
+
opacity: 0.4;
|
|
1404
|
+
font-weight: 400;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/* The chevron Buttons inherit `--color-text` from the pill. The
|
|
1408
|
+
`disabled` attribute lives on the inner <button> rendered by
|
|
1409
|
+
`Button.svelte`, so dim the wrapper via :has() — the transparent
|
|
1410
|
+
variant has no background of its own to dim otherwise. */
|
|
1411
|
+
:global(.button:has(> button[disabled])) {
|
|
1412
|
+
opacity: 0.3;
|
|
1413
|
+
cursor: default;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
.pagination {
|
|
1418
|
+
z-index: 1;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
:global(.play svg.progress) {
|
|
1422
|
+
stroke-width: 3;
|
|
1423
|
+
stroke-dasharray: 163.41;
|
|
1424
|
+
stroke-dashoffset: calc(163.41 - (163.41 * var(--progress, 0)));
|
|
1425
|
+
transition: stroke-dashoffset var(--speed, 300ms) linear;
|
|
1426
|
+
width: 70%;
|
|
1427
|
+
height: 70%;
|
|
1428
|
+
stroke: white;
|
|
1429
|
+
fill: none;
|
|
1430
|
+
transform: rotate(-90deg) !important;
|
|
1431
|
+
transform-origin: center center;
|
|
1432
|
+
opacity: 0.25;
|
|
1433
|
+
position: absolute;
|
|
1434
|
+
top: 15%;
|
|
1435
|
+
left: 15%;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
.gallery.slider:not(.modal) {
|
|
1440
|
+
container: gallery-slider / inline-size;
|
|
1441
|
+
:global(.carousel) {
|
|
1442
|
+
aspect-ratio: var(--aspect-ratio);
|
|
1443
|
+
}
|
|
1444
|
+
.bg {
|
|
1445
|
+
background-color: var(--color-bg-muted, var(--bg-high));
|
|
1446
|
+
}
|
|
1447
|
+
&.size-0 {
|
|
1448
|
+
--aspect-ratio: 1 / 1;
|
|
1449
|
+
height: auto;
|
|
1450
|
+
&.radius-1 {
|
|
1451
|
+
.bg,
|
|
1452
|
+
:global(.carousel) {
|
|
1453
|
+
@container (min-width: 80ch) {
|
|
1454
|
+
border-radius: var(--radius-lg, 0.5rem);
|
|
1455
|
+
@supports (corner-shape: squircle) {
|
|
1456
|
+
corner-shape: squircle;
|
|
1457
|
+
border-radius: calc(var(--radius-lg, 0.5rem) * var(--squircle-ratio, 2));
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
&.radius-2 {
|
|
1463
|
+
.bg,
|
|
1464
|
+
:global(.carousel) {
|
|
1465
|
+
@container (min-width: 80ch) {
|
|
1466
|
+
border-radius: var(--_rxl);
|
|
1467
|
+
@supports (corner-shape: squircle) {
|
|
1468
|
+
corner-shape: squircle;
|
|
1469
|
+
border-radius: calc(var(--_rxl) * var(--squircle-ratio, 2));
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
&.radius-3 {
|
|
1475
|
+
.bg,
|
|
1476
|
+
:global(.carousel) {
|
|
1477
|
+
@container (min-width: 80ch) {
|
|
1478
|
+
border-radius: var(--_r2xl);
|
|
1479
|
+
@supports (corner-shape: squircle) {
|
|
1480
|
+
corner-shape: squircle;
|
|
1481
|
+
border-radius: calc(var(--_r2xl) * var(--squircle-ratio, 2));
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
&.size-1 {
|
|
1488
|
+
&.radius-1 {
|
|
1489
|
+
.bg,
|
|
1490
|
+
:global(.carousel) {
|
|
1491
|
+
@container (min-width: 1200px) {
|
|
1492
|
+
border-radius: var(--_rxl);
|
|
1493
|
+
@supports (corner-shape: squircle) {
|
|
1494
|
+
corner-shape: squircle;
|
|
1495
|
+
border-radius: calc(var(--_rxl) * var(--squircle-ratio, 2));
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
&.radius-2 {
|
|
1501
|
+
.bg,
|
|
1502
|
+
:global(.carousel) {
|
|
1503
|
+
@container (min-width: 1200px) {
|
|
1504
|
+
border-radius: var(--_r2xl);
|
|
1505
|
+
@supports (corner-shape: squircle) {
|
|
1506
|
+
corner-shape: squircle;
|
|
1507
|
+
border-radius: calc(var(--_r2xl) * var(--squircle-ratio, 2));
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
&.radius-3 {
|
|
1513
|
+
.bg,
|
|
1514
|
+
:global(.carousel) {
|
|
1515
|
+
@container (min-width: 1200px) {
|
|
1516
|
+
border-radius: var(--_r3xl);
|
|
1517
|
+
@supports (corner-shape: squircle) {
|
|
1518
|
+
corner-shape: squircle;
|
|
1519
|
+
border-radius: calc(var(--_r3xl) * var(--squircle-ratio, 2));
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
.gallery.slider:not(.modal).controls-overlay {
|
|
1528
|
+
&.radius-1 {
|
|
1529
|
+
.controls {
|
|
1530
|
+
border-top-left-radius: var(--_rxl);
|
|
1531
|
+
border-top-right-radius: var(--_rxl);
|
|
1532
|
+
@supports (corner-shape: squircle) {
|
|
1533
|
+
corner-shape: squircle;
|
|
1534
|
+
border-top-left-radius: calc(var(--_rxl) * var(--squircle-ratio, 2));
|
|
1535
|
+
border-top-right-radius: calc(var(--_rxl) * var(--squircle-ratio, 2));
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
&.radius-2 {
|
|
1540
|
+
.controls {
|
|
1541
|
+
border-top-left-radius: var(--_r2xl);
|
|
1542
|
+
border-top-right-radius: var(--_r2xl);
|
|
1543
|
+
border-bottom-left-radius: var(--_r2xl);
|
|
1544
|
+
border-bottom-right-radius: var(--_r2xl);
|
|
1545
|
+
@supports (corner-shape: squircle) {
|
|
1546
|
+
corner-shape: squircle;
|
|
1547
|
+
border-top-left-radius: calc(var(--_r2xl) * var(--squircle-ratio, 2));
|
|
1548
|
+
border-top-right-radius: calc(var(--_r2xl) * var(--squircle-ratio, 2));
|
|
1549
|
+
border-bottom-left-radius: calc(var(--_r2xl) * var(--squircle-ratio, 2));
|
|
1550
|
+
border-bottom-right-radius: calc(var(--_r2xl) * var(--squircle-ratio, 2));
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
&.radius-3 {
|
|
1555
|
+
.controls {
|
|
1556
|
+
border-top-left-radius: var(--_r3xl);
|
|
1557
|
+
border-top-right-radius: var(--_r3xl);
|
|
1558
|
+
border-bottom-left-radius: var(--_r3xl);
|
|
1559
|
+
border-bottom-right-radius: var(--_r3xl);
|
|
1560
|
+
@supports (corner-shape: squircle) {
|
|
1561
|
+
corner-shape: squircle;
|
|
1562
|
+
border-top-left-radius: calc(var(--_r3xl) * var(--squircle-ratio, 2));
|
|
1563
|
+
border-top-right-radius: calc(var(--_r3xl) * var(--squircle-ratio, 2));
|
|
1564
|
+
border-bottom-left-radius: calc(var(--_r3xl) * var(--squircle-ratio, 2));
|
|
1565
|
+
border-bottom-right-radius: calc(var(--_r3xl) * var(--squircle-ratio, 2));
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
.controls {
|
|
1570
|
+
z-index: 2;
|
|
1571
|
+
justify-content: center;
|
|
1572
|
+
> .spacer {
|
|
1573
|
+
display: none;
|
|
1574
|
+
}
|
|
1575
|
+
background-color: color-mix(
|
|
1576
|
+
in oklch,
|
|
1577
|
+
var(--color-bg-muted, var(--bg-high)),
|
|
1578
|
+
transparent 30%
|
|
1579
|
+
);
|
|
1580
|
+
backdrop-filter: blur(10px);
|
|
1581
|
+
width: fit-content;
|
|
1582
|
+
left: 50%;
|
|
1583
|
+
transform: translateX(-50%);
|
|
1584
|
+
padding: 0.25rem;
|
|
1585
|
+
gap: 0.5rem;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
.gallery.slider.modal.controls-overlay {
|
|
1589
|
+
.controls {
|
|
1590
|
+
z-index: 3;
|
|
1591
|
+
bottom: 0.5rem;
|
|
1592
|
+
gap: 0.5rem;
|
|
1593
|
+
nav.pages {
|
|
1594
|
+
left: 50%;
|
|
1595
|
+
transform: translate3d(-50%, 0, 0);
|
|
1596
|
+
bottom: 3.5rem;
|
|
1597
|
+
z-index: 2;
|
|
1598
|
+
}
|
|
1599
|
+
/* The lightbox backdrop is always dark regardless of light/dark mode,
|
|
1600
|
+
so don't let the transparent Button variant's light-dark() tokens
|
|
1601
|
+
leak in (its light-mode --color-text-active is near-black). Pin a
|
|
1602
|
+
fixed dark-surface palette: white icons on a translucent white pill
|
|
1603
|
+
that gets *brighter* on hover, never darker. */
|
|
1604
|
+
:global(> .button),
|
|
1605
|
+
.actions :global(> .button) {
|
|
1606
|
+
--color-text: rgb(255 255 255 / 0.92);
|
|
1607
|
+
--color-text-active: #ffffff;
|
|
1608
|
+
--color-text-disabled: rgb(255 255 255 / 0.4);
|
|
1609
|
+
--color-bg: rgb(255 255 255 / 0.12);
|
|
1610
|
+
--color-bg-active: rgb(255 255 255 / 0.28);
|
|
1611
|
+
}
|
|
1612
|
+
:global(> .button button),
|
|
1613
|
+
.actions :global(> .button button) {
|
|
1614
|
+
backdrop-filter: blur(8px);
|
|
1615
|
+
}
|
|
1616
|
+
.pagination {
|
|
1617
|
+
margin: 0 0.5rem;
|
|
1618
|
+
}
|
|
1619
|
+
.actions {
|
|
1620
|
+
position: absolute;
|
|
1621
|
+
bottom: 0;
|
|
1622
|
+
left: 5rem;
|
|
1623
|
+
z-index: 2;
|
|
1624
|
+
display: flex;
|
|
1625
|
+
:global(> .button button svg) {
|
|
1626
|
+
filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.95))
|
|
1627
|
+
drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.25))
|
|
1628
|
+
drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.5));
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
@media (min-width: 768px) {
|
|
1633
|
+
nav.pages {
|
|
1634
|
+
bottom: 1rem;
|
|
1635
|
+
}
|
|
1636
|
+
display: block;
|
|
1637
|
+
position: static;
|
|
1638
|
+
height: unset;
|
|
1639
|
+
width: unset;
|
|
1640
|
+
bottom: unset;
|
|
1641
|
+
left: unset;
|
|
1642
|
+
right: unset;
|
|
1643
|
+
:global(> .button) {
|
|
1644
|
+
bottom: 1rem;
|
|
1645
|
+
position: absolute;
|
|
1646
|
+
}
|
|
1647
|
+
:global(> .button button svg) {
|
|
1648
|
+
filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.95))
|
|
1649
|
+
drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.25))
|
|
1650
|
+
drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.5));
|
|
1651
|
+
}
|
|
1652
|
+
.actions {
|
|
1653
|
+
position: absolute;
|
|
1654
|
+
top: 4.75rem;
|
|
1655
|
+
right: 0.875rem;
|
|
1656
|
+
left: unset;
|
|
1657
|
+
bottom: unset;
|
|
1658
|
+
display: flex;
|
|
1659
|
+
flex-direction: column;
|
|
1660
|
+
justify-content: center;
|
|
1661
|
+
align-items: center;
|
|
1662
|
+
}
|
|
1663
|
+
.pagination {
|
|
1664
|
+
position: absolute;
|
|
1665
|
+
font-size: 1.5rem;
|
|
1666
|
+
top: 0.875rem;
|
|
1667
|
+
right: 4.25rem;
|
|
1668
|
+
height: 3rem;
|
|
1669
|
+
margin: 0;
|
|
1670
|
+
display: flex;
|
|
1671
|
+
align-items: center;
|
|
1672
|
+
text-align: right;
|
|
1673
|
+
z-index: 2;
|
|
1674
|
+
backdrop-filter: blur(5px);
|
|
1675
|
+
padding: 0 1rem;
|
|
1676
|
+
border-radius: 9999px;
|
|
1677
|
+
}
|
|
1678
|
+
:global(.play) {
|
|
1679
|
+
z-index: 2;
|
|
1680
|
+
}
|
|
1681
|
+
:global(.close) {
|
|
1682
|
+
right: 0.875rem;
|
|
1683
|
+
top: 0.875rem;
|
|
1684
|
+
z-index: 2;
|
|
1685
|
+
/* Nudge the dismiss control up a touch. The icon button sizes
|
|
1686
|
+
off its own font-size (× --control-height-ratio), so bumping
|
|
1687
|
+
the font scales the pill AND the icon together, keeping the
|
|
1688
|
+
translucent-white-pill language intact. */
|
|
1689
|
+
font-size: 1.15rem;
|
|
1690
|
+
}
|
|
1691
|
+
:global(.prev),
|
|
1692
|
+
:global(.next) {
|
|
1693
|
+
top: 50%;
|
|
1694
|
+
transform: translateY(-50%);
|
|
1695
|
+
bottom: unset;
|
|
1696
|
+
width: 4.5rem;
|
|
1697
|
+
height: min(20rem, 50%);
|
|
1698
|
+
aspect-ratio: auto;
|
|
1699
|
+
box-shadow: none;
|
|
1700
|
+
cursor: pointer;
|
|
1701
|
+
z-index: 2;
|
|
1702
|
+
/* These stretch into full-height edge strips on desktop — hit
|
|
1703
|
+
areas, not pills. Keep them invisible at rest (no white slab,
|
|
1704
|
+
no blur) and brighten with only a soft white wash on hover. */
|
|
1705
|
+
--color-bg: transparent;
|
|
1706
|
+
--color-bg-active: rgb(255 255 255 / 0.08);
|
|
1707
|
+
}
|
|
1708
|
+
:global(.prev button),
|
|
1709
|
+
:global(.next button) {
|
|
1710
|
+
backdrop-filter: none;
|
|
1711
|
+
}
|
|
1712
|
+
:global(.prev button svg),
|
|
1713
|
+
:global(.next button svg) {
|
|
1714
|
+
height: 80%;
|
|
1715
|
+
width: 80%;
|
|
1716
|
+
}
|
|
1717
|
+
:global(.prev) {
|
|
1718
|
+
left: 0;
|
|
1719
|
+
padding-left: 0.5rem;
|
|
1720
|
+
}
|
|
1721
|
+
:global(.next) {
|
|
1722
|
+
right: 0;
|
|
1723
|
+
padding-right: 0.5rem;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
.gallery.slider.controls-inline {
|
|
1729
|
+
nav.pages {
|
|
1730
|
+
top: -4.5rem;
|
|
1731
|
+
bottom: unset;
|
|
1732
|
+
}
|
|
1733
|
+
.controls {
|
|
1734
|
+
justify-content: center;
|
|
1735
|
+
gap: 0;
|
|
1736
|
+
top: 100%;
|
|
1737
|
+
bottom: unset;
|
|
1738
|
+
@container (max-width: 500px) {
|
|
1739
|
+
gap: 0.5rem;
|
|
1740
|
+
padding: 0;
|
|
1741
|
+
.pagination {
|
|
1742
|
+
padding: 0 0.5rem;
|
|
1743
|
+
font-size: 1rem;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
.spacer {
|
|
1747
|
+
display: none;
|
|
1748
|
+
flex: 0;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
.controls > .pagination {
|
|
1752
|
+
color: var(--color-text, var(--text));
|
|
1753
|
+
margin: 0 1rem;
|
|
1754
|
+
font-weight: normal;
|
|
1755
|
+
font-size: 1.5rem;
|
|
1756
|
+
}
|
|
1757
|
+
.controls > :global(.play) {
|
|
1758
|
+
svg.progress {
|
|
1759
|
+
stroke: var(--color-text-muted);
|
|
1760
|
+
opacity: 1;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
.gallery.slider.modal {
|
|
1766
|
+
position: fixed;
|
|
1767
|
+
z-index: var(--layer-modal, 1000);
|
|
1768
|
+
top: 0;
|
|
1769
|
+
left: 0;
|
|
1770
|
+
bottom: 0;
|
|
1771
|
+
width: 100%;
|
|
1772
|
+
height: 100%;
|
|
1773
|
+
.bg {
|
|
1774
|
+
background-color: rgba(0, 0, 0, 0.85);
|
|
1775
|
+
@supports (backdrop-filter: blur(25px)) {
|
|
1776
|
+
filter: blur(0px);
|
|
1777
|
+
backdrop-filter: blur(25px);
|
|
1778
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
&.fullscreen {
|
|
1782
|
+
.bg {
|
|
1783
|
+
background-color: black !important;
|
|
1784
|
+
opacity: 1 !important;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
:global(.carousel) {
|
|
1788
|
+
aspect-ratio: var(--aspect-ratio);
|
|
1789
|
+
height: calc(100% - 4.5rem);
|
|
1790
|
+
}
|
|
1791
|
+
@media (min-width: 768px) {
|
|
1792
|
+
:global(.carousel) {
|
|
1793
|
+
height: 100%;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
.fullscreen-name {
|
|
1798
|
+
position: absolute;
|
|
1799
|
+
bottom: 0;
|
|
1800
|
+
left: 0;
|
|
1801
|
+
right: 0;
|
|
1802
|
+
z-index: 2;
|
|
1803
|
+
pointer-events: none;
|
|
1804
|
+
&::before {
|
|
1805
|
+
content: '';
|
|
1806
|
+
position: absolute;
|
|
1807
|
+
inset: 0;
|
|
1808
|
+
background-image: linear-gradient(to top, rgba(0, 0, 0, 0.95), rgba(0, 0, 0, 0));
|
|
1809
|
+
z-index: -1;
|
|
1810
|
+
}
|
|
1811
|
+
text-align: center;
|
|
1812
|
+
color: white;
|
|
1813
|
+
font-size: var(--text-base, 1rem);
|
|
1814
|
+
padding: 6rem 1rem 5rem;
|
|
1815
|
+
text-shadow:
|
|
1816
|
+
0 1px 2px rgba(0, 0, 0, 0.5),
|
|
1817
|
+
0 0 10px rgba(0, 0, 0, 0.3);
|
|
1818
|
+
white-space: nowrap;
|
|
1819
|
+
overflow: hidden;
|
|
1820
|
+
text-overflow: ellipsis;
|
|
1821
|
+
transition: opacity 1000ms ease;
|
|
1822
|
+
@starting-style {
|
|
1823
|
+
opacity: 0;
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
@media (min-width: 768px) {
|
|
1827
|
+
.fullscreen-name {
|
|
1828
|
+
padding: 3rem 5rem 1rem;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
.pagination {
|
|
1833
|
+
font-size: 1.3rem;
|
|
1834
|
+
color: white;
|
|
1835
|
+
text-shadow:
|
|
1836
|
+
1px 1px 0 rgba(0, 0, 0, 0.5),
|
|
1837
|
+
1px 1px 10px rgba(0, 0, 0, 0.5),
|
|
1838
|
+
0 0 40px black;
|
|
1839
|
+
font-weight: bold;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
.gallery.display-masonry {
|
|
1844
|
+
width: 100%;
|
|
1845
|
+
margin-inline: auto;
|
|
1846
|
+
display: grid;
|
|
1847
|
+
grid-auto-flow: dense;
|
|
1848
|
+
gap: var(--gallery-gap, 12px);
|
|
1849
|
+
padding: 0 var(--gallery-gap, 12px) var(--gallery-gap, 12px);
|
|
1850
|
+
max-width: 2160px;
|
|
1851
|
+
grid-auto-rows: 1fr;
|
|
1852
|
+
--cols: 4;
|
|
1853
|
+
--cols-per-image: 8;
|
|
1854
|
+
--cols-desktop: calc(var(--cols) * var(--cols-per-image));
|
|
1855
|
+
--cols-tablet: max(
|
|
1856
|
+
var(--cols-per-image),
|
|
1857
|
+
calc(
|
|
1858
|
+
round((var(--cols-desktop) * 0.75) / var(--cols-per-image), 1) *
|
|
1859
|
+
var(--cols-per-image)
|
|
1860
|
+
)
|
|
1861
|
+
);
|
|
1862
|
+
--cols-phone: max(
|
|
1863
|
+
var(--cols-per-image),
|
|
1864
|
+
calc(
|
|
1865
|
+
round((var(--cols-desktop) * 0.45) / var(--cols-per-image), 1) *
|
|
1866
|
+
var(--cols-per-image)
|
|
1867
|
+
)
|
|
1868
|
+
);
|
|
1869
|
+
grid-template-columns: repeat(var(--cols-phone), minmax(0, 1fr));
|
|
1870
|
+
@container (min-width: 768px) {
|
|
1871
|
+
grid-template-columns: repeat(var(--cols-tablet), minmax(0, 1fr));
|
|
1872
|
+
}
|
|
1873
|
+
@container (min-width: 1024px) {
|
|
1874
|
+
grid-template-columns: repeat(var(--cols-desktop), minmax(0, 1fr));
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
&.radius-0 {
|
|
1878
|
+
--radius-lg: 0px;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
&.size-00 {
|
|
1882
|
+
--cols: 8;
|
|
1883
|
+
.name {
|
|
1884
|
+
font-size: 0.8rem;
|
|
1885
|
+
}
|
|
1886
|
+
&.radius-1 {
|
|
1887
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.4);
|
|
1888
|
+
}
|
|
1889
|
+
&.radius-2 {
|
|
1890
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.6);
|
|
1891
|
+
}
|
|
1892
|
+
&.radius-3 {
|
|
1893
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.8);
|
|
1894
|
+
}
|
|
1895
|
+
&.spacing-0 {
|
|
1896
|
+
--gallery-gap: 0px;
|
|
1897
|
+
}
|
|
1898
|
+
&.spacing-1 {
|
|
1899
|
+
--gallery-gap: min(6px, 1.5cqw);
|
|
1900
|
+
}
|
|
1901
|
+
&.spacing-2 {
|
|
1902
|
+
--gallery-gap: min(10px, 1.5cqw);
|
|
1903
|
+
}
|
|
1904
|
+
&.spacing-3 {
|
|
1905
|
+
--gallery-gap: min(16px, 1.5cqw);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
&.size-0 {
|
|
1909
|
+
--cols: 6;
|
|
1910
|
+
.name {
|
|
1911
|
+
font-size: 0.9rem;
|
|
1912
|
+
}
|
|
1913
|
+
&.radius-1 {
|
|
1914
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.45);
|
|
1915
|
+
}
|
|
1916
|
+
&.radius-2 {
|
|
1917
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.65);
|
|
1918
|
+
}
|
|
1919
|
+
&.radius-3 {
|
|
1920
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.85);
|
|
1921
|
+
}
|
|
1922
|
+
&.spacing-0 {
|
|
1923
|
+
--gallery-gap: 0px;
|
|
1924
|
+
}
|
|
1925
|
+
&.spacing-1 {
|
|
1926
|
+
--gallery-gap: min(8px, 1.5cqw);
|
|
1927
|
+
}
|
|
1928
|
+
&.spacing-2 {
|
|
1929
|
+
--gallery-gap: min(14px, 1.5cqw);
|
|
1930
|
+
}
|
|
1931
|
+
&.spacing-3 {
|
|
1932
|
+
--gallery-gap: min(20px, 1.5cqw);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
&.size-1 {
|
|
1936
|
+
--cols: 4;
|
|
1937
|
+
&.radius-1 {
|
|
1938
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.45);
|
|
1939
|
+
}
|
|
1940
|
+
&.radius-2 {
|
|
1941
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.8);
|
|
1942
|
+
}
|
|
1943
|
+
&.radius-3 {
|
|
1944
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.1);
|
|
1945
|
+
}
|
|
1946
|
+
&.spacing-0 {
|
|
1947
|
+
--gallery-gap: 0px;
|
|
1948
|
+
}
|
|
1949
|
+
&.spacing-1 {
|
|
1950
|
+
--gallery-gap: min(10px, 3cqw);
|
|
1951
|
+
}
|
|
1952
|
+
&.spacing-2 {
|
|
1953
|
+
--gallery-gap: min(16px, 3cqw);
|
|
1954
|
+
}
|
|
1955
|
+
&.spacing-3 {
|
|
1956
|
+
--gallery-gap: min(24px, 3cqw);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
&.size-2 {
|
|
1960
|
+
--cols: 3;
|
|
1961
|
+
&.radius-1 {
|
|
1962
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.5);
|
|
1963
|
+
}
|
|
1964
|
+
&.radius-2 {
|
|
1965
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1);
|
|
1966
|
+
}
|
|
1967
|
+
&.radius-3 {
|
|
1968
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.4);
|
|
1969
|
+
}
|
|
1970
|
+
&.spacing-0 {
|
|
1971
|
+
--gallery-gap: 0px;
|
|
1972
|
+
}
|
|
1973
|
+
&.spacing-1 {
|
|
1974
|
+
--gallery-gap: min(12px, 5cqw);
|
|
1975
|
+
}
|
|
1976
|
+
&.spacing-2 {
|
|
1977
|
+
--gallery-gap: min(20px, 5cqw);
|
|
1978
|
+
}
|
|
1979
|
+
&.spacing-3 {
|
|
1980
|
+
--gallery-gap: min(28px, 5cqw);
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
&.size-3 {
|
|
1984
|
+
--cols: 2;
|
|
1985
|
+
&.radius-1 {
|
|
1986
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.5);
|
|
1987
|
+
}
|
|
1988
|
+
&.radius-2 {
|
|
1989
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.1);
|
|
1990
|
+
}
|
|
1991
|
+
&.radius-3 {
|
|
1992
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.5);
|
|
1993
|
+
}
|
|
1994
|
+
&.spacing-0 {
|
|
1995
|
+
--gallery-gap: 0px;
|
|
1996
|
+
}
|
|
1997
|
+
&.spacing-1 {
|
|
1998
|
+
--gallery-gap: min(14px, 5cqw);
|
|
1999
|
+
}
|
|
2000
|
+
&.spacing-2 {
|
|
2001
|
+
--gallery-gap: min(24px, 5cqw);
|
|
2002
|
+
}
|
|
2003
|
+
&.spacing-3 {
|
|
2004
|
+
--gallery-gap: min(34px, 5cqw);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
&::before {
|
|
2009
|
+
content: '';
|
|
2010
|
+
width: 0;
|
|
2011
|
+
padding-bottom: 100%;
|
|
2012
|
+
grid-row: 1 / 1;
|
|
2013
|
+
grid-column: 1 / 1;
|
|
2014
|
+
aspect-ratio: 1;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
> .gallery-item {
|
|
2018
|
+
grid-column-end: span var(--cols-per-image);
|
|
2019
|
+
grid-row-end: span max(1, calc(var(--cols-per-image) * 1 / var(--ratio, 1)));
|
|
2020
|
+
&.favorite {
|
|
2021
|
+
--zero-if-one-column: min(
|
|
2022
|
+
round(down, calc((var(--cols-phone) / var(--cols-per-image)) - 1), 1),
|
|
2023
|
+
1
|
|
2024
|
+
);
|
|
2025
|
+
--favorite-cols: calc(
|
|
2026
|
+
var(--cols-per-image) + var(--cols-per-image) * var(--zero-if-one-column)
|
|
2027
|
+
);
|
|
2028
|
+
grid-column-end: span calc(var(--cols-per-image) * 2);
|
|
2029
|
+
grid-row-end: span
|
|
2030
|
+
max(1, round(down, calc(var(--cols-per-image) * 2 / var(--ratio, 1)), 1));
|
|
2031
|
+
@container (max-width: 767px) {
|
|
2032
|
+
grid-column-end: span var(--favorite-cols);
|
|
2033
|
+
grid-row-end: span
|
|
2034
|
+
max(1, round(down, calc(var(--favorite-cols) * 1 / var(--ratio, 1)), 1));
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
&:first-child {
|
|
2038
|
+
grid-column-start: 1;
|
|
2039
|
+
grid-row-start: 1;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
.gallery.display-grid {
|
|
2045
|
+
width: 100%;
|
|
2046
|
+
margin-inline: auto;
|
|
2047
|
+
display: grid;
|
|
2048
|
+
grid-auto-flow: dense;
|
|
2049
|
+
gap: var(--gallery-gap, 12px);
|
|
2050
|
+
padding: 0 var(--gallery-gap, 12px) var(--gallery-gap, 12px);
|
|
2051
|
+
max-width: 2160px;
|
|
2052
|
+
grid-auto-rows: 1fr;
|
|
2053
|
+
container: gallery-grid / inline-size;
|
|
2054
|
+
|
|
2055
|
+
&.radius-0 {
|
|
2056
|
+
--radius-lg: 0px;
|
|
2057
|
+
}
|
|
2058
|
+
&.size-00 {
|
|
2059
|
+
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
|
2060
|
+
.name {
|
|
2061
|
+
font-size: 0.7rem;
|
|
2062
|
+
}
|
|
2063
|
+
&.radius-1 {
|
|
2064
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.3);
|
|
2065
|
+
}
|
|
2066
|
+
&.radius-2 {
|
|
2067
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.45);
|
|
2068
|
+
}
|
|
2069
|
+
&.radius-3 {
|
|
2070
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.6);
|
|
2071
|
+
}
|
|
2072
|
+
&.spacing-0 {
|
|
2073
|
+
--gallery-gap: 0px;
|
|
2074
|
+
}
|
|
2075
|
+
&.spacing-1 {
|
|
2076
|
+
--gallery-gap: min(6px, 1.5cqw);
|
|
2077
|
+
}
|
|
2078
|
+
&.spacing-2 {
|
|
2079
|
+
--gallery-gap: min(10px, 1.5cqw);
|
|
2080
|
+
}
|
|
2081
|
+
&.spacing-3 {
|
|
2082
|
+
--gallery-gap: min(16px, 1.5cqw);
|
|
2083
|
+
}
|
|
2084
|
+
@container (min-width: 768px) {
|
|
2085
|
+
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
&.size-0 {
|
|
2089
|
+
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
|
2090
|
+
.name {
|
|
2091
|
+
font-size: 0.8rem;
|
|
2092
|
+
}
|
|
2093
|
+
&.radius-1 {
|
|
2094
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.35);
|
|
2095
|
+
}
|
|
2096
|
+
&.radius-2 {
|
|
2097
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.55);
|
|
2098
|
+
}
|
|
2099
|
+
&.radius-3 {
|
|
2100
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.75);
|
|
2101
|
+
}
|
|
2102
|
+
&.spacing-0 {
|
|
2103
|
+
--gallery-gap: 0px;
|
|
2104
|
+
}
|
|
2105
|
+
&.spacing-1 {
|
|
2106
|
+
--gallery-gap: min(8px, 1.5cqw);
|
|
2107
|
+
}
|
|
2108
|
+
&.spacing-2 {
|
|
2109
|
+
--gallery-gap: min(14px, 1.5cqw);
|
|
2110
|
+
}
|
|
2111
|
+
&.spacing-3 {
|
|
2112
|
+
--gallery-gap: min(20px, 1.5cqw);
|
|
2113
|
+
}
|
|
2114
|
+
@container (min-width: 768px) {
|
|
2115
|
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
&.size-1 {
|
|
2119
|
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
2120
|
+
&.radius-1 {
|
|
2121
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.45);
|
|
2122
|
+
}
|
|
2123
|
+
&.radius-2 {
|
|
2124
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.8);
|
|
2125
|
+
}
|
|
2126
|
+
&.radius-3 {
|
|
2127
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.1);
|
|
2128
|
+
}
|
|
2129
|
+
&.spacing-0 {
|
|
2130
|
+
--gallery-gap: 0px;
|
|
2131
|
+
}
|
|
2132
|
+
&.spacing-1 {
|
|
2133
|
+
--gallery-gap: min(10px, 3cqw);
|
|
2134
|
+
}
|
|
2135
|
+
&.spacing-2 {
|
|
2136
|
+
--gallery-gap: min(16px, 3cqw);
|
|
2137
|
+
}
|
|
2138
|
+
&.spacing-3 {
|
|
2139
|
+
--gallery-gap: min(24px, 3cqw);
|
|
2140
|
+
}
|
|
2141
|
+
@container (min-width: 768px) {
|
|
2142
|
+
grid-template-columns: repeat(auto-fill, minmax(225px, 1fr));
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
&.size-2 {
|
|
2146
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
2147
|
+
&.radius-1 {
|
|
2148
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.5);
|
|
2149
|
+
}
|
|
2150
|
+
&.radius-2 {
|
|
2151
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1);
|
|
2152
|
+
}
|
|
2153
|
+
&.radius-3 {
|
|
2154
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.4);
|
|
2155
|
+
}
|
|
2156
|
+
&.spacing-0 {
|
|
2157
|
+
--gallery-gap: 0px;
|
|
2158
|
+
}
|
|
2159
|
+
&.spacing-1 {
|
|
2160
|
+
--gallery-gap: min(12px, 5cqw);
|
|
2161
|
+
}
|
|
2162
|
+
&.spacing-2 {
|
|
2163
|
+
--gallery-gap: min(20px, 5cqw);
|
|
2164
|
+
}
|
|
2165
|
+
&.spacing-3 {
|
|
2166
|
+
--gallery-gap: min(28px, 5cqw);
|
|
2167
|
+
}
|
|
2168
|
+
@container (min-width: 768px) {
|
|
2169
|
+
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
&.size-3 {
|
|
2173
|
+
grid-template-columns: repeat(auto-fill, minmax(440px, 1fr));
|
|
2174
|
+
&.radius-1 {
|
|
2175
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.5);
|
|
2176
|
+
}
|
|
2177
|
+
&.radius-2 {
|
|
2178
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.1);
|
|
2179
|
+
}
|
|
2180
|
+
&.radius-3 {
|
|
2181
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.5);
|
|
2182
|
+
}
|
|
2183
|
+
&.spacing-0 {
|
|
2184
|
+
--gallery-gap: 0px;
|
|
2185
|
+
}
|
|
2186
|
+
&.spacing-1 {
|
|
2187
|
+
--gallery-gap: min(14px, 5cqw);
|
|
2188
|
+
}
|
|
2189
|
+
&.spacing-2 {
|
|
2190
|
+
--gallery-gap: min(24px, 5cqw);
|
|
2191
|
+
}
|
|
2192
|
+
&.spacing-3 {
|
|
2193
|
+
--gallery-gap: min(34px, 5cqw);
|
|
2194
|
+
}
|
|
2195
|
+
@container (min-width: 768px) {
|
|
2196
|
+
grid-template-columns: repeat(auto-fill, minmax(520px, 1fr));
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
&::before {
|
|
2201
|
+
content: '';
|
|
2202
|
+
width: 0;
|
|
2203
|
+
padding-bottom: 100%;
|
|
2204
|
+
grid-row: 1 / 1;
|
|
2205
|
+
grid-column: 1 / 1;
|
|
2206
|
+
aspect-ratio: 1;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
> .gallery-item {
|
|
2210
|
+
grid-row-end: span 1;
|
|
2211
|
+
grid-column-end: span 1;
|
|
2212
|
+
&.favorite {
|
|
2213
|
+
grid-column-end: span 2;
|
|
2214
|
+
grid-row-end: span 2;
|
|
2215
|
+
}
|
|
2216
|
+
&:first-child {
|
|
2217
|
+
grid-column-start: 1;
|
|
2218
|
+
grid-row-start: 1;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
.gallery.display-masonry-row {
|
|
2224
|
+
--row-height: 250px;
|
|
2225
|
+
--max-row-height: 350px;
|
|
2226
|
+
display: flex;
|
|
2227
|
+
flex-wrap: wrap;
|
|
2228
|
+
justify-content: center;
|
|
2229
|
+
gap: var(--gallery-gap, 12px);
|
|
2230
|
+
padding: 0 var(--gallery-gap, 12px) var(--gallery-gap, 12px);
|
|
2231
|
+
max-width: 2160px;
|
|
2232
|
+
margin-inline: auto;
|
|
2233
|
+
|
|
2234
|
+
&.radius-0 {
|
|
2235
|
+
--radius-lg: 0px;
|
|
2236
|
+
}
|
|
2237
|
+
&.size-00 {
|
|
2238
|
+
--row-height: 45px;
|
|
2239
|
+
--max-row-height: 75px;
|
|
2240
|
+
&.radius-1 {
|
|
2241
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.4);
|
|
2242
|
+
}
|
|
2243
|
+
&.radius-2 {
|
|
2244
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.5);
|
|
2245
|
+
}
|
|
2246
|
+
&.radius-3 {
|
|
2247
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.65);
|
|
2248
|
+
}
|
|
2249
|
+
&.spacing-0 {
|
|
2250
|
+
--gallery-gap: 0px;
|
|
2251
|
+
}
|
|
2252
|
+
&.spacing-1 {
|
|
2253
|
+
--gallery-gap: min(6px, 1.5cqw);
|
|
2254
|
+
}
|
|
2255
|
+
&.spacing-2 {
|
|
2256
|
+
--gallery-gap: min(10px, 1.5cqw);
|
|
2257
|
+
}
|
|
2258
|
+
&.spacing-3 {
|
|
2259
|
+
--gallery-gap: min(16px, 1.5cqw);
|
|
2260
|
+
}
|
|
2261
|
+
@container (min-width: 768px) {
|
|
2262
|
+
--row-height: 100px;
|
|
2263
|
+
--max-row-height: 140px;
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
&.size-0 {
|
|
2267
|
+
--row-height: 70px;
|
|
2268
|
+
--max-row-height: 110px;
|
|
2269
|
+
&.radius-1 {
|
|
2270
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.45);
|
|
2271
|
+
}
|
|
2272
|
+
&.radius-2 {
|
|
2273
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.55);
|
|
2274
|
+
}
|
|
2275
|
+
&.radius-3 {
|
|
2276
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.75);
|
|
2277
|
+
}
|
|
2278
|
+
&.spacing-0 {
|
|
2279
|
+
--gallery-gap: 0px;
|
|
2280
|
+
}
|
|
2281
|
+
&.spacing-1 {
|
|
2282
|
+
--gallery-gap: min(8px, 1.5cqw);
|
|
2283
|
+
}
|
|
2284
|
+
&.spacing-2 {
|
|
2285
|
+
--gallery-gap: min(14px, 1.5cqw);
|
|
2286
|
+
}
|
|
2287
|
+
&.spacing-3 {
|
|
2288
|
+
--gallery-gap: min(20px, 1.5cqw);
|
|
2289
|
+
}
|
|
2290
|
+
@container (min-width: 768px) {
|
|
2291
|
+
--row-height: 150px;
|
|
2292
|
+
--max-row-height: 200px;
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
&.size-1 {
|
|
2296
|
+
--row-height: 100px;
|
|
2297
|
+
--max-row-height: 150px;
|
|
2298
|
+
&.radius-1 {
|
|
2299
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.35);
|
|
2300
|
+
}
|
|
2301
|
+
&.radius-2 {
|
|
2302
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.5);
|
|
2303
|
+
}
|
|
2304
|
+
&.radius-3 {
|
|
2305
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.7);
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
&.spacing-0 {
|
|
2309
|
+
--gallery-gap: 0px;
|
|
2310
|
+
}
|
|
2311
|
+
&.spacing-1 {
|
|
2312
|
+
--gallery-gap: min(10px, 3cqw);
|
|
2313
|
+
}
|
|
2314
|
+
&.spacing-2 {
|
|
2315
|
+
--gallery-gap: min(16px, 3cqw);
|
|
2316
|
+
}
|
|
2317
|
+
&.spacing-3 {
|
|
2318
|
+
--gallery-gap: min(24px, 3cqw);
|
|
2319
|
+
}
|
|
2320
|
+
@container (min-width: 768px) {
|
|
2321
|
+
--row-height: 200px;
|
|
2322
|
+
--max-row-height: 300px;
|
|
2323
|
+
&.radius-1 {
|
|
2324
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.45);
|
|
2325
|
+
}
|
|
2326
|
+
&.radius-2 {
|
|
2327
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.8);
|
|
2328
|
+
}
|
|
2329
|
+
&.radius-3 {
|
|
2330
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.1);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
&.size-2 {
|
|
2335
|
+
--row-height: 250px;
|
|
2336
|
+
--max-row-height: 350px;
|
|
2337
|
+
&.radius-1 {
|
|
2338
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.5);
|
|
2339
|
+
}
|
|
2340
|
+
&.radius-2 {
|
|
2341
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1);
|
|
2342
|
+
}
|
|
2343
|
+
&.radius-3 {
|
|
2344
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.4);
|
|
2345
|
+
}
|
|
2346
|
+
&.spacing-0 {
|
|
2347
|
+
--gallery-gap: 0px;
|
|
2348
|
+
}
|
|
2349
|
+
&.spacing-1 {
|
|
2350
|
+
--gallery-gap: min(12px, 5cqw);
|
|
2351
|
+
}
|
|
2352
|
+
&.spacing-2 {
|
|
2353
|
+
--gallery-gap: min(20px, 5cqw);
|
|
2354
|
+
}
|
|
2355
|
+
&.spacing-3 {
|
|
2356
|
+
--gallery-gap: min(28px, 5cqw);
|
|
2357
|
+
}
|
|
2358
|
+
@container (max-width: 767px) {
|
|
2359
|
+
--max-row-height: 700px;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
&.size-3 {
|
|
2363
|
+
--row-height: 350px;
|
|
2364
|
+
--max-row-height: 500px;
|
|
2365
|
+
&.radius-1 {
|
|
2366
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 0.5);
|
|
2367
|
+
}
|
|
2368
|
+
&.radius-2 {
|
|
2369
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.1);
|
|
2370
|
+
}
|
|
2371
|
+
&.radius-3 {
|
|
2372
|
+
--radius-lg: calc(var(--gallery-gap, 12px) * 1.5);
|
|
2373
|
+
}
|
|
2374
|
+
&.spacing-0 {
|
|
2375
|
+
--gallery-gap: 0px;
|
|
2376
|
+
}
|
|
2377
|
+
&.spacing-1 {
|
|
2378
|
+
--gallery-gap: min(14px, 5cqw);
|
|
2379
|
+
}
|
|
2380
|
+
&.spacing-2 {
|
|
2381
|
+
--gallery-gap: min(24px, 5cqw);
|
|
2382
|
+
}
|
|
2383
|
+
&.spacing-3 {
|
|
2384
|
+
--gallery-gap: min(34px, 5cqw);
|
|
2385
|
+
}
|
|
2386
|
+
@container (max-width: 767px) {
|
|
2387
|
+
--max-row-height: 850px;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
> .gallery-item {
|
|
2392
|
+
flex-basis: calc(var(--ratio, 1) * var(--row-height));
|
|
2393
|
+
flex-grow: calc(var(--ratio, 1) * 100);
|
|
2394
|
+
aspect-ratio: var(--ratio, 1);
|
|
2395
|
+
max-height: var(--max-row-height);
|
|
2396
|
+
max-width: calc(var(--ratio, 1) * var(--max-row-height) * 1.1);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
.gallery.display-list {
|
|
2401
|
+
display: flex;
|
|
2402
|
+
flex-direction: column;
|
|
2403
|
+
max-width: 600px;
|
|
2404
|
+
margin-inline: auto;
|
|
2405
|
+
/* Size-driven scale: row height, horizontal padding, body text and the
|
|
2406
|
+
square thumbnail all key off these so smaller sizes feel uniformly
|
|
2407
|
+
tighter and larger sizes uniformly roomier. */
|
|
2408
|
+
--line-height: 3.5rem;
|
|
2409
|
+
--list-pad: 9px;
|
|
2410
|
+
--list-text-size: 1rem;
|
|
2411
|
+
--thumb-size: calc(var(--line-height) * 0.72);
|
|
2412
|
+
|
|
2413
|
+
&.radius-0 {
|
|
2414
|
+
--radius-lg: 0px;
|
|
2415
|
+
.info {
|
|
2416
|
+
border-radius: 0px !important;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
&.radius-1 {
|
|
2420
|
+
--radius-lg: calc(var(--line-height) * 0.1);
|
|
2421
|
+
}
|
|
2422
|
+
&.radius-2 {
|
|
2423
|
+
--radius-lg: calc(var(--line-height) * 0.15);
|
|
2424
|
+
}
|
|
2425
|
+
&.radius-3 {
|
|
2426
|
+
--radius-lg: calc(var(--line-height) * 0.2);
|
|
2427
|
+
}
|
|
2428
|
+
&.size-00 {
|
|
2429
|
+
--line-height: 2.25rem;
|
|
2430
|
+
--list-pad: 3px;
|
|
2431
|
+
--list-text-size: 0.78rem;
|
|
2432
|
+
}
|
|
2433
|
+
&.size-0 {
|
|
2434
|
+
--line-height: 2.75rem;
|
|
2435
|
+
--list-pad: 6px;
|
|
2436
|
+
--list-text-size: 0.88rem;
|
|
2437
|
+
}
|
|
2438
|
+
&.size-1 {
|
|
2439
|
+
--line-height: 3.5rem;
|
|
2440
|
+
--list-pad: 9px;
|
|
2441
|
+
--list-text-size: 1rem;
|
|
2442
|
+
}
|
|
2443
|
+
&.size-2 {
|
|
2444
|
+
--line-height: 4.5rem;
|
|
2445
|
+
--list-pad: 13px;
|
|
2446
|
+
--list-text-size: 1.15rem;
|
|
2447
|
+
}
|
|
2448
|
+
&.size-3 {
|
|
2449
|
+
--line-height: 5.5rem;
|
|
2450
|
+
--list-pad: 17px;
|
|
2451
|
+
--list-text-size: 1.3rem;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
> .list-item {
|
|
2455
|
+
display: flex;
|
|
2456
|
+
height: var(--line-height);
|
|
2457
|
+
align-items: center;
|
|
2458
|
+
/* No padding here: the clickable .info fills the row edge-to-edge so
|
|
2459
|
+
hover/press feedback can never appear on a non-clickable sliver.
|
|
2460
|
+
The content inset lives on .info instead. */
|
|
2461
|
+
padding: 0;
|
|
2462
|
+
position: relative;
|
|
2463
|
+
z-index: 1;
|
|
2464
|
+
overflow: hidden;
|
|
2465
|
+
/* Drives the subtle 3D push of the inner row on press, matching ListItem. */
|
|
2466
|
+
perspective: 100px;
|
|
2467
|
+
|
|
2468
|
+
/* Subtle text-tinted divider between rows, matching ListItem. */
|
|
2469
|
+
&::after {
|
|
2470
|
+
content: '';
|
|
2471
|
+
position: absolute;
|
|
2472
|
+
top: 0;
|
|
2473
|
+
left: var(--list-pad);
|
|
2474
|
+
right: var(--list-pad);
|
|
2475
|
+
border-top: solid 1px color-mix(in oklch, transparent, var(--color-text) 6%);
|
|
2476
|
+
pointer-events: none;
|
|
2477
|
+
z-index: 1;
|
|
2478
|
+
}
|
|
2479
|
+
&:first-child::after {
|
|
2480
|
+
content: none;
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
.info {
|
|
2484
|
+
display: flex;
|
|
2485
|
+
flex: 1;
|
|
2486
|
+
min-width: 0;
|
|
2487
|
+
cursor: pointer;
|
|
2488
|
+
align-items: center;
|
|
2489
|
+
position: relative;
|
|
2490
|
+
overflow: hidden;
|
|
2491
|
+
/* Fill the full row height + width so the whole visible area of the
|
|
2492
|
+
row is the click target — feedback and clickability stay in sync. */
|
|
2493
|
+
align-self: stretch;
|
|
2494
|
+
padding: 0 calc(var(--list-pad) - 2px);
|
|
2495
|
+
border-radius: calc(var(--radius-lg) + var(--list-pad));
|
|
2496
|
+
@supports (corner-shape: squircle) {
|
|
2497
|
+
corner-shape: squircle;
|
|
2498
|
+
border-radius: calc(
|
|
2499
|
+
(var(--radius-lg) + var(--list-pad)) * var(--squircle-ratio, 2)
|
|
2500
|
+
);
|
|
2501
|
+
}
|
|
2502
|
+
/* Press effect, matching ListItem's translate-on-active. */
|
|
2503
|
+
transition: translate 200ms ease;
|
|
2504
|
+
|
|
2505
|
+
/* Hover/active background overlay (text @ 6%), matching ListItem.
|
|
2506
|
+
It lives on .info (the click target), not the row, so it only
|
|
2507
|
+
ever shows where the user can actually click. */
|
|
2508
|
+
&::before {
|
|
2509
|
+
content: '';
|
|
2510
|
+
position: absolute;
|
|
2511
|
+
top: 2px;
|
|
2512
|
+
left: 0;
|
|
2513
|
+
right: 0;
|
|
2514
|
+
bottom: 2px;
|
|
2515
|
+
background-color: var(--color-text);
|
|
2516
|
+
opacity: 0;
|
|
2517
|
+
border-radius: var(--radius-lg);
|
|
2518
|
+
@supports (corner-shape: squircle) {
|
|
2519
|
+
corner-shape: squircle;
|
|
2520
|
+
border-radius: calc(var(--radius-lg) * var(--squircle-ratio, 2));
|
|
2521
|
+
}
|
|
2522
|
+
z-index: -1;
|
|
2523
|
+
transition: opacity 300ms ease;
|
|
2524
|
+
}
|
|
2525
|
+
@media (hover: hover) and (pointer: fine) {
|
|
2526
|
+
&:hover {
|
|
2527
|
+
&::before {
|
|
2528
|
+
opacity: 0.06;
|
|
2529
|
+
transition: opacity 0ms ease;
|
|
2530
|
+
}
|
|
2531
|
+
/* Image gently zooms inside its (overflow-hidden) square. */
|
|
2532
|
+
.thumbnail-img {
|
|
2533
|
+
transform: scale(1.08);
|
|
2534
|
+
}
|
|
2535
|
+
.thumbnail-blur {
|
|
2536
|
+
transform: scale(1.32);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
&:active {
|
|
2541
|
+
translate: 0px 2px clamp(-4px, calc(0.2em - 12px), -2px);
|
|
2542
|
+
}
|
|
2543
|
+
&:focus-visible {
|
|
2544
|
+
outline: none;
|
|
2545
|
+
&::after {
|
|
2546
|
+
content: '';
|
|
2547
|
+
position: absolute;
|
|
2548
|
+
inset: 2px 0;
|
|
2549
|
+
border: solid 1px var(--color-border-active);
|
|
2550
|
+
border-radius: var(--radius-lg);
|
|
2551
|
+
@supports (corner-shape: squircle) {
|
|
2552
|
+
corner-shape: squircle;
|
|
2553
|
+
border-radius: calc(var(--radius-lg) * var(--squircle-ratio, 2));
|
|
2554
|
+
}
|
|
2555
|
+
pointer-events: none;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
.thumbnail {
|
|
2559
|
+
flex-shrink: 0;
|
|
2560
|
+
width: var(--thumb-size);
|
|
2561
|
+
height: var(--thumb-size);
|
|
2562
|
+
position: relative;
|
|
2563
|
+
color: white;
|
|
2564
|
+
display: flex;
|
|
2565
|
+
align-items: center;
|
|
2566
|
+
justify-content: center;
|
|
2567
|
+
border-radius: var(--radius-lg);
|
|
2568
|
+
@supports (corner-shape: squircle) {
|
|
2569
|
+
corner-shape: squircle;
|
|
2570
|
+
border-radius: calc(var(--radius-lg) * var(--squircle-ratio, 2));
|
|
2571
|
+
}
|
|
2572
|
+
overflow: hidden;
|
|
2573
|
+
/* The square box behind contain-fit thumbnails so images of
|
|
2574
|
+
any aspect ratio read as consistently sized tiles. Falls back
|
|
2575
|
+
to a text-tinted fill so the square stays visible even when
|
|
2576
|
+
the surface tokens aren't defined by the host theme. */
|
|
2577
|
+
background-color: var(
|
|
2578
|
+
--color-bg-muted,
|
|
2579
|
+
color-mix(in oklch, var(--color-text, gray) 20%, transparent)
|
|
2580
|
+
);
|
|
2581
|
+
.thumbnail-blur,
|
|
2582
|
+
.thumbnail-img,
|
|
2583
|
+
.thumbnail-placeholder {
|
|
2584
|
+
position: absolute;
|
|
2585
|
+
inset: 0;
|
|
2586
|
+
width: 100%;
|
|
2587
|
+
height: 100%;
|
|
2588
|
+
object-fit: contain;
|
|
2589
|
+
display: block;
|
|
2590
|
+
transition: transform 350ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
2591
|
+
}
|
|
2592
|
+
.thumbnail-blur {
|
|
2593
|
+
z-index: 0;
|
|
2594
|
+
object-fit: cover;
|
|
2595
|
+
filter: blur(8px) saturate(1.2);
|
|
2596
|
+
transform: scale(1.2);
|
|
2597
|
+
pointer-events: none;
|
|
2598
|
+
user-select: none;
|
|
2599
|
+
}
|
|
2600
|
+
.thumbnail-placeholder {
|
|
2601
|
+
z-index: 0;
|
|
2602
|
+
background: light-dark(rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
|
|
2603
|
+
}
|
|
2604
|
+
.thumbnail-img {
|
|
2605
|
+
z-index: 1;
|
|
2606
|
+
opacity: 1;
|
|
2607
|
+
transition:
|
|
2608
|
+
opacity 300ms ease,
|
|
2609
|
+
transform 350ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
2610
|
+
&.no-blur {
|
|
2611
|
+
transition:
|
|
2612
|
+
opacity 200ms ease,
|
|
2613
|
+
transform 350ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
2614
|
+
}
|
|
2615
|
+
&.fading {
|
|
2616
|
+
opacity: 0;
|
|
2617
|
+
transition: none;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
.icon {
|
|
2621
|
+
position: absolute;
|
|
2622
|
+
width: clamp(1rem, calc(var(--line-height) * 0.42), 2rem);
|
|
2623
|
+
height: clamp(1rem, calc(var(--line-height) * 0.42), 2rem);
|
|
2624
|
+
top: 50%;
|
|
2625
|
+
left: 50%;
|
|
2626
|
+
translate: -50% -50%;
|
|
2627
|
+
z-index: 2;
|
|
2628
|
+
background-color: rgba(0, 0, 0, 0.6);
|
|
2629
|
+
backdrop-filter: blur(10px);
|
|
2630
|
+
border-radius: 100%;
|
|
2631
|
+
display: flex;
|
|
2632
|
+
align-items: center;
|
|
2633
|
+
justify-content: center;
|
|
2634
|
+
:global(svg) {
|
|
2635
|
+
width: 80%;
|
|
2636
|
+
height: 80%;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
.name {
|
|
2641
|
+
flex: 1;
|
|
2642
|
+
min-width: 0;
|
|
2643
|
+
padding-left: calc(var(--list-pad) + 0.5rem);
|
|
2644
|
+
padding-right: var(--list-pad);
|
|
2645
|
+
font-size: var(--list-text-size);
|
|
2646
|
+
color: var(--color-text);
|
|
2647
|
+
text-overflow: ellipsis;
|
|
2648
|
+
white-space: nowrap;
|
|
2649
|
+
overflow: hidden;
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
/* Action buttons sit outside the clickable .info; give them the
|
|
2653
|
+
row inset that used to come from .list-item's own padding. */
|
|
2654
|
+
> .actions {
|
|
2655
|
+
flex-shrink: 0;
|
|
2656
|
+
margin-right: var(--list-pad);
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
/* Reduced layouts when only a few images */
|
|
2662
|
+
.gallery.display-grid,
|
|
2663
|
+
.gallery.display-masonry,
|
|
2664
|
+
.gallery.display-masonry-row {
|
|
2665
|
+
&:has(.gallery-item:first-child:nth-last-child(1)) {
|
|
2666
|
+
display: flex;
|
|
2667
|
+
flex-wrap: wrap;
|
|
2668
|
+
align-items: start;
|
|
2669
|
+
justify-content: center;
|
|
2670
|
+
&:before {
|
|
2671
|
+
display: none;
|
|
2672
|
+
}
|
|
2673
|
+
&.radius-1 {
|
|
2674
|
+
--radius-lg: var(--radius-md, 0.375rem);
|
|
2675
|
+
}
|
|
2676
|
+
&.radius-2 {
|
|
2677
|
+
--radius-lg: var(--radius-lg, 0.5rem);
|
|
2678
|
+
}
|
|
2679
|
+
&.radius-3 {
|
|
2680
|
+
--radius-lg: var(--_rxl);
|
|
2681
|
+
}
|
|
2682
|
+
> .gallery-item {
|
|
2683
|
+
flex-basis: 100%;
|
|
2684
|
+
flex-grow: 1;
|
|
2685
|
+
max-width: none;
|
|
2686
|
+
max-height: none;
|
|
2687
|
+
aspect-ratio: max(var(--ratio, 1), 0.85);
|
|
2688
|
+
.name {
|
|
2689
|
+
font-size: 1rem;
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
@container (min-width: 768px) {
|
|
2693
|
+
&.radius-1 {
|
|
2694
|
+
--radius-lg: var(--_rxl);
|
|
2695
|
+
}
|
|
2696
|
+
&.radius-2 {
|
|
2697
|
+
--radius-lg: var(--_r2xl);
|
|
2698
|
+
}
|
|
2699
|
+
&.radius-3 {
|
|
2700
|
+
--radius-lg: var(--_r3xl);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
&:has(.gallery-item:first-child:nth-last-child(2)) {
|
|
2706
|
+
display: flex;
|
|
2707
|
+
flex-wrap: wrap;
|
|
2708
|
+
align-items: start;
|
|
2709
|
+
justify-content: center;
|
|
2710
|
+
&:before {
|
|
2711
|
+
display: none;
|
|
2712
|
+
}
|
|
2713
|
+
&.radius-1 {
|
|
2714
|
+
--radius-lg: var(--radius-md, 0.375rem);
|
|
2715
|
+
}
|
|
2716
|
+
&.radius-2 {
|
|
2717
|
+
--radius-lg: var(--radius-lg, 0.5rem);
|
|
2718
|
+
}
|
|
2719
|
+
&.radius-3 {
|
|
2720
|
+
--radius-lg: var(--_rxl);
|
|
2721
|
+
}
|
|
2722
|
+
> .gallery-item {
|
|
2723
|
+
flex-basis: 0;
|
|
2724
|
+
flex-grow: 1;
|
|
2725
|
+
max-width: none;
|
|
2726
|
+
max-height: none;
|
|
2727
|
+
aspect-ratio: max(var(--ratio, 1), 0.75);
|
|
2728
|
+
.name {
|
|
2729
|
+
font-size: 1rem;
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
@container (min-width: 768px) {
|
|
2733
|
+
&.radius-1 {
|
|
2734
|
+
--radius-lg: var(--radius-lg, 0.5rem);
|
|
2735
|
+
}
|
|
2736
|
+
&.radius-2 {
|
|
2737
|
+
--radius-lg: var(--_rxl);
|
|
2738
|
+
}
|
|
2739
|
+
&.radius-3 {
|
|
2740
|
+
--radius-lg: var(--_r2xl);
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
&:has(.gallery-item:first-child:nth-last-child(3)) {
|
|
2746
|
+
display: flex;
|
|
2747
|
+
flex-wrap: wrap;
|
|
2748
|
+
align-items: start;
|
|
2749
|
+
justify-content: center;
|
|
2750
|
+
&:before {
|
|
2751
|
+
display: none;
|
|
2752
|
+
}
|
|
2753
|
+
&.radius-1 {
|
|
2754
|
+
--radius-lg: var(--radius-sm, 0.25rem);
|
|
2755
|
+
}
|
|
2756
|
+
&.radius-2 {
|
|
2757
|
+
--radius-lg: var(--radius-md, 0.375rem);
|
|
2758
|
+
}
|
|
2759
|
+
&.radius-3 {
|
|
2760
|
+
--radius-lg: var(--radius-lg, 0.5rem);
|
|
2761
|
+
}
|
|
2762
|
+
> .gallery-item {
|
|
2763
|
+
flex-basis: 0;
|
|
2764
|
+
flex-grow: 1;
|
|
2765
|
+
max-width: none;
|
|
2766
|
+
max-height: none;
|
|
2767
|
+
aspect-ratio: max(var(--ratio, 1), 0.75);
|
|
2768
|
+
.name {
|
|
2769
|
+
font-size: 1rem;
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
@container (min-width: 768px) {
|
|
2773
|
+
&.radius-1 {
|
|
2774
|
+
--radius-lg: var(--radius-md, 0.375rem);
|
|
2775
|
+
}
|
|
2776
|
+
&.radius-2 {
|
|
2777
|
+
--radius-lg: var(--radius-lg, 0.5rem);
|
|
2778
|
+
}
|
|
2779
|
+
&.radius-3 {
|
|
2780
|
+
--radius-lg: var(--_rxl);
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
&.size-2 {
|
|
2784
|
+
@container (max-width: 767px) {
|
|
2785
|
+
> .gallery-item {
|
|
2786
|
+
flex-basis: 100%;
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
&:has(.gallery-item:first-child:nth-last-child(4)) {
|
|
2793
|
+
&.size-0 {
|
|
2794
|
+
display: flex;
|
|
2795
|
+
flex-wrap: wrap;
|
|
2796
|
+
align-items: start;
|
|
2797
|
+
justify-content: center;
|
|
2798
|
+
&:before {
|
|
2799
|
+
display: none;
|
|
2800
|
+
}
|
|
2801
|
+
&.radius-1 {
|
|
2802
|
+
--radius-lg: var(--radius-sm, 0.25rem);
|
|
2803
|
+
}
|
|
2804
|
+
&.radius-2 {
|
|
2805
|
+
--radius-lg: var(--radius-md, 0.375rem);
|
|
2806
|
+
}
|
|
2807
|
+
&.radius-3 {
|
|
2808
|
+
--radius-lg: var(--radius-lg, 0.5rem);
|
|
2809
|
+
}
|
|
2810
|
+
> .gallery-item {
|
|
2811
|
+
flex-basis: 0;
|
|
2812
|
+
flex-grow: 1;
|
|
2813
|
+
max-width: none;
|
|
2814
|
+
max-height: none;
|
|
2815
|
+
aspect-ratio: max(var(--ratio, 1), 0.75);
|
|
2816
|
+
.name {
|
|
2817
|
+
font-size: 1rem;
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
@container (min-width: 768px) {
|
|
2821
|
+
&.radius-1 {
|
|
2822
|
+
--radius-lg: var(--radius-md, 0.375rem);
|
|
2823
|
+
}
|
|
2824
|
+
&.radius-2 {
|
|
2825
|
+
--radius-lg: var(--radius-lg, 0.5rem);
|
|
2826
|
+
}
|
|
2827
|
+
&.radius-3 {
|
|
2828
|
+
--radius-lg: var(--_rxl);
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
&.size-2 {
|
|
2833
|
+
@container (max-width: 767px) {
|
|
2834
|
+
> .gallery-item {
|
|
2835
|
+
flex-basis: 100%;
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
&:has(.gallery-item:first-child:nth-last-child(5)).size-0:not(.display-grid) {
|
|
2842
|
+
display: flex;
|
|
2843
|
+
flex-wrap: wrap;
|
|
2844
|
+
align-items: start;
|
|
2845
|
+
justify-content: center;
|
|
2846
|
+
&:before {
|
|
2847
|
+
display: none;
|
|
2848
|
+
}
|
|
2849
|
+
&.radius-1 {
|
|
2850
|
+
--radius-lg: var(--radius-sm, 0.25rem);
|
|
2851
|
+
}
|
|
2852
|
+
&.radius-2 {
|
|
2853
|
+
--radius-lg: var(--radius-md, 0.375rem);
|
|
2854
|
+
}
|
|
2855
|
+
&.radius-3 {
|
|
2856
|
+
--radius-lg: var(--radius-lg, 0.5rem);
|
|
2857
|
+
}
|
|
2858
|
+
> .gallery-item {
|
|
2859
|
+
flex-basis: 0;
|
|
2860
|
+
flex-grow: 1;
|
|
2861
|
+
max-width: none;
|
|
2862
|
+
max-height: none;
|
|
2863
|
+
aspect-ratio: max(var(--ratio, 1), 0.75);
|
|
2864
|
+
.name {
|
|
2865
|
+
font-size: 1rem;
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
@container (min-width: 768px) {
|
|
2869
|
+
&.radius-1 {
|
|
2870
|
+
--radius-lg: var(--radius-md, 0.375rem);
|
|
2871
|
+
}
|
|
2872
|
+
&.radius-2 {
|
|
2873
|
+
--radius-lg: var(--radius-lg, 0.5rem);
|
|
2874
|
+
}
|
|
2875
|
+
&.radius-3 {
|
|
2876
|
+
--radius-lg: var(--_rxl);
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
</style>
|