1use std::sync::OnceLock;
7
8use gtk::CssProvider;
9use gtk::gdk;
10use gtk::style_context_add_provider_for_display;
11
12const LIBRARY_CSS: &str = r#"
13@keyframes mimick-pulse {
14 0% { background-color: alpha(@view_fg_color, 0.06); }
15 50% { background-color: alpha(@view_fg_color, 0.14); }
16 100% { background-color: alpha(@view_fg_color, 0.06); }
17}
18
19picture.mimick-thumbnail-loading {
20 background-color: alpha(@view_fg_color, 0.08);
21 animation: mimick-pulse 1.4s ease-in-out infinite;
22}
23
24picture.mimick-thumbnail-loaded {
25}
26
27picture.mimick-thumbnail-error {
28 background-color: alpha(@error_color, 0.18);
29}
30
31overlay.mimick-cell {
32}
33
34box.mimick-empty {
35 padding: 32px;
36}
37
38label.mimick-empty-title {
39 font-size: 1.2em;
40 font-weight: bold;
41}
42
43label.mimick-empty-subtitle {
44 opacity: 0.65;
45}
46
47label.mimick-timeline-banner {
48 font-size: 1.05em;
49 font-weight: 600;
50 padding: 4px 8px;
51 background-color: alpha(@accent_bg_color, 0.10);
52 border-bottom: 1px solid alpha(@view_fg_color, 0.10);
53}
54
55image.mimick-status-badge {
56 opacity: 0.90;
57 color: white;
58 -gtk-icon-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
59}
60
61image.mimick-video-badge {
62 opacity: 0.92;
63 color: white;
64 -gtk-icon-shadow: 0 2px 4px rgba(0, 0, 0, 0.8);
65}
66
67picture.mimick-person-avatar {
68 border-radius: 9999px;
69 background-color: alpha(@view_fg_color, 0.08);
70}
71
72picture.mimick-explore-tile,
73overlay.mimick-explore-tile {
74 border-radius: 6px;
75 background-color: alpha(@view_fg_color, 0.08);
76}
77
78box.mimick-explore-spacer {
79 min-width: 130px;
80 min-height: 100px;
81}
82
83window.mimick-wide box.mimick-explore-spacer {
84 min-width: 220px;
85 min-height: 150px;
86}
87
88overlay.mimick-see-more-tile {
89 border-radius: 6px;
90 background-color: alpha(@view_fg_color, 0.06);
91 border: 1px dashed alpha(@view_fg_color, 0.2);
92}
93
94box.mimick-stat-card {
95 border-radius: 12px;
96 padding: 14px;
97 background: alpha(@accent_bg_color, 0.08);
98}
99
100box.mimick-stat-card.photo-card {
101 background: alpha(#6a9fc7, 0.14);
102}
103
104box.mimick-stat-card.video-card {
105 background: alpha(#d16c96, 0.14);
106}
107
108box.mimick-stat-card.storage-card {
109 background: alpha(#7bb876, 0.14);
110}
111
112label.mimick-stat-value {
113 font-size: 1.6em;
114 font-weight: 800;
115}
116
117label.mimick-stat-label {
118 font-size: 0.85em;
119 font-weight: 600;
120 opacity: 0.60;
121}
122
123progressbar.mimick-quota-bar trough {
124 min-height: 6px;
125 border-radius: 999px;
126 background: alpha(@window_fg_color, 0.10);
127}
128
129progressbar.mimick-quota-bar progress {
130 min-height: 6px;
131 border-radius: 999px;
132 background: mix(@accent_bg_color, #d16c96, 0.45);
133}
134
135box.mimick-version-badge {
136 border-radius: 999px;
137 padding: 4px 12px;
138 background: alpha(@accent_bg_color, 0.12);
139}
140
141.mimick-masonry-canvas {
142 background-color: @view_bg_color;
143}
144
145/* Hide the horizontal scrollbar on the search form. hscrollbar_policy
146 is Automatic (not Never) so the scrolled window's minimum width is
147 decoupled from the wide AdwEntryRow children (~434px). The scrollbar
148 itself is never needed visually. */
149scrolledwindow.mimick-search-scroll scrollbar.horizontal {
150 min-height: 0;
151 opacity: 0;
152}
153
154picture.mimick-grid-thumb {
155 min-width: 80px;
156 min-height: 60px;
157}
158
159window.mimick-wide picture.mimick-grid-thumb {
160 min-width: 356px;
161 min-height: 200px;
162}
163
164picture.mimick-thumbnail-square,
165picture.mimick-thumbnail-square.mimick-thumbnail-loading,
166picture.mimick-thumbnail-square.mimick-thumbnail-loaded,
167picture.mimick-thumbnail-square.mimick-thumbnail-error {
168 border-radius: 0;
169}
170
171box.mimick-transfer-shell {
172 min-height: 36px;
173 border-top: 1px solid alpha(@window_fg_color, 0.08);
174 background: alpha(@window_fg_color, 0.03);
175}
176
177box.mimick-transfer-shell.active {
178 background: alpha(@accent_bg_color, 0.04);
179}
180
181progressbar.mimick-transfer-progress {
182 min-width: 100px;
183 min-height: 18px;
184}
185
186progressbar.mimick-transfer-progress trough {
187 min-height: 18px;
188 border-radius: 999px;
189 background: alpha(@window_fg_color, 0.12);
190}
191
192progressbar.mimick-transfer-progress progress {
193 min-height: 18px;
194 border-radius: 999px;
195 background: mix(@accent_bg_color, #d16c96, 0.45);
196}
197
198/* ── Press feedback ───────────────────────────────────────────────
199 Subtle scale-down on press for all interactive elements.
200 GTK4 natively skips CSS transitions when gtk-enable-animations
201 is off, so accessibility is respected automatically.
202
203 Covers: header-bar buttons, dialog buttons, sidebar rows,
204 settings switch/action/spin rows, lightbox actions, grid tiles,
205 explore/album tiles, and any widget with .mimick-pressable. */
206
207button,
208.mimick-pressable,
209menubutton > button,
210row.activatable {
211 transition: all 150ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
212}
213
214button:active,
215.mimick-pressable:active,
216menubutton > button:active,
217row.activatable:active {
218 transform: scale(0.97);
219 opacity: 0.88;
220}
221
222overlay.mimick-cell {
223 transition: transform 80ms ease-out, opacity 80ms ease-out;
224}
225
226overlay.mimick-cell:active {
227 transform: scale(0.96);
228 opacity: 0.90;
229}
230
231picture.mimick-explore-tile,
232overlay.mimick-explore-tile,
233picture.mimick-person-avatar {
234 transition: transform 80ms ease-out, opacity 80ms ease-out;
235}
236
237picture.mimick-explore-tile:active,
238overlay.mimick-explore-tile:active,
239picture.mimick-person-avatar:active {
240 transform: scale(0.95);
241 opacity: 0.88;
242}
243
244/* Details-pane group accents. The icon classes paint just the prefix icon,
245 keeping the row text in the standard foreground for legibility. */
246.mimick-detail-icon {
247 min-width: 28px;
248 min-height: 28px;
249 padding: 6px;
250 border-radius: 10px;
251 margin: 2px 0;
252 -gtk-icon-size: 16px;
253}
254
255.mimick-accent-camera {
256 color: @accent_color;
257 background-color: alpha(@accent_bg_color, 0.18);
258}
259
260.mimick-accent-image {
261 color: @success_color;
262 background-color: alpha(@success_bg_color, 0.20);
263}
264
265.mimick-accent-location {
266 color: @warning_color;
267 background-color: alpha(@warning_bg_color, 0.22);
268}
269
270scrolledwindow.mimick-details-pane {
271 background: alpha(@window_bg_color, 0.30);
272}
273
274scrolledwindow.mimick-details-pane > viewport > box {
275 padding: 4px;
276}
277
278/* Lightbox image-load spinner. The Mimick app icon eases in from slow
279 rotation, accelerates through the middle of the cycle, then eases back
280 out — giving the breathing rhythm requested for "ramp up / ramp down". */
281@keyframes mimick-icon-spin {
282 0% { transform: rotate(0deg); }
283 20% { transform: rotate(40deg); }
284 50% { transform: rotate(220deg); }
285 80% { transform: rotate(320deg); }
286 100% { transform: rotate(360deg); }
287}
288
289image.mimick-loader-icon {
290 opacity: 0.85;
291 -gtk-icon-shadow: 0 2px 12px alpha(black, 0.45);
292 animation: mimick-icon-spin 2.4s ease-in-out infinite;
293}
294
295picture.mimick-lightbox-picture {
296 background-color: alpha(@view_fg_color, 0.05);
297}
298
299box.mimick-preview-unavailable {
300 min-width: 240px;
301 padding: 20px;
302 border-radius: 8px;
303 background-color: alpha(@window_bg_color, 0.92);
304 border: 1px solid alpha(@view_fg_color, 0.16);
305}
306
307/* ── Drag-and-drop overlay ───────────────────────────────────────
308 Shown when files are dragged over the window. */
309
310box.mimick-drop-overlay {
311 background: alpha(@accent_bg_color, 0.50);
312 border: none;
313 border-radius: 0;
314 margin: 0;
315 transition: opacity 200ms ease-in-out;
316}
317
318box.mimick-drop-overlay image {
319 -gtk-icon-size: 48px;
320 opacity: 0.85;
321 color: @accent_fg_color;
322}
323
324box.mimick-drop-overlay label {
325 font-size: 1.1em;
326 font-weight: 600;
327 opacity: 0.9;
328 color: @accent_fg_color;
329}
330
331/* ── Drag badge (multi-file count) ─────────────────────────────── */
332
333box.mimick-drag-badge {
334 background: @accent_bg_color;
335 color: @accent_fg_color;
336 border-radius: 999px;
337 padding: 2px 8px;
338 font-weight: bold;
339 font-size: 11px;
340 min-width: 20px;
341 min-height: 20px;
342}
343
344/* ── Album sidebar row drop-hover ──────────────────────────────── */
345
346row.mimick-album-drop-hover {
347 background: alpha(@accent_bg_color, 0.25);
348 border-radius: 6px;
349 transition: background 150ms ease-in-out, transform 150ms ease-in-out;
350 transform: scale(1.03);
351}
352"#;
353
354static REGISTERED: OnceLock<()> = OnceLock::new();
355
356pub fn ensure_registered() {
358 REGISTERED.get_or_init(|| {
359 let Some(display) = gdk::Display::default() else {
360 log::warn!("No default GDK display; library CSS not registered");
361 return;
362 };
363
364 let provider = CssProvider::new();
365 provider.load_from_string(LIBRARY_CSS);
366 style_context_add_provider_for_display(
367 &display,
368 &provider,
369 gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
370 );
371 });
372}