Skip to main content

euv_ui/style/class/page/
fn.rs

1use super::*;
2
3class! {
4    // ═══════════════════════════════════════════════════════════════════════════
5    // Page Banner (unified header with emoji icon)
6    // ═══════════════════════════════════════════════════════════════════════════
7
8    pub(crate) c_page {
9        position: "relative";
10        text-align: "center";
11        box-sizing: "border-box";
12    }
13
14    pub(crate) c_page_glow {
15        position: "absolute";
16        top: "-50%";
17        left: "50%";
18        transform: "translateX(-50%)";
19        width: "400px";
20        max-width: "100%";
21        height: "400px";
22        pointer-events: "none";
23    }
24
25    pub(crate) c_page_content {
26        position: "relative";
27        z-index: "1";
28    }
29
30    pub(crate) c_page_icon {
31        font-size: "36px";
32        padding-bottom: var!(space-md);
33        @media ((max-width: 767px)) {
34            font-size: "40px";
35        }
36    }
37
38    pub(crate) c_page_title {
39        font-size: var!(font-4xl);
40        font-weight: "800";
41        letter-spacing: "-0.03em";
42        margin: "0px";
43        color: var!(foreground);
44        margin-bottom: var!(space-sm);
45        @media ((max-width: 767px)) {
46            font-size: var!(font-3xl);
47        }
48    }
49
50    pub(crate) c_page_subtitle {
51        font-size: var!(font-lg);
52        color: var!(muted-foreground);
53        margin: "0px auto";
54        margin-bottom: var!(space-sm);
55        max-width: "560px";
56        opacity: "1";
57        @media ((max-width: 767px)) {
58            font-size: var!(font-base);
59        }
60    }
61
62    // ═══════════════════════════════════════════════════════════════════════════
63    // Camera Page
64    // ═══════════════════════════════════════════════════════════════════════════
65
66    pub c_camera_video_container {
67        margin: format!("{} 0", var!(space-lg));
68    }
69
70    pub c_camera_video_active {
71        width: "100%";
72        background: var!(surface-media);
73        aspect-ratio: "16 / 9";
74        object-fit: "cover";
75        border: format!("2px solid {}", var!(accent));
76        box-sizing: "border-box";
77    }
78
79    pub c_camera_video_hidden {
80        width: "100%";
81        aspect-ratio: "16 / 9";
82        display: "none";
83    }
84
85    pub c_camera_video_placeholder {
86        width: "100%";
87        aspect-ratio: "16 / 9";
88        border: format!("2px dashed {}", var!(border));
89        display: "flex";
90        align-items: "center";
91        justify-content: "center";
92        box-sizing: "border-box";
93    }
94
95    pub c_camera_placeholder_content {
96        display: "flex";
97        flex-direction: "column";
98        align-items: "center";
99        gap: var!(space-md);
100    }
101
102    pub c_camera_placeholder_icon {
103        font-size: var!(font-4xl);
104        opacity: "1";
105    }
106
107    pub c_camera_placeholder_text {
108        font-size: var!(font-base);
109        color: "inherit";
110        opacity: "1";
111    }
112
113    pub c_camera_error_box {
114        margin: format!("{} 0px", var!(gap-component));
115        color: var!(foreground);
116        font-size: var!(font-base);
117        word-break: "break-all";
118        overflow-wrap: "break-word";
119        box-sizing: "border-box";
120    }
121
122    pub c_camera_scan_result_box {
123        margin-top: var!(gap-component);
124        box-sizing: "border-box";
125    }
126
127    pub c_camera_scan_result_label {
128        font-size: var!(font-sm);
129        font-weight: "600";
130        color: var!(foreground);
131        margin-bottom: var!(space-xs);
132    }
133
134    pub c_camera_scan_result_value {
135        font-size: var!(font-base);
136        color: "inherit";
137        word-break: "break-all";
138        overflow-wrap: "break-word";
139    }
140
141    // ═══════════════════════════════════════════════════════════════════════════
142    // Canvas Drawing Board
143    // ═══════════════════════════════════════════════════════════════════════════
144
145    pub c_canvas_preview_container {
146        margin: format!("{} 0", var!(space-lg));
147        overflow: "hidden";
148        border: format!("2px solid {}", var!(border));
149        background: var!(surface-drawing);
150        aspect-ratio: "9 / 16";
151        display: "flex";
152        align-items: "center";
153        justify-content: "center";
154        box-sizing: "border-box";
155    }
156
157    pub c_canvas_placeholder {
158        color: var!(muted-foreground);
159        font-size: var!(font-sm);
160        text-align: "center";
161        padding: var!(space-lg);
162    }
163
164    pub c_canvas_preview_image {
165        width: "100%";
166        height: "100%";
167        object-fit: "cover";
168        display: "block";
169    }
170
171    pub c_canvas_container_fullscreen {
172        width: "100%";
173        height: "100%";
174        background: var!(background);
175        display: "flex";
176        flex-direction: "column";
177        position: "fixed";
178        top: "0";
179        left: "0";
180        z-index: "10002";
181        padding: format!("{} {} {} {}", var!(padding-shell-top), var!(space-lg), var!(padding-shell-bottom), var!(space-lg));
182        box-sizing: "border-box";
183    }
184
185    pub c_canvas_drawing_fullscreen_wrapper {
186        flex: "1";
187        display: "flex";
188        align-items: "center";
189        justify-content: "center";
190        overflow: "hidden";
191        padding: var!(space-xs);
192    }
193
194    pub c_canvas_drawing_fullscreen {
195        border: format!("2px solid {}", var!(border));
196        display: "block";
197        cursor: "crosshair";
198        touch-action: "none";
199    }
200
201    pub c_canvas_fullscreen_toolbar {
202        display: "flex";
203        flex-direction: "column";
204        align-items: "stretch";
205        width: "100%";
206        padding: format!("{} {}", var!(space-xs), "0px");
207        flex-shrink: "0";
208        gap: var!(space-xs);
209    }
210
211    pub c_canvas_fullscreen_toolbar_row_top {
212        display: "flex";
213        align-items: "center";
214        height: var!(mobile-header-height);
215        width: "100%";
216        gap: var!(space-sm);
217    }
218
219    pub c_canvas_fullscreen_toolbar_color_wrapper {
220        display: "flex";
221        align-items: "center";
222        justify-content: "center";
223        flex: "1";
224        min-width: "0";
225    }
226
227    pub c_canvas_color_input_fullscreen {
228        width: "100%";
229        height: "42px";
230        border: format!("1px solid {}", var!(border));
231        cursor: "pointer";
232        padding: format!("{}", var!(space-xs));
233    }
234
235    pub c_canvas_fullscreen_toolbar_row_bottom {
236        display: "flex";
237        align-items: "center";
238        width: "100%";
239    }
240
241    pub c_canvas_fullscreen_range_input {
242        flex: "1 1 0%";
243        min-width: "120px";
244        cursor: "pointer";
245        -webkit-appearance: "none";
246        appearance: "none";
247        border: "none";
248        outline: "none";
249        height: "24px";
250        ::-webkit-slider-runnable-track {
251            height: "6px";
252            background: format!("linear-gradient(to right, {} 0%, {} var(--value,50%), {} var(--value,50%), {} 100%)", var!(foreground), var!(foreground), var!(border), var!(border));
253            border-radius: "3px";
254            border: "none";
255        }
256        ::-webkit-slider-thumb {
257            width: "20px";
258            height: "20px";
259            background: var!(background);
260            border: format!("2px solid {}", var!(accent));
261            border-radius: "50%";
262            cursor: "pointer";
263            -webkit-appearance: "none";
264            // The thumb is taller than the track it rides on, so the margin
265            // pulls it up until it sits centred on the track.
266            margin-top: "-7px";
267        }
268        ::-moz-range-track {
269            height: "6px";
270            background: format!("linear-gradient(to right, {} 0%, {} var(--value,50%), {} var(--value,50%), {} 100%)", var!(foreground), var!(foreground), var!(border), var!(border));
271            border-radius: "3px";
272            border: "none";
273        }
274        ::-moz-range-thumb {
275            width: "20px";
276            height: "20px";
277            background: var!(background);
278            border: format!("2px solid {}", var!(accent));
279            border-radius: "50%";
280            cursor: "pointer";
281        }
282    }
283
284    pub c_canvas_fullscreen_toolbar_button {
285        flex: "0 0 auto";
286        overflow: "hidden";
287    }
288
289    // ═══════════════════════════════════════════════════════════════════════════
290    // Game Fullscreen (2D / 3D)
291    // ═══════════════════════════════════════════════════════════════════════════
292
293    // Fullscreen overlay container for the 2D and 3D game pages.
294    //
295    // Mirrors `c_canvas_container_fullscreen` so the safe-area caching
296    // pass in `UseEuvLayout::apply_cached_insets` can pick this selector
297    // up alongside the canvas one. Differs in that the game overlay holds
298    // the *same* `<canvas>` element that the inline page mounts (no
299    // canvas re-creation), so the running game loop, balls, cubes, FPS
300    // counter and other reactive state survive the fullscreen toggle
301    // without reset.
302    pub c_game_container_fullscreen {
303        width: "100%";
304        height: "100%";
305        background: var!(background);
306        display: "flex";
307        flex-direction: "column";
308        position: "fixed";
309        top: "0";
310        left: "0";
311        z-index: "10002";
312        padding: format!(
313        "{} {} {} {}",
314        var!(padding-shell-top),
315        var!(space-lg),
316        var!(padding-shell-bottom),
317        var!(space-lg)
318        );
319        box-sizing: "border-box";
320    }
321
322    // Toolbar wrapping the fullscreen exit button on game pages.
323    //
324    // Game pages only need an `Exit` button here (no color picker / line
325    // width control like the canvas drawing board), so the layout is a
326    // single horizontal row at the bottom of the overlay.
327    pub c_game_fullscreen_toolbar {
328        display: "flex";
329        align-items: "center";
330        justify-content: "flex-end";
331        width: "100%";
332        padding: format!("{} {} {} {}", var!(space-xs), var!(space-lg), var!(space-xs), var!(space-lg));
333        flex-shrink: "0";
334        gap: var!(space-sm);
335        box-sizing: "border-box";
336    }
337
338    // Wrapper that fills the fullscreen game container with the canvas.
339    //
340    // The game's `<canvas>` backing buffer is 600x400 (3:2). The canvas
341    // itself has `width: 100%; height: 100%` so it fills this wrapper
342    // exactly, and `object-fit: contain` on the canvas class makes the
343    // browser uniformly scale the backing buffer to fit while preserving
344    // its 3:2 aspect ratio. Without `object-fit: contain`, the canvas
345    // would be stretched to whatever shape the wrapper has, making balls
346    // render as horizontally-ovoid ellipses (e.g. on 1280x800 viewports
347    // → 1.6:1, slightly squashed vertically). The letterbox bars appear
348    // inside the canvas element rather than around an outer wrapper, so
349    // the canvas always fills the fullscreen container with the backing
350    // buffer uniformly scaled to its native aspect ratio.
351    pub c_game_fullscreen_canvas_letterbox {
352        position: "relative";
353        width: "100%";
354        max-width: "100%";
355        max-height: "100%";
356        height: "100%";
357        display: "flex";
358        align-items: "center";
359        justify-content: "center";
360    }
361
362    // ═══════════════════════════════════════════════════════════════════════════
363    // Custom Attrs Demo
364    // ═══════════════════════════════════════════════════════════════════════════
365
366    pub c_custom_attrs_demo {
367        // Use the theme foreground token (not `inherit`) so the text inside
368        // the dynamic demo block stays readable when the block itself
369        // applies a user-supplied background (e.g. `background-color: #000000`)
370        // that otherwise paints the inherited text the same colour.
371        color: var!(foreground);
372        box-sizing: "border-box";
373    }
374
375    pub c_demo_text {
376        color: var!(foreground);
377        margin-bottom: var!(gap-component);
378    }
379
380    pub c_demo_text_muted {
381        color: var!(foreground);
382        opacity: "1";
383        font-size: var!(font-base);
384        margin-bottom: "0px";
385    }
386
387    // ═══════════════════════════════════════════════════════════════════════════
388    // Conditional Rendering Demo
389    // ═══════════════════════════════════════════════════════════════════════════
390
391    pub c_toggle_content {
392        margin-top: var!(gap-component);
393        color: "inherit";
394    }
395
396    pub c_toggle_title {
397        margin-top: "0px";
398        color: var!(accent);
399        font-size: var!(font-md);
400    }
401
402    pub c_role_button_row {
403        display: "flex";
404        flex-wrap: "nowrap";
405        gap: var!(gap-element);
406        margin-bottom: var!(gap-component);
407    }
408
409    pub c_role_guest_text {
410        color: var!(foreground);
411        font-size: var!(font-base);
412    }
413
414    pub c_role_user_text {
415        color: var!(foreground);
416        font-size: var!(font-base);
417    }
418
419    pub c_role_admin_text {
420        color: var!(foreground);
421        font-size: var!(font-base);
422    }
423
424    // ═══════════════════════════════════════════════════════════════════════════
425    // 404 Not Found
426    // ═══════════════════════════════════════════════════════════════════════════
427
428    pub c_render_count_text {
429        font-size: var!(font-base);
430        color: "inherit";
431        margin-bottom: var!(gap-component);
432    }
433
434    // ═══════════════════════════════════════════════════════════════════════════
435    // Event Demo
436    // ═══════════════════════════════════════════════════════════════════════════
437
438    pub c_event_result {
439        font-size: var!(font-base);
440        color: "inherit";
441        margin-top: var!(gap-element);
442    }
443
444    pub c_event_highlight {
445        font-weight: "600";
446        color: var!(accent);
447    }
448
449    pub c_event_info_grid {
450        display: "grid";
451        grid-template-columns: "1fr 1fr";
452        gap: var!(gap-element);
453        margin-top: var!(gap-component);
454        @media ((max-width: 767px)) {
455            grid-template-columns: "1fr";
456            gap: var!(gap-element);
457        }
458    }
459
460    pub c_event_info_row {
461        display: "flex";
462        align-items: "center";
463        gap: var!(space-sm);
464        overflow: "hidden";
465    }
466
467    pub c_event_info_label {
468        font-size: var!(font-base);
469        font-weight: "600";
470        color: var!(foreground);
471        flex-shrink: "0";
472    }
473
474    pub c_event_info_value {
475        c_text_ellipsis();
476        font-size: var!(font-base);
477        font-weight: "600";
478        color: var!(accent);
479        font-family: "ui-monospace, monospace";
480        flex: "1";
481    }
482
483    pub c_event_mouse_area {
484        min-height: "120px";
485        padding: format!("{} 0px", var!(space-xl));
486        border: format!("2px dashed {}", var!(border));
487        cursor: "crosshair";
488        text-align: "center";
489        user-select: "none";
490        color: "inherit";
491    }
492
493    pub c_event_drag_zone {
494        min-height: "100px";
495        padding: format!("{} 0px", var!(space-xl));
496        border: format!("2px dashed {}", var!(border));
497        text-align: "center";
498        user-select: "none";
499        color: var!(foreground);
500    }
501
502    pub c_event_drag_zone_active {
503        min-height: "100px";
504        padding: format!("{} 0px", var!(space-xl));
505        border: format!("2px dashed {}", var!(border));
506        text-align: "center";
507        user-select: "none";
508        color: var!(foreground);
509    }
510
511    pub c_event_drag_item {
512        display: "inline-block";
513        padding: format!("{} {}", var!(space-sm), var!(space-xl));
514        background: var!(accent);
515        color: var!(text-on-accent);
516        font-size: var!(font-base);
517        font-weight: "500";
518        cursor: "grab";
519        margin: var!(space-sm);
520    }
521
522    pub c_event_drop_zone {
523        border: format!("2px dashed {}", var!(border));
524        padding: format!("{} {}", var!(space-4xl), var!(space-xl));
525        text-align: "center";
526        cursor: "pointer";
527    }
528
529    pub c_event_drop_zone_active {
530        border: format!("2px dashed {}", var!(accent));
531        padding: format!("{} {}", var!(space-4xl), var!(space-xl));
532        text-align: "center";
533        cursor: "pointer";
534        background: var!(accent-muted);
535    }
536
537    pub c_event_drop_icon {
538        font-size: var!(font-5xl);
539        display: "block";
540        padding-bottom: var!(space-md);
541    }
542
543    pub c_event_drop_text {
544        font-size: var!(font-lg);
545        font-weight: "500";
546        color: "inherit";
547        padding-bottom: var!(space-md);
548    }
549
550    pub c_event_drop_hint {
551        font-size: var!(font-base);
552        color: "inherit";
553        opacity: "1";
554        margin: "0px";
555    }
556
557    pub c_event_wheel_zone {
558        min-height: "120px";
559        padding: format!("{} 0px", var!(space-xl));
560        border: format!("2px dashed {}", var!(border));
561        text-align: "center";
562        overflow: "auto";
563        color: var!(foreground);
564    }
565
566    pub c_event_clipboard_area {
567        color: var!(foreground);
568    }
569
570    pub c_event_touch_zone {
571        min-height: "120px";
572        padding: format!("{} 0px", var!(space-xl));
573        border: format!("2px dashed {}", var!(accent));
574        text-align: "center";
575        touch-action: "none";
576        user-select: "none";
577        color: var!(accent);
578    }
579
580    pub c_event_form_area {
581        padding: format!("{} 0px", var!(space-lg));
582        color: var!(foreground);
583    }
584
585    pub c_event_media_area {
586        padding: format!("{} 0px", var!(space-lg));
587        color: var!(foreground);
588        max-width: "100%";
589        overflow: "hidden";
590    }
591
592    pub c_event_audio {
593        width: "100%";
594        max-width: "100%";
595    }
596
597    pub c_event_video_area {
598        color: var!(foreground);
599        width: "100%";
600        overflow: "hidden";
601    }
602
603    pub c_event_video {
604        width: "100%";
605        max-width: "100%";
606    }
607
608    pub c_event_image_area {
609        padding: format!("{} 0px", var!(space-lg));
610        color: "inherit";
611        width: "100%";
612        overflow: "hidden";
613        display: "flex";
614        flex-direction: "column";
615        align-items: "center";
616        gap: var!(space-md);
617    }
618
619    pub c_event_image {
620        width: "200px";
621        max-width: "100%";
622        object-fit: "contain";
623        display: "block";
624    }
625
626    pub c_event_url_text {
627        text-align: "center";
628        font-size: var!(font-sm);
629        color: "inherit";
630        opacity: "1";
631        font-family: "ui-monospace, monospace";
632        word-break: "break-all";
633        width: "100%";
634    }
635
636    // ═══════════════════════════════════════════════════════════════════════════
637    // Timer Demo
638    // ═══════════════════════════════════════════════════════════════════════════
639
640    pub c_timer_display {
641        text-align: "center";
642        padding: var!(space-xl);
643        margin: format!("{} 0px", var!(gap-component));
644        color: "inherit";
645    }
646
647    pub c_timer_value {
648        font-size: var!(font-base);
649        font-weight: "700";
650        color: var!(accent);
651        letter-spacing: "0.04em";
652        font-family: "ui-monospace, monospace";
653    }
654
655    pub c_timer_controls {
656        display: "flex";
657        flex-wrap: "wrap";
658        gap: var!(gap-element);
659    }
660
661    pub c_timer_done {
662        text-align: "center";
663        margin-top: var!(gap-component);
664        font-size: var!(font-lg);
665        font-weight: "600";
666        color: var!(foreground);
667    }
668
669    // ═══════════════════════════════════════════════════════════════════════════
670    // Browser API Demo
671    // ═══════════════════════════════════════════════════════════════════════════
672
673    pub c_browser_api_row {
674        display: "grid";
675        grid-template-columns: "1fr 1fr";
676        gap: var!(gap-component);
677        margin-bottom: var!(gap-component);
678        @media ((max-width: 767px)) {
679            grid-template-columns: "1fr";
680        }
681    }
682
683    pub c_browser_api_actions {
684        display: "flex";
685        flex-wrap: "wrap";
686        gap: var!(gap-element);
687        margin: format!("{} 0px", var!(gap-component));
688    }
689
690    pub c_browser_result_box {
691        margin-top: var!(gap-component);
692        font-size: var!(font-base);
693        word-break: "break-all";
694        color: "inherit";
695    }
696
697    pub c_browser_result_label {
698        font-weight: "600";
699        color: var!(accent);
700    }
701
702    pub c_browser_result_value {
703        color: "inherit";
704    }
705
706    pub c_browser_info_grid {
707        display: "grid";
708        gap: var!(gap-component);
709        margin-top: var!(gap-component);
710        @media ((max-width: 767px)) {
711            grid-template-columns: "1fr";
712        }
713    }
714
715    pub c_browser_info_item {
716        display: "flex";
717        flex-direction: "column";
718        gap: var!(space-xs);
719        color: "inherit";
720    }
721
722    pub c_browser_info_label {
723        font-size: var!(font-sm);
724        font-weight: "600";
725        color: "inherit";
726        opacity: "1";
727        text-transform: "uppercase";
728        letter-spacing: "0.05em";
729    }
730
731    pub c_browser_info_value {
732        font-size: var!(font-base);
733        color: "inherit";
734        word-break: "break-all";
735        font-family: "ui-monospace, monospace";
736    }
737
738    // ═══════════════════════════════════════════════════════════════════════════
739    // Keep-Alive Demo
740    // ═══════════════════════════════════════════════════════════════════════════
741
742    pub c_keep_alive_tab_bar {
743        display: "flex";
744        border-bottom: format!("1px dashed {}", var!(border));
745        margin-bottom: var!(gap-component);
746        gap: var!(gap-element);
747    }
748
749    pub c_keep_alive_tab_panel {
750        padding: format!("{} 0px", var!(gap-element));
751    }
752
753    pub c_keep_alive_panel_title {
754        margin-top: "0px";
755        color: var!(accent);
756        font-size: var!(font-md);
757        margin-bottom: var!(gap-element);
758    }
759
760    pub c_keep_alive_demo_text {
761        color: "inherit";
762        font-size: var!(font-base);
763        margin-bottom: var!(gap-component);
764    }
765
766    pub c_keep_alive_counter_display {
767        display: "flex";
768        justify-content: "center";
769        align-items: "center";
770        margin: format!("{} 0px", var!(space-xl));
771    }
772
773    pub c_keep_alive_counter_value {
774        font-size: var!(font-base);
775        font-weight: "700";
776        font-variant-numeric: "tabular-nums";
777        color: var!(accent);
778        text-align: "center";
779    }
780
781    pub c_keep_alive_counter_controls {
782        display: "flex";
783        flex-wrap: "wrap";
784        gap: format!("{}", var!(space-md));
785    }
786
787    pub c_keep_alive_form_group {
788        margin-bottom: var!(gap-component);
789    }
790
791    pub c_keep_alive_form_preview {
792        margin-top: var!(gap-component);
793        background: var!(accent-muted);
794    }
795
796    pub c_keep_alive_preview_label {
797        font-size: var!(font-base);
798        font-weight: "600";
799        color: var!(accent);
800        margin: format!("0px 0px {} 0px", var!(space-xs));
801    }
802
803    // ═══════════════════════════════════════════════════════════════════════════
804    // Binding Demo
805    // ═══════════════════════════════════════════════════════════════════════════
806
807    pub c_binding_child_box {
808        display: "flex";
809        flex-direction: "column";
810        gap: var!(gap-component);
811        margin-top: var!(gap-component);
812    }
813
814    pub c_binding_child_label {
815        font-size: var!(font-base);
816        font-weight: "600";
817        color: var!(accent);
818        text-transform: "uppercase";
819        letter-spacing: "0.05em";
820    }
821
822    pub c_binding_parent_box {
823        display: "flex";
824        flex-direction: "column";
825        gap: var!(gap-component);
826        margin-top: var!(gap-component);
827    }
828
829    pub c_binding_section_title {
830        margin-top: var!(gap-component);
831        margin-bottom: var!(gap-element);
832        color: var!(accent);
833        font-size: var!(font-md);
834        font-weight: "600";
835    }
836
837    pub c_binding_temp_converter {
838        display: "flex";
839        align-items: "flex-end";
840        gap: var!(gap-component);
841        flex-wrap: "wrap";
842        margin-top: var!(gap-component);
843    }
844
845    pub c_binding_temp_field {
846        flex: "1";
847        min-width: "120px";
848    }
849
850    pub c_binding_temp_arrow {
851        font-size: var!(font-xl);
852        font-weight: "700";
853        color: var!(accent);
854        // The row is `align-items: flex-end`, so the glyph would sit on the
855        // bottom edge of the input. The padding lifts it by half the
856        // difference between the input box and the line box of the glyph —
857        // a number no spacing token holds, because it is derived from the
858        // two heights rather than chosen.
859        padding-bottom: "10px";
860    }
861
862    pub c_binding_color_mixer {
863        margin-top: var!(gap-component);
864    }
865
866    pub c_binding_color_preview {
867        width: "100%";
868        height: "80px";
869        display: "flex";
870        align-items: "center";
871        justify-content: "center";
872        margin-bottom: var!(gap-component);
873    }
874
875    pub c_binding_color_hex {
876        font-family: "ui-monospace, monospace";
877        font-size: var!(font-xl);
878        font-weight: "700";
879        color: var!(text-on-accent);
880        letter-spacing: "0.02em";
881    }
882
883    pub c_binding_slider_row {
884        display: "flex";
885        align-items: "center";
886        gap: format!("{}", var!(space-md));
887        margin-bottom: var!(space-sm);
888    }
889
890    pub c_binding_slider_label {
891        font-size: var!(font-base);
892        font-weight: "700";
893        min-width: var!(font-sm);
894    }
895
896    pub c_binding_slider {
897        flex: "1";
898        height: "24px";
899        padding: "0px";
900        cursor: "pointer";
901        -webkit-appearance: "none";
902        appearance: "none";
903        border: "none";
904        outline: "none";
905        ::-webkit-slider-runnable-track {
906            height: "6px";
907            background: format!("linear-gradient(to right, {} 0%, {} var(--value,50%), {} var(--value,50%), {} 100%)", var!(foreground), var!(foreground), var!(border), var!(border));
908            border-radius: "3px";
909            border: "none";
910        }
911        ::-webkit-slider-thumb {
912            width: "20px";
913            height: "20px";
914            background: var!(background);
915            border: format!("2px solid {}", var!(accent));
916            border-radius: "50%";
917            cursor: "pointer";
918            -webkit-appearance: "none";
919            // The thumb is taller than the track it rides on, so the margin
920            // pulls it up until it sits centred on the track.
921            margin-top: "-7px";
922        }
923        : active::-webkit-slider-thumb {
924            transform: "scale(0.92)";
925        }
926        ::-moz-range-track {
927            height: "6px";
928            background: format!("linear-gradient(to right, {} 0%, {} var(--value,50%), {} var(--value,50%), {} 100%)", var!(foreground), var!(foreground), var!(border), var!(border));
929            border-radius: "3px";
930            border: "none";
931        }
932        ::-moz-range-thumb {
933            width: "20px";
934            height: "20px";
935            background: var!(background);
936            border: format!("2px solid {}", var!(accent));
937            border-radius: "50%";
938            cursor: "pointer";
939        }
940        : active::-moz-range-thumb {
941            transform: "scale(0.92)";
942        }
943    }
944
945    pub c_binding_slider_value {
946        font-size: var!(font-base);
947        font-weight: "500";
948        color: "inherit";
949        min-width: "32px";
950        text-align: "right";
951        font-family: "ui-monospace, monospace";
952    }
953
954    pub c_binding_typed_prop_value {
955        font-family: "ui-monospace, monospace";
956        font-size: var!(font-base);
957        font-weight: "600";
958        color: var!(accent);
959        padding: format!("1px {}", var!(space-sm));
960    }
961
962    pub c_binding_typed_prop_group {
963        display: "flex";
964        align-items: "center";
965        gap: var!(space-sm);
966    }
967
968    pub c_binding_typed_warning {
969        font-size: var!(font-base);
970        font-weight: "500";
971        color: var!(foreground);
972        margin-bottom: "0px";
973    }
974
975    pub c_binding_form_label {
976        display: "block";
977        color: "inherit";
978        font-weight: "500";
979        font-size: var!(font-base);
980    }
981
982    pub c_binding_demo_text {
983        color: "inherit";
984        margin: "0px"
985    }
986
987    pub c_binding_compact_button {
988        display: "inline-flex";
989        justify-content: "center";
990        align-items: "center";
991        height: "42px";
992        padding: format!("0px {}", var!(space-md));
993        background: var!(accent);
994        color: var!(text-on-accent);
995        border: format!("1px solid {}", var!(accent));
996        cursor: "pointer";
997        font-size: var!(font-sm);
998        font-weight: "600";
999        outline: "none";
1000        white-space: "nowrap";
1001        user-select: "none";
1002        -webkit-user-select: "none";
1003        box-sizing: "border-box";
1004        :hover {
1005            background: var!(accent);
1006        }
1007        :focus-visible {
1008            outline: "none";
1009        }
1010        :active {
1011            background: var!(accent);
1012            color: var!(text-on-accent);
1013            border-color: var!(accent);
1014        }
1015    }
1016
1017    // ═══════════════════════════════════════════════════════════════════════════
1018    // Custom Attrs - Dynamic Style
1019    // ═══════════════════════════════════════════════════════════════════════════
1020
1021    pub c_attrs_dynamic_demo(prop_key: &str, prop_value: &str) {
1022        {
1023            prop_key
1024        }
1025        : prop_value;
1026        // No layout defaults: this class lives on a `<p>` whose own margin /
1027        // spacing is controlled by its parent's typography rules, and pinning
1028        // any colour here would shadow the user-supplied dynamic property.
1029        // Users are expected to set `color` / `background` / `font-*` /
1030        // etc. via the two CSS Property Key / Value inputs above; whatever
1031        // they type becomes the literal CSS rule applied to the paragraph.
1032    }
1033
1034    // ═══════════════════════════════════════════════════════════════════════════
1035    // Dynamic Component Demo
1036    // ═══════════════════════════════════════════════════════════════════════════
1037
1038    pub c_dynamic_component_tab_bar {
1039        display: "flex";
1040        flex-wrap: "wrap";
1041        gap: var!(gap-element);
1042        margin-bottom: var!(gap-component);
1043    }
1044
1045    pub c_dynamic_component_panel {
1046        display: "block";
1047        min-height: var!(min-height-sm);
1048        margin-top: var!(gap-component);
1049    }
1050
1051    // ═══════════════════════════════════════════════════════════════════════════
1052    // Sticky & CSS Effects Demo
1053    // ═══════════════════════════════════════════════════════════════════════════
1054
1055    // ═══════════════════════════════════════════════════════════════════════════
1056    // Home Page — Section
1057    // ═══════════════════════════════════════════════════════════════════════════
1058
1059    pub c_home {
1060        position: "relative";
1061        text-align: "center";
1062        margin-bottom: var!(space-xl);
1063        flex: "1";
1064        display: "flex";
1065        flex-direction: "column";
1066        justify-content: "center";
1067        @media ((max-width: 767px)) {
1068            margin-bottom: var!(space-lg);
1069            flex: "0 0 auto";
1070            justify-content: "flex-start";
1071        }
1072    }
1073
1074    pub c_home_content {
1075        position: "relative";
1076        z-index: "1";
1077    }
1078
1079    pub c_home_badge_row {
1080        display: "inline-flex";
1081        align-items: "center";
1082        // A base rule resets this to `flex`, so the row spans the full column
1083        // and `text-align: center` on the parent no longer centres it — a flex
1084        // child is placed by its own alignment. Without this the badges sit at
1085        // the left edge while the centred title below them does not.
1086        justify-content: "center";
1087        gap: var!(space-sm);
1088        margin-bottom: var!(space-md);
1089    }
1090
1091    pub c_home_badge {
1092        // The wrapping-atom recipe (§7), like the tag and the badge.
1093        display: "inline-block";
1094        vertical-align: "middle";
1095        text-align: "center";
1096        line-height: "1";
1097        box-sizing: "border-box";
1098        padding: format!("{} {}", var!(space-xs), var!(space-sm));
1099        color: var!(accent);
1100        font-size: var!(font-xs);
1101        font-weight: "600";
1102        letter-spacing: "0.05em";
1103        border: format!("1px solid {}", var!(accent));
1104    }
1105
1106    pub c_home_title {
1107        font-size: var!(font-5xl);
1108        font-weight: "800";
1109        letter-spacing: "-0.03em";
1110        margin: "0px";
1111        color: var!(foreground);
1112        margin-bottom: var!(space-xs);
1113        @media ((max-width: 767px)) {
1114            font-size: var!(font-4xl);
1115        }
1116    }
1117
1118    pub c_home_subtitle {
1119        font-size: var!(font-lg);
1120        color: var!(foreground);
1121        margin: "0px auto";
1122        max-width: "520px";
1123        margin-bottom: var!(space-lg);
1124        @media ((max-width: 767px)) {
1125            font-size: var!(font-base);
1126        }
1127    }
1128
1129    pub c_home_actions {
1130        display: "flex";
1131        gap: var!(space-md);
1132        justify-content: "center";
1133        flex-wrap: "wrap";
1134        // Below the breakpoint the actions become equal-width rows: the two
1135        // labels differ in length, so a content-sized wrap leaves the shorter
1136        // button visibly narrower and the row reads as misaligned.
1137        @media ((max-width: 767px)) {
1138            flex-wrap: "nowrap";
1139            align-items: "stretch";
1140        }
1141    }
1142
1143    pub c_home_btn_primary {
1144        display: "inline-flex";
1145        justify-content: "center";
1146        align-items: "center";
1147        gap: var!(space-sm);
1148        // A button is a nowrap element (§7).
1149        white-space: "nowrap";
1150        padding: format!("{} {}", var!(space-sm), var!(space-2xl));
1151        background: var!(accent);
1152        color: var!(text-on-accent);
1153        font-size: var!(font-base);
1154        font-weight: "600";
1155        text-decoration: "none";
1156        border: "1.5px solid transparent";
1157        cursor: "pointer";
1158        letter-spacing: "0.01em";
1159        vertical-align: "middle";
1160        min-height: var!(min-height-sm);
1161        @media ((max-width: 767px)) {
1162            flex: "1 1 0px";
1163            min-width: "0px";
1164        }
1165        :focus-visible {
1166            outline: "none";
1167        }
1168        :active {
1169            background: var!(accent);
1170            color: var!(text-on-accent);
1171            border-color: "1.5px solid transparent";
1172        }
1173    }
1174
1175    pub c_home_btn_secondary {
1176        display: "inline-flex";
1177        justify-content: "center";
1178        align-items: "center";
1179        gap: var!(space-sm);
1180        // A button is a nowrap element (§7).
1181        white-space: "nowrap";
1182        padding: format!("{} {}", var!(space-sm), var!(space-2xl));
1183        color: var!(accent);
1184        font-size: var!(font-base);
1185        font-weight: "600";
1186        text-decoration: "none";
1187        border: format!("1.5px solid {}", var!(accent));
1188        cursor: "pointer";
1189        letter-spacing: "0.01em";
1190        vertical-align: "middle";
1191        min-height: var!(min-height-sm);
1192        @media ((max-width: 767px)) {
1193            flex: "1 1 0px";
1194            min-width: "0px";
1195        }
1196        :focus-visible {
1197            outline: "none";
1198        }
1199        :active {
1200            background: "transparent";
1201            color: var!(accent);
1202            border-color: var!(accent);
1203        }
1204    }
1205
1206    // ═══════════════════════════════════════════════════════════════════════════
1207    // Home Page — Stats Row
1208    // ═══════════════════════════════════════════════════════════════════════════
1209
1210    pub c_home_stats {
1211        display: "grid";
1212        grid-template-columns: "repeat(4, 1fr)";
1213        gap: var!(space-md);
1214        margin-bottom: var!(space-xl);
1215        @media ((max-width: 767px)) {
1216            grid-template-columns: "repeat(2, 1fr)";
1217            gap: var!(space-sm);
1218        }
1219    }
1220
1221    pub c_home_stat_card {
1222        display: "flex";
1223        flex-direction: "column";
1224        align-items: "center";
1225        justify-content: "center";
1226        gap: var!(space-xs);
1227    }
1228
1229    pub c_home_stat_icon {
1230        font-size: var!(font-2xl);
1231    }
1232
1233    pub c_home_stat_value {
1234        font-size: var!(font-xl);
1235        font-weight: "800";
1236        color: var!(foreground);
1237        letter-spacing: "-0.01em";
1238    }
1239
1240    pub c_home_stat_label {
1241        font-size: var!(font-sm);
1242        color: var!(muted-foreground);
1243        font-weight: "500";
1244    }
1245
1246    // ═══════════════════════════════════════════════════════════════════════════
1247    // Home Page — Section & Feature Cards
1248    // ═══════════════════════════════════════════════════════════════════════════
1249
1250    pub c_home_section_title {
1251        font-size: var!(font-2xl);
1252        font-weight: "700";
1253        color: var!(foreground);
1254        margin: "0px";
1255        margin: format!("{} 0px", var!(gap-component));
1256        letter-spacing: "-0.02em";
1257    }
1258
1259    pub c_home_section_desc {
1260        font-size: var!(font-base);
1261        color: var!(foreground);
1262        margin: "0px";
1263        margin-bottom: var!(space-2xl);
1264    }
1265
1266    pub c_home_feature_grid {
1267        display: "grid";
1268        grid-template-columns: "repeat(2, 1fr)";
1269        // The cards carry the block gap between them, so the grid only has
1270        // to separate the columns: a row gap on top of that would count the
1271        // same space twice and the rows would drift apart.
1272        column-gap: var!(space-md);
1273        row-gap: "0px";
1274        @media ((max-width: 767px)) {
1275            grid-template-columns: "1fr";
1276        }
1277    }
1278
1279    pub c_feature_card {
1280        display: "flex";
1281        flex-direction: "column";
1282        gap: var!(space-sm);
1283        padding: "0px";
1284        overflow: "hidden";
1285    }
1286
1287    pub c_feature_header {
1288        display: "flex";
1289        align-items: "center";
1290        gap: var!(space-sm);
1291    }
1292
1293    pub c_feature_icon {
1294        font-size: var!(font-2xl);
1295        display: "flex";
1296        align-items: "center";
1297        justify-content: "center";
1298        line-height: "1";
1299        flex-shrink: "0";
1300    }
1301
1302    pub c_feature_name {
1303        font-size: var!(font-lg);
1304        font-weight: "600";
1305        color: var!(foreground);
1306        margin: "0px";
1307        overflow: "hidden";
1308        text-overflow: "ellipsis";
1309        white-space: "nowrap";
1310    }
1311
1312    pub c_feature_desc {
1313        font-size: var!(font-sm);
1314        color: var!(foreground);
1315        margin: "0px";
1316    }
1317
1318    // ═══════════════════════════════════════════════════════════════════════════
1319    // Home Page
1320    // ═══════════════════════════════════════════════════════════════════════════
1321
1322    pub(crate) c_text_ellipsis {
1323        overflow: "hidden";
1324        text-overflow: "ellipsis";
1325        white-space: "nowrap";
1326    }
1327
1328    pub c_info_row {
1329        display: "flex";
1330        align-items: "center";
1331        gap: var!(space-md);
1332        padding: format!("{} 0", var!(space-sm));
1333        overflow: "hidden";
1334    }
1335
1336    pub c_info_label {
1337        font-size: var!(font-sm);
1338        font-weight: "500";
1339        color: var!(muted-foreground);
1340        min-width: "72px";
1341        flex-shrink: "0";
1342        letter-spacing: "0.02em";
1343    }
1344
1345    pub c_info_value {
1346        c_text_ellipsis();
1347        font-size: var!(font-base);
1348        font-weight: "600";
1349        color: var!(foreground);
1350        font-family: "ui-monospace, monospace";
1351        flex: "1";
1352    }
1353
1354    pub c_info_link {
1355        c_text_ellipsis();
1356        font-size: var!(font-base);
1357        font-weight: "600";
1358        color: var!(accent);
1359        font-family: "ui-monospace, monospace";
1360        flex: "1";
1361        text-decoration: "none";
1362        transition: format!("opacity {} {}", var!(duration-fast), var!(ease-out));
1363        :hover {
1364            opacity: "1";
1365        }
1366    }
1367
1368    // ═══════════════════════════════════════════════════════════════════════════
1369    // Async / Loading States
1370    // ═══════════════════════════════════════════════════════════════════════════
1371
1372    pub(crate) c_loading_container {
1373        display: "flex";
1374        align-items: "center";
1375        justify-content: "center";
1376        gap: var!(gap-component);
1377        margin: format!("{} 0px", var!(gap-component));
1378        box-sizing: "border-box";
1379    }
1380
1381    pub c_spinner {
1382        width: "40px";
1383        height: "40px";
1384        border: format!("4px solid {}", var!(accent-muted));
1385        border-top: format!("4px solid {}", var!(accent));
1386        border-right: format!("4px solid {}", var!(accent));
1387        border-bottom: format!("4px solid {}", var!(accent));
1388        border-radius: "50%";
1389        flex-shrink: "0";
1390        animation: "euv-spin 0.8s linear infinite";
1391    }
1392
1393    pub(crate) c_loading_text_col {
1394        display: "flex";
1395        flex-direction: "column";
1396        gap: var!(space-xs);
1397    }
1398
1399    pub(crate) c_loading_title {
1400        color: var!(foreground);
1401        font-size: var!(font-md);
1402        font-weight: "500";
1403    }
1404
1405    pub(crate) c_loading_subtitle {
1406        color: var!(muted-foreground);
1407        font-size: var!(font-base);
1408    }
1409
1410    pub c_loading_overlay(background: &str) {
1411        position: "absolute";
1412        top: "0";
1413        left: "0";
1414        width: "100%";
1415        height: "100%";
1416        display: "flex";
1417        flex-direction: "column";
1418        align-items: "center";
1419        justify-content: "center";
1420        gap: var!(gap-component);
1421        background: background;
1422        z-index: "5";
1423        box-sizing: "border-box";
1424    }
1425
1426    pub c_error_container {
1427        display: "flex";
1428        align-items: "center";
1429        gap: var!(gap-element);
1430        margin-top: var!(gap-component);
1431        box-sizing: "border-box";
1432    }
1433
1434    pub c_error_icon {
1435        width: "20px";
1436        height: "20px";
1437        display: "flex";
1438        align-items: "center";
1439        justify-content: "center";
1440        flex-shrink: "0";
1441        color: var!(foreground);
1442        font-size: var!(font-sm);
1443        font-weight: "700";
1444    }
1445
1446    pub c_error_text {
1447        color: var!(foreground);
1448        font-size: var!(font-base);
1449    }
1450
1451    pub c_data_box {
1452        margin: format!("{} 0px", var!(gap-component));
1453        box-sizing: "border-box";
1454    }
1455
1456    pub c_data_pre {
1457        color: var!(foreground);
1458        font-size: var!(font-base);
1459        margin: "0px";
1460        white-space: "pre-wrap";
1461        word-break: "break-all";
1462        font-family: "ui-monospace, monospace";
1463    }
1464
1465    pub c_fetch_hint {
1466        color: "inherit";
1467        opacity: "1";
1468        font-size: var!(font-base);
1469        margin-bottom: var!(gap-component);
1470    }
1471
1472    // ═══════════════════════════════════════════════════════════════════════════
1473    // Log / Console Display
1474    // ═══════════════════════════════════════════════════════════════════════════
1475
1476    pub c_log_container {
1477        font-family: "ui-monospace, monospace";
1478        font-size: var!(font-base);
1479        margin-top: "0px";
1480        color: var!(foreground);
1481    }
1482
1483    pub c_log_item {
1484        padding: format!("{} 0px", var!(space-sm));
1485        font-size: var!(font-base);
1486        color: var!(foreground);
1487    }
1488}