Skip to main content

euv_ui/style/class/display/
fn.rs

1use super::*;
2
3class! {
4    pub c_euv_tabs {
5        display: "flex";
6        flex-direction: "column";
7        width: "100%";
8    }
9
10    pub c_euv_tabs_bar {
11        display: "flex";
12        border-bottom: format!("1px dashed {}", var!(border));
13        margin-bottom: var!(gap-component);
14        gap: var!(gap-element);
15        @media ((max-width: 767px)) {
16            flex-wrap: "wrap";
17        }
18    }
19
20    pub c_euv_tab_item {
21        padding: format!("{} {}", var!(space-md), var!(space-xl));
22        cursor: "pointer";
23        font-size: var!(font-base);
24        font-weight: "500";
25        border-bottom: "2px solid transparent";
26        color: "inherit";
27        background: "transparent";
28        touch-action: "manipulation";
29        user-select: "none";
30        -webkit-user-select: "none";
31        white-space: "nowrap";
32        :hover {
33            background: var!(accent-muted);
34            color: var!(accent);
35        }
36    }
37
38    pub c_euv_tab_item_active {
39        padding: format!("{} {}", var!(space-md), var!(space-xl));
40        cursor: "pointer";
41        font-size: var!(font-base);
42        font-weight: "500";
43        border-bottom: format!("2px solid {}", var!(accent));
44        color: var!(text-on-accent);
45        background: var!(accent);
46        touch-action: "manipulation";
47        user-select: "none";
48        -webkit-user-select: "none";
49        white-space: "nowrap";
50    }
51
52    pub c_euv_tabs_panel {
53        padding: format!("{} 0px", var!(gap-element));
54    }
55
56    pub c_euv_collapse_item {
57        border-bottom: format!("1px dashed {}", var!(border));
58    }
59
60    pub c_euv_collapse_header {
61        display: "flex";
62        align-items: "center";
63        gap: var!(space-sm);
64        width: "100%";
65        min-height: var!(min-height-base);
66        padding: format!("{} 0px", var!(space-sm));
67        cursor: "pointer";
68        text-align: "left";
69        font-size: var!(font-base);
70        font-weight: "500";
71        color: var!(foreground);
72        background: "transparent";
73        border: "none";
74        outline: "none";
75        touch-action: "manipulation";
76        user-select: "none";
77        -webkit-user-select: "none";
78        :hover {
79            background: var!(accent-muted);
80            color: var!(accent);
81        }
82    }
83
84    pub c_euv_collapse_header_active {
85        display: "flex";
86        align-items: "center";
87        gap: var!(space-sm);
88        width: "100%";
89        min-height: var!(min-height-base);
90        padding: format!("{} 0px", var!(space-sm));
91        cursor: "pointer";
92        text-align: "left";
93        font-size: var!(font-base);
94        font-weight: "600";
95        color: var!(accent);
96        background: var!(accent-muted);
97        border: "none";
98        outline: "none";
99        touch-action: "manipulation";
100        user-select: "none";
101        -webkit-user-select: "none";
102    }
103
104    pub c_euv_collapse_body {
105        overflow: "hidden";
106        font-size: var!(font-base);
107        color: var!(foreground);
108        transition: format!("max-height {} {}, opacity {} {}", var!(duration-normal), var!(ease-out), var!(duration-normal), var!(ease-out));
109    }
110
111    pub c_euv_collapse_body_open {
112        overflow: "hidden";
113        max-height: "none";
114        opacity: "1";
115        padding-bottom: var!(space-md);
116        font-size: var!(font-base);
117        color: var!(foreground);
118        transition: format!("max-height {} {}, opacity {} {}", var!(duration-normal), var!(ease-out), var!(duration-normal), var!(ease-out));
119    }
120
121    pub c_euv_collapse_body_closed {
122        overflow: "hidden";
123        max-height: "0px";
124        opacity: "0";
125        font-size: var!(font-base);
126        color: var!(foreground);
127        transition: format!("max-height {} {}, opacity {} {}", var!(duration-normal), var!(ease-out), var!(duration-normal), var!(ease-out));
128    }
129
130    pub c_euv_breadcrumb {
131        display: "flex";
132        align-items: "center";
133        flex-wrap: "wrap";
134        gap: var!(space-xs);
135        font-size: var!(font-sm);
136    }
137
138    pub c_euv_breadcrumb_item {
139        font-size: var!(font-sm);
140        color: var!(muted-foreground);
141        cursor: "pointer";
142        text-decoration: "none";
143        touch-action: "manipulation";
144        user-select: "none";
145        -webkit-user-select: "none";
146        :hover {
147            background: var!(accent-muted);
148            color: var!(accent);
149        }
150    }
151
152    pub c_euv_breadcrumb_sep {
153        font-size: var!(font-sm);
154        color: var!(muted-foreground);
155        user-select: "none";
156        -webkit-user-select: "none";
157    }
158
159    pub c_euv_breadcrumb_current {
160        font-size: var!(font-sm);
161        font-weight: "600";
162        color: var!(foreground);
163        cursor: "default";
164    }
165
166    pub c_euv_divider {
167        display: "block";
168        width: "100%";
169    }
170
171    pub c_euv_divider_horizontal {
172        display: "block";
173        width: "100%";
174        height: "0px";
175        border: "none";
176        border-top: format!("1px solid {}", var!(border));
177        margin: format!("{} 0px", var!(space-lg));
178    }
179
180    pub c_euv_divider_vertical {
181        display: "block";
182        width: "0px";
183        height: "auto";
184        align-self: "center";
185        border: "none";
186        border-left: format!("1px solid {}", var!(border));
187        margin: format!("0px {}", var!(space-sm));
188    }
189
190    pub c_euv_divider_labeled {
191        display: "flex";
192        align-items: "center";
193        gap: var!(space-sm);
194        width: "100%";
195        margin: format!("{} 0px", var!(space-lg));
196    }
197
198    pub c_euv_divider_label_text {
199        font-size: var!(font-xs);
200        font-weight: "500";
201        color: var!(muted-foreground);
202        white-space: "nowrap";
203        user-select: "none";
204        -webkit-user-select: "none";
205    }
206
207    // ═══════════════════════════════════════════════════════════════════════════
208    // Card Component
209    // ═══════════════════════════════════════════════════════════════════════════
210
211    pub c_card {
212        color: var!(foreground);
213        box-sizing: "border-box";
214    }
215
216    pub c_card_title {
217        margin: format!("{} 0px", var!(gap-component));
218        color: var!(foreground);
219        font-size: var!(font-lg);
220        font-weight: "600";
221        padding-bottom: var!(gap-component);
222        border-bottom: format!("1px dashed {}", var!(border));
223        letter-spacing: "-0.01em";
224        @media ((max-width: 767px)) {
225            font-size: var!(font-md);
226        }
227    }
228
229    // ═══════════════════════════════════════════════════════════════════════════
230    // Buttons
231    // ═══════════════════════════════════════════════════════════════════════════
232
233    pub c_primary_button {
234        display: "flex";
235        justify-content: "center";
236        align-items: "center";
237        gap: var!(space-sm);
238        width: "100%";
239        height: "42px";
240        background: var!(accent);
241        color: var!(text-on-accent);
242        border: "1px solid transparent";
243        padding: format!("0px {}", var!(space-2xl));
244        cursor: "pointer";
245        font-size: var!(font-base);
246        font-weight: "500";
247        letter-spacing: "0.02em";
248        text-align: "center";
249        outline: "none";
250        box-sizing: "border-box";
251        white-space: "nowrap";
252        overflow: "hidden";
253        text-overflow: "ellipsis";
254        user-select: "none";
255        -webkit-user-select: "none";
256        :focus-visible {
257            outline: "none";
258        }
259        :disabled {
260            background: var!(muted-foreground);
261            cursor: "not-allowed";
262            opacity: "1";
263        }
264        :hover {
265            background: var!(accent);
266        }
267        :active {
268            background: var!(accent);
269            color: var!(text-on-accent);
270            border-color: "transparent";
271        }
272        @media ((max-width: 767px)) {
273            max-width: "100%";
274            padding: format!("{} {}", var!(space-lg), var!(space-xl));
275            font-size: var!(font-md);
276        }
277    }
278
279    pub c_modal_close_button {
280        display: "flex";
281        justify-content: "center";
282        align-items: "center";
283        color: "inherit";
284        border: "none";
285        padding: format!("{} {}", var!(space-sm), var!(space-md));
286        cursor: "pointer";
287        font-size: "20px";
288        font-weight: "400";
289        vertical-align: "middle";
290        outline: "none";
291        opacity: "1";
292        flex-shrink: "0";
293        transition: format!("opacity {} {}", var!(duration-fast), var!(ease-out));
294        :hover {
295            opacity: "1";
296        }
297        :active {
298            opacity: "1";
299        }
300        :focus {
301            outline: "none";
302        }
303    }
304
305    // ═══════════════════════════════════════════════════════════════════════════
306    // Button Controls Container
307    // ═══════════════════════════════════════════════════════════════════════════
308
309    pub c_button_controls {
310        display: "flex";
311        flex-wrap: "wrap";
312        gap: var!(gap-element);
313        margin-top: var!(gap-component);
314        margin-bottom: var!(gap-component);
315    }
316
317    // ═══════════════════════════════════════════════════════════════════════════
318    // Euv Button Variants
319    // ═══════════════════════════════════════════════════════════════════════════
320
321    pub c_euv_button_primary_md {
322        display: "flex";
323        justify-content: "center";
324        align-items: "center";
325        gap: var!(space-sm);
326        flex: "1 1 120px";
327        height: "42px";
328        padding: format!("0px {}", var!(space-xl));
329        background: var!(accent);
330        color: var!(text-on-accent);
331        cursor: "pointer";
332        font-size: var!(font-base);
333        font-weight: "500";
334        outline: "none";
335        white-space: "nowrap";
336        user-select: "none";
337        -webkit-user-select: "none";
338        box-sizing: "border-box";
339        :disabled {
340            background: var!(muted-foreground);
341            cursor: "not-allowed";
342            opacity: "1";
343        }
344    }
345
346    pub c_euv_button_outline_md {
347        display: "flex";
348        justify-content: "center";
349        align-items: "center";
350        gap: var!(space-sm);
351        flex: "1 1 120px";
352        height: "42px";
353        padding: format!("0px {}", var!(space-xl));
354        color: var!(foreground);
355        border: format!("1px solid {}", var!(border));
356        cursor: "pointer";
357        font-size: var!(font-base);
358        font-weight: "500";
359        outline: "none";
360        white-space: "nowrap";
361        user-select: "none";
362        -webkit-user-select: "none";
363        box-sizing: "border-box";
364        :focus-visible {
365            outline: "none";
366        }
367        :disabled {
368            background: var!(muted-foreground);
369            cursor: "not-allowed";
370            opacity: "1";
371        }
372    }
373
374    // ═══════════════════════════════════════════════════════════════════════════
375    // Badges
376    // ═══════════════════════════════════════════════════════════════════════════
377
378    pub c_badge {
379        display: "inline-flex";
380        justify-content: "center";
381        align-items: "center";
382        color: var!(text-on-accent);
383        padding: format!("{} {}", var!(space-2xs), var!(space-sm));
384        border: format!("1px solid {}", var!(accent));
385        font-size: var!(font-xs);
386        font-weight: "600";
387        cursor: "pointer";
388        text-align: "center";
389        background: var!(accent);
390        :focus-visible {
391            outline: "none";
392        }
393        @media ((max-width: 767px)) {
394            padding: format!("{} {}", var!(space-2xs), var!(space-xs));
395            font-size: var!(font-xs);
396        }
397    }
398
399    pub c_badge_outline {
400        display: "inline-flex";
401        justify-content: "center";
402        align-items: "center";
403        color: var!(foreground);
404        padding: format!("{} {}", var!(space-2xs), var!(space-sm));
405        font-size: var!(font-xs);
406        font-weight: "600";
407        cursor: "pointer";
408        text-align: "center";
409        border: format!("1.5px solid {}", var!(border));
410        @media ((max-width: 767px)) {
411            padding: format!("{} {}", var!(space-2xs), var!(space-xs));
412            font-size: var!(font-xs);
413        }
414    }
415
416    pub c_euv_tag_solid_black {
417        display: "inline-block";
418        vertical-align: "middle";
419        text-align: "center";
420        line-height: "1";
421        color: var!(text-on-accent);
422        padding: format!("{} {}", var!(space-xs), var!(space-md));
423        font-size: var!(font-sm);
424        font-weight: "600";
425        cursor: "pointer";
426        background: var!(accent);
427        border: format!("1px solid {}", var!(accent));
428        box-sizing: "border-box";
429    }
430
431    pub c_euv_tag_solid_white {
432        display: "inline-block";
433        vertical-align: "middle";
434        text-align: "center";
435        line-height: "1";
436        color: var!(foreground);
437        padding: format!("{} {}", var!(space-2xs), var!(space-sm));
438        font-size: var!(font-xs);
439        font-weight: "600";
440        cursor: "pointer";
441        border: format!("1.5px solid {}", var!(accent));
442        box-sizing: "border-box";
443    }
444
445    // ═══════════════════════════════════════════════════════════════════════════
446    // Euv Tag Variants (semantic colour tags)
447    // ═══════════════════════════════════════════════════════════════════════════
448
449    pub c_euv_tag_outline_black {
450        display: "inline-block";
451        vertical-align: "middle";
452        text-align: "center";
453        line-height: "1";
454        color: var!(accent);
455        padding: format!("{} {}", var!(space-2xs), var!(space-sm));
456        font-size: var!(font-xs);
457        font-weight: "600";
458        cursor: "pointer";
459        border: format!("1.5px solid {}", var!(accent));
460        box-sizing: "border-box";
461    }
462
463    pub c_euv_tag_outline_white {
464        display: "inline-block";
465        vertical-align: "middle";
466        text-align: "center";
467        line-height: "1";
468        color: var!(foreground);
469        padding: format!("{} {}", var!(space-xs), var!(space-md));
470        font-size: var!(font-sm);
471        font-weight: "600";
472        cursor: "pointer";
473        border: format!("1.5px solid {}", var!(border));
474        box-sizing: "border-box";
475    }
476
477    // ═══════════════════════════════════════════════════════════════════════════
478    // Status Boxes
479    // ═══════════════════════════════════════════════════════════════════════════
480
481    pub c_error_box {
482        margin: format!("{} 0px", var!(gap-component));
483        background: var!(accent-muted);
484        color: var!(foreground);
485        font-size: var!(font-base);
486        box-sizing: "border-box";
487    }
488
489    pub c_success_box {
490        margin: format!("{} 0px", var!(gap-component));
491        background: var!(accent-muted);
492        color: var!(foreground);
493        font-size: var!(font-base);
494        box-sizing: "border-box";
495    }
496
497    // ═══════════════════════════════════════════════════════════════════════════
498    // Counter / Signals Demo
499    // ═══════════════════════════════════════════════════════════════════════════
500
501    pub c_counter_row {
502        display: "grid";
503        grid-template-columns: "1fr 1fr";
504        align-items: "center";
505        gap: var!(gap-component);
506        @media ((max-width: 767px)) {
507            grid-template-columns: "1fr";
508        }
509    }
510
511    pub c_counter_text {
512        font-size: var!(font-base);
513        color: "inherit";
514        margin-bottom: var!(gap-component);
515    }
516
517    pub c_counter_value {
518        font-weight: "700";
519        color: var!(accent);
520        font-size: var!(font-base);
521    }
522
523    pub c_counter_value_row {
524        margin-top: var!(space-sm);
525        word-break: "break-all";
526        overflow-wrap: "anywhere";
527        font-size: var!(font-base);
528        color: "inherit";
529        font-weight: "400";
530    }
531
532    pub c_badge_row {
533        display: "flex";
534        gap: var!(gap-inline);
535        flex-wrap: "wrap";
536        align-items: "center";
537    }
538
539    pub c_badge_hint {
540        margin-bottom: var!(gap-component);
541        color: "inherit";
542        opacity: "1";
543        font-size: var!(font-base);
544    }
545
546    // ═══════════════════════════════════════════════════════════════════════════
547    // Tabs
548    // ═══════════════════════════════════════════════════════════════════════════
549
550    pub c_tab_bar {
551        display: "flex";
552        border-bottom: format!("1px dashed {}", var!(border));
553        margin-bottom: var!(gap-component);
554        gap: var!(gap-element);
555        @media ((max-width: 767px)) {
556            flex-wrap: "wrap";
557        }
558    }
559
560    pub c_tab_item_active {
561        padding: format!("{} {}", var!(space-md), var!(space-xl));
562        cursor: "pointer";
563        border-bottom: format!("2px solid {}", var!(accent));
564        color: var!(text-on-accent);
565        background: var!(accent);
566        font-size: var!(font-base);
567        font-weight: "500";
568        // iOS WebKit: tap on a non-button element inside a scrollable
569        // container can be silently dropped because iOS interprets the
570        // touch as the start of a scroll gesture and never dispatches the
571        // synthetic `click`. `touch-action: manipulation` tells iOS this
572        // element only responds to taps and panning — no double-tap-zoom
573        // wait, no scroll-gesture disambiguation. `user-select: none`
574        // additionally suppresses the iOS text-selection bubble that
575        // would otherwise intercept the tap. See ui/src/style/css/fn.rs
576        // for the global reset and PR that documents the iOS repro.
577        touch-action: "manipulation";
578        user-select: "none";
579        -webkit-user-select: "none";
580    }
581
582    pub c_tab_item_inactive {
583        padding: format!("{} {}", var!(space-md), var!(space-xl));
584        cursor: "pointer";
585        border-bottom: "2px solid transparent";
586        color: "inherit";
587        font-size: var!(font-base);
588        font-weight: "500";
589        // iOS WebKit fix — see c_tab_item_active above for the rationale.
590        touch-action: "manipulation";
591        user-select: "none";
592        -webkit-user-select: "none";
593        :hover {
594            background: var!(accent-muted);
595            color: var!(accent);
596        }
597    }
598
599    pub c_tab_content {
600        padding: format!("{} 0px", var!(gap-element));
601    }
602
603    pub c_tab_text {
604        color: "inherit";
605        font-size: var!(font-base);
606        margin-bottom: var!(gap-element);
607    }
608
609    pub c_tab_text_muted {
610        color: "inherit";
611        opacity: "1";
612        font-size: var!(font-base);
613    }
614
615    pub c_tab_text_input {
616        color: "inherit";
617        font-size: var!(font-base);
618        margin-bottom: var!(gap-component);
619    }
620
621    // ═══════════════════════════════════════════════════════════════════════════
622    // List
623    // ═══════════════════════════════════════════════════════════════════════════
624
625    pub c_list_input {
626        width: "100%";
627        flex: "1";
628        height: "42px";
629        padding: format!("0px {}", var!(space-lg));
630        border: format!("1px solid {}", var!(border));
631        font-size: var!(font-base);
632        line-height: "normal";
633        box-sizing: "border-box";
634        outline: "none";
635        color: var!(foreground);
636        appearance: "none";
637        -webkit-appearance: "none";
638        -moz-appearance: "none";
639        vertical-align: "middle";
640        :hover {
641            border-color: var!(accent);
642            background: var!(accent-muted);
643        }
644        :focus {
645            outline: "none";
646            border-color: var!(accent);
647            background: var!(accent-muted);
648        }
649    }
650
651    pub c_list_input_error {
652        width: "100%";
653        flex: "1";
654        height: "42px";
655        padding: format!("0px {}", var!(space-lg));
656        border: format!("1px solid {}", var!(foreground));
657        font-size: var!(font-base);
658        line-height: "normal";
659        box-sizing: "border-box";
660        outline: "none";
661        color: var!(foreground);
662        appearance: "none";
663        -webkit-appearance: "none";
664        -moz-appearance: "none";
665        vertical-align: "middle";
666        :focus {
667            outline: "none";
668            border-color: var!(foreground);
669        }
670    }
671
672    pub c_list_error_text {
673        color: var!(foreground);
674        font-size: var!(font-base);
675        margin: format!("{} 0px", var!(gap-element));
676    }
677
678    pub c_inline_input_button_wrap {
679        flex-shrink: "0";
680    }
681
682    pub c_list_ul {
683        list-style: "none";
684        padding: "0px";
685        margin: "0px";
686        margin-top: var!(gap-component);
687    }
688
689    pub c_list_item {
690        display: "flex";
691        justify-content: "space-between";
692        align-items: "center";
693        gap: var!(gap-component);
694        min-height: var!(min-height-base);
695        margin: format!("{} 0px", var!(gap-element));
696    }
697
698    pub c_list_item_text {
699        flex: "1";
700        overflow: "hidden";
701        text-overflow: "ellipsis";
702        white-space: "nowrap";
703        font-size: var!(font-base);
704        color: "inherit";
705        min-width: "0";
706    }
707
708    pub c_list_item_button {
709        max-width: "120px";
710        flex-shrink: "0";
711    }
712
713    // ═══════════════════════════════════════════════════════════════════════════
714    // Animation Demo
715    // ═══════════════════════════════════════════════════════════════════════════
716
717    pub c_anim_fade_in {
718        margin-top: var!(gap-component);
719        animation: format!("euv-fade-in 0.5s {}", var!(ease-out));
720        font-size: var!(font-base);
721        color: "inherit";
722    }
723
724    pub c_anim_spin_container {
725        display: "flex";
726        justify-content: "center";
727        align-items: "center";
728        margin: format!("{} 0px", var!(space-lg));
729        min-height: "80px";
730    }
731
732    pub c_anim_spin {
733        font-size: var!(font-5xl);
734        animation: "euv-spin 1.5s linear infinite";
735        display: "inline-block";
736        @media ((max-width: 767px)) {
737            font-size: var!(font-4xl);
738        }
739    }
740
741    pub c_anim_spin_stopped {
742        font-size: var!(font-5xl);
743        display: "inline-block";
744        opacity: "1";
745        @media ((max-width: 767px)) {
746            font-size: var!(font-4xl);
747        }
748    }
749
750    pub c_anim_pulse_container {
751        display: "flex";
752        justify-content: "center";
753        align-items: "center";
754        margin: format!("{} 0px", var!(space-lg));
755        min-height: "80px";
756    }
757
758    pub c_anim_pulse {
759        font-size: var!(font-5xl);
760        animation: "euv-pulse 1.5s ease-in-out infinite";
761        display: "inline-block";
762        color: var!(foreground);
763        @media ((max-width: 767px)) {
764            font-size: var!(font-4xl);
765        }
766    }
767
768    pub c_anim_pulse_stopped {
769        font-size: var!(font-5xl);
770        display: "inline-block";
771        opacity: "1";
772        color: var!(foreground);
773        @media ((max-width: 767px)) {
774            font-size: var!(font-4xl);
775        }
776    }
777
778    pub c_progress_container {
779        width: "100%";
780        height: "12px";
781        margin: format!("{} 0px", var!(space-lg));
782        overflow: "hidden";
783    }
784
785    pub c_progress_bar_running {
786        height: "100%";
787        background: var!(accent);
788        animation: format!("euv-progress 1.6s {} forwards", var!(ease-in-out));
789    }
790
791    pub c_progress_bar_stopped {
792        height: "100%";
793        background: var!(accent);
794        width: "0%";
795    }
796
797    pub c_anim_scale_box {
798        margin: format!("{} 0px", var!(gap-component));
799        padding: var!(space-xl);
800        background: var!(accent);
801        cursor: "pointer";
802        transition: format!("transform {} {}", var!(duration-normal), var!(ease-out));
803    }
804
805    // ═══════════════════════════════════════════════════════════════════════════
806    // Unified Layout Primitives
807    // ═══════════════════════════════════════════════════════════════════════════
808
809    pub c_element_stack {
810        display: "flex";
811        flex-direction: "column";
812        gap: var!(gap-element);
813    }
814
815    pub c_switcher {
816        display: "flex";
817        gap: var!(gap-component);
818        flex-wrap: "wrap";
819        @media ((max-width: 767px)) {
820            flex-direction: "column";
821            gap: var!(gap-component-mobile);
822        }
823    }
824
825    pub c_switcher_col {
826        flex: "1";
827        min-width: "200px";
828        @media ((max-width: 767px)) {
829            min-width: "100%";
830        }
831    }
832
833    // ═══════════════════════════════════════════════════════════════════════════
834    // Unified Description / Hint Text
835    // ═══════════════════════════════════════════════════════════════════════════
836
837    pub c_hint {
838        color: "inherit";
839        opacity: "1";
840        font-size: var!(font-base);
841        margin-bottom: var!(gap-component);
842    }
843
844    // ═══════════════════════════════════════════════════════════════════════════
845    // euv_pagination
846    // ═══════════════════════════════════════════════════════════════════════════
847
848    pub c_euv_pagination {
849        // CSS grid with two equal columns gives us perfect 50/50 widths
850        // regardless of the link content length, which flex with `1 1
851        // auto` cannot (each link starts at its own content size). The
852        // mobile breakpoint stacks the grid to a single column so each
853        // link sizes to its content.
854        display: "grid";
855        grid-template-columns: "1fr 1fr";
856        gap: var!(gap-component);
857        // ~`space-2xl` (1.5rem) above the pagination — comfortable
858        // breathing room without a huge gap before prev/next. The original
859        // `space-4xl` (2.5rem) margin was doubled by a `padding-top` in the
860        // docs app override, leaving ~100px of empty space above the
861        // pagination boxes which made the page feel bottom-heavy.
862        margin-top: var!(space-2xl);
863        // `min-width: 0` lets prev/next text actually use ellipsis when the
864        // container is narrow — without it the grid track blows out the row.
865        min-width: "0px";
866        @media ((max-width: 767px)) {
867            grid-template-columns: "minmax(0, 1fr)";
868        }
869    }
870
871    pub c_euv_pagination_link {
872        // `min-width: 0` lets long labels ellipsize on the desktop row
873        // layout. `overflow: hidden` is required to actually apply ellipsis
874        // when content is wider than the link.
875        min-width: "0px";
876        border: format!("1px solid {}", var!(border));
877        padding: var!(space-lg);
878        cursor: "pointer";
879        display: "flex";
880        flex-direction: "column";
881        gap: var!(space-2xs);
882        overflow: "hidden";
883        :hover {
884            border-color: var!(accent);
885        }
886    }
887
888    pub c_euv_pagination_label {
889        font-size: var!(font-xs);
890        color: var!(muted-foreground);
891        text-transform: "uppercase";
892        letter-spacing: "0.08em";
893    }
894
895    pub c_euv_pagination_text {
896        font-size: var!(font-base);
897        font-weight: "600";
898        color: var!(accent);
899        // Ellipsize when the link is too narrow for the page title. The
900        // outer `.c_euv_pagination_link` already sets `overflow: hidden`
901        // and `min-width: 0`, so a single `text-overflow: ellipsis` here
902        // is enough — without `white-space: nowrap` the text wraps to
903        // multiple lines and the ellipsis never kicks in.
904        white-space: "nowrap";
905        overflow: "hidden";
906        text-overflow: "ellipsis";
907        min-width: "0px";
908    }
909
910    pub c_euv_pagination_next {
911        text-align: "right";
912    }
913
914    pub c_euv_pagination_spacer {
915        flex: "1";
916    }
917
918    // ═══════════════════════════════════════════════════════════════════════════
919    // euv_markdown / euv_footer
920    // ═══════════════════════════════════════════════════════════════════════════
921
922    pub c_euv_markdown {
923        width: "100%";
924        min-width: "0px";
925    }
926
927    pub c_euv_footer {
928        // Footer sits at the natural end of the article column. The user
929        // reaches it by scrolling to the bottom of the article — no sticky
930        // or fixed positioning. This matches the desktop behavior and
931        // Avoid any layout interference on mobile (where a sticky footer
932        // would overlap the last lines of content while scrolling).
933        // `space-2xl` (1.5rem) above the footer matches the gap between
934        // article and pagination, keeping a consistent rhythm in the
935        // tail block. The previous `space-7xl` (5rem) was far beyond the
936        // typography scale and made the footer feel detached from the
937        // pagination.
938        margin-top: var!(space-2xl);
939        // The dashed rule is this element's own `border-top`, so the
940        // padding below is the gap from the rule to the text and the
941        // padding above is the gap from the preceding content to the rule.
942        // Only one of them belongs to the band inside the rule, and it is
943        // the top one — that is the space between the divider and the text.
944        // `space-md` keeps the text close to the rule; the block is
945        // `display: flex` with `align-items: center` so the line is
946        // centred in the row the padding defines rather than riding its
947        // top edge.
948        padding: format!("{} 0px", var!(space-md));
949        border-top: format!("1px dashed {}", var!(border));
950        display: "flex";
951        align-items: "center";
952        justify-content: "center";
953        text-align: "center";
954        font-size: var!(font-sm);
955        color: var!(muted-foreground);
956    }
957
958    // ═══════════════════════════════════════════════════════════════════════════
959    // euv_result / euv_doc_layout
960    // ═══════════════════════════════════════════════════════════════════════════
961
962    pub c_euv_result {
963        display: "flex";
964        flex-direction: "column";
965        align-items: "center";
966        justify-content: "center";
967        text-align: "center";
968        gap: var!(space-md);
969        padding: format!("{} {}", var!(space-4xl), var!(space-md));
970        // Half the available height, not half the viewport: the result block
971        // lives inside a scroll container, so a viewport unit would be taller
972        // than the visible area on short pages and leave the buttons below
973        // the fold.
974        min-height: "50%";
975    }
976
977    pub c_euv_result_code {
978        font-size: var!(font-6xl);
979        font-weight: "700";
980        line-height: "1";
981        letter-spacing: "-0.02em";
982    }
983
984    pub c_euv_result_title {
985        font-size: var!(font-2xl);
986        font-weight: "600";
987        margin: "0px";
988    }
989
990    pub c_euv_result_description {
991        color: var!(muted-foreground);
992        margin: "0px";
993    }
994
995    pub c_euv_result_actions {
996        display: "flex";
997        gap: var!(space-md);
998        margin-top: var!(space-lg);
999    }
1000
1001    pub c_euv_doc_layout {
1002        display: "flex";
1003        gap: var!(space-4xl);
1004        width: "100%";
1005        min-width: "0px";
1006        max-width: "1080px";
1007        margin: "0px auto";
1008        // Fill the visible area of the scroll container (`c_app_main`,
1009        // `overflow: auto` with a definite `height: 100%`) so the content
1010        // column has room to push its tail to the bottom. A viewport unit
1011        // would be wrong here: `100vh` measures the whole window including
1012        // the app header, overshooting the visible area by the header
1013        // height and pushing pagination/footer one header past the fold.
1014        // Being a direct child of the scroll container, a percentage does
1015        // resolve against its content box — put it one level deeper and
1016        // the intermediate auto-height flex row makes `100%` degenerate to
1017        // `auto`.
1018        min-height: "100%";
1019    }
1020
1021    pub c_euv_doc_content {
1022        // Two parallel children — the article body and the
1023        // `c_euv_doc_tail` wrapper (pagination + footer). The flex column
1024        // distributes them at the two ends:
1025        //   - when the article is shorter than the column's intrinsic
1026        //     height, `justify-content: space-between` pushes the tail to
1027        //     the bottom edge;
1028        //   - when the article is longer, the tail sits right after the
1029        //     article in normal flow (because content height exceeds
1030        //     min-height, there's no remaining space to distribute).
1031        // The scroll container is `c_app_main` (`overflow: auto`), not the
1032        // window, so a viewport unit measures the whole window including the
1033        // app header. `100vh` therefore overshoots by the header height and
1034        // pushes the tail one header past the fold on a short page. Height
1035        // comes from `c_euv_doc_layout` (a direct child of the scroll
1036        // container, where a percentage resolves); here `align-items:
1037        // stretch` — the flex default — makes this column take that height
1038        // so `space-between` has the full column to distribute across.
1039        //   - article shorter than the column: `space-between` drops the
1040        //     tail to the bottom edge of the visible area;
1041        //   - article longer: the column grows with it and the tail follows
1042        //     the body in normal flow, scrolling with the page.
1043        flex: "1";
1044        min-width: "0px";
1045        max-width: var!(content-max-width);
1046        display: "flex";
1047        flex-direction: "column";
1048        justify-content: "space-between";
1049    }
1050
1051    pub c_euv_doc_body {
1052        // Groups the page title and the rendered markdown into a single
1053        // flex item. Without this the column would hold the `<h1>` and
1054        // the `<article>` as two separate items, and
1055        // `justify-content: space-between` would push them apart — a wide
1056        // blank band between the title and the first paragraph on any
1057        // page whose body is shorter than the column.
1058        display: "flex";
1059        flex-direction: "column";
1060        // The body takes its natural height; only the tail is pushed to
1061        // the far end. `flex-shrink: 0` keeps a long body from being
1062        // compressed when the column is height-constrained.
1063        flex: "0 0 auto";
1064        min-width: "0px";
1065    }
1066
1067    pub c_euv_doc_tail {
1068        // Plain block wrapper that groups pagination + footer into one
1069        // flex child. With `display: contents` the wrapper would be
1070        // skipped and the two siblings would behave as direct children
1071        // of `c_euv_doc_content` — breaking the two-parallel-containers
1072        // contract that `justify-content: space-between` relies on.
1073        display: "block";
1074    }
1075
1076    pub c_euv_doc_toc {
1077        width: "200px";
1078        flex-shrink: "0";
1079        // Pin the TOC column to the viewport while the main content scrolls.
1080        // The sticky scroll container is `c_app_main` (the overflow:auto
1081        // wrapper), so `top: 0` keeps the TOC aligned with the top of the
1082        // scrollable area for the whole article. The column is left at its
1083        // natural flex stretch height (= the article's content height) so
1084        // the sticky range covers the whole main column — without the stretch
1085        // the column would shrink to its content height and the sticky would
1086        // disengage as soon as the TOC's bottom exits the viewport.
1087        position: "sticky";
1088        top: "0px";
1089        padding-top: var!(padding-main-top);
1090        @media ((max-width: 1100px)) {
1091            display: "none";
1092        }
1093    }
1094}