-
Notifications
You must be signed in to change notification settings - Fork 10.7k
Expand file tree
/
Copy pathglobals.css
More file actions
7164 lines (7092 loc) · 209 KB
/
Copy pathglobals.css
File metadata and controls
7164 lines (7092 loc) · 209 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Atelier Zero — landing page styles.
*
* Mirrors `design-templates/open-design-landing/example.html` <style> block 1:1.
* When the canonical example.html changes, mirror the diff here so the
* template's known-good rendering stays in lockstep with the deployed site.
*
* Fonts are self-hosted via `@fontsource-variable/*` packages. The variable
* font files (one woff2 per family, ~30-50KB each) cover the entire
* weight axis we use, are served same-origin (no Google Fonts CSS or
* fonts.gstatic.com TLS handshake), and ship through CF's edge — PageSpeed
* was attributing ~2.3s of render-blocking + LCP to the old Google Fonts
* round-trip chain.
*
* Subset CSS files only declare @font-face metadata (~1KB each). The
* browser downloads the woff2 that matches the unicode-range of text it's
* about to render, so multi-script support stays intact without paying for
* unused scripts up front.
*/
@font-face {
font-family: 'Albert Sans';
src: url('/skill-assets/AlbertSans-VariableFont_wght.woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Albert Sans';
src: url('/skill-assets/AlbertSans-Italic-VariableFont_wght.woff2') format('woff2');
font-weight: 100 900;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Remix Icon';
src: url('/skill-assets/remixicon.subset.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
:root {
--paper: #fafafa;
--paper-warm: #f5f5f5;
--paper-dark: #f0f0f0;
--ink: #262626;
--ink-soft: #434343;
--ink-mute: #595959;
--ink-faint: #8c8c8c;
--coral: #63fe13;
--coral-tint: color-mix(in oklab, var(--bone), var(--coral) 8%);
--coral-soft: #83ff3b;
--mustard: #63fe13;
--olive: #218c00;
--bone: #ffffff;
--line: #d9d9d9;
--line-hover: #bfbfbf;
--line-soft: #f0f0f0;
--line-faint: #f5f5f5;
--shadow: 0 30px 60px -30px rgba(38, 38, 38, 0.16);
--serif: 'Albert Sans', 'PingFang SC', 'Microsoft YaHei', sans-serif;
--sans: 'Albert Sans', 'PingFang SC', 'Microsoft YaHei', sans-serif;
--body: 'Albert Sans', 'PingFang SC', 'Microsoft YaHei', sans-serif;
--mono: 'Albert Sans', 'PingFang SC', 'Microsoft YaHei', sans-serif;
--spacing-2: 2px;
--spacing-4: 4px;
--spacing-6: 6px;
--spacing-8: 8px;
--spacing-10: 10px;
--spacing-12: 12px;
--spacing-16: 16px;
--spacing-18: 18px;
--spacing-20: 20px;
--spacing-22: 22px;
--spacing-24: 24px;
--spacing-28: 28px;
--spacing-32: 32px;
--spacing-36: 36px;
--spacing-38: 38px;
--spacing-42: 42px;
--spacing-48: 48px;
--spacing-52: 52px;
--spacing-58: 58px;
--spacing-62: 62px;
--spacing-68: 68px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
background: var(--paper);
color: var(--ink);
/*
* Lock the page width to the viewport so any decorative element
* positioned with negative offsets (ribbons, hero-art margin-right,
* mega footer word, etc.) cannot create a horizontal scrollbar on
* narrow viewports.
*
* Use `overflow-x: clip` (modern) instead of `hidden`: clip does not
* establish a containing block for `position: fixed/sticky`, so the
* sticky nav keeps working. Browsers that don't support clip fall
* back to the hidden declaration just above it.
*/
overflow-x: hidden;
overflow-x: clip;
max-width: 100%;
}
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body {
font-family: var(--body);
font-size: 16px;
line-height: 1.55;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: relative;
overflow-wrap: break-word;
word-wrap: break-word;
}
img, video, canvas { max-width: 100%; height: auto; }
/*
* Paper texture overlay across the whole page.
*
* Rendered ABOVE `.shell` (z-index 2) so the noise/grain multiply blend
* applies uniformly to every section — including `.topbar` and `.nav`,
* which have an opaque `background: var(--paper)` to keep sticky-scroll
* content readable. Without this, opaque sections would show clean paper
* and transparent sections (hero, about, …) would show textured paper,
* which reads as two slightly different colors at the seam.
*
* Z-index is set equal to the side-rails (also z-index 3); because the
* pseudo-element appears in document order before the rails, the rails
* paint on top of the texture and keep their crisp rotated lettering.
*/
body::before {
content: '';
position: fixed;
inset: 0;
pointer-events: none;
z-index: 3;
background-image:
radial-gradient(circle at 12% 18%, rgba(217, 217, 217, 0.45) 0, transparent 28%),
radial-gradient(circle at 88% 72%, rgba(240, 240, 240, 0.72) 0, transparent 32%),
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.15 0 0 0 0 0.15 0 0 0 0 0.15 0 0 0 0.035 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
background-size: auto, auto, 240px 240px;
mix-blend-mode: multiply;
opacity: 0.62;
}
.shell { position: relative; z-index: 2; }
.container {
max-width: 1360px;
padding: 0 64px;
margin: 0 auto;
position: relative;
}
.container.wide { max-width: 1480px; }
/* fixed side rails — rotated brand text on the right edge */
.side-rail {
position: fixed;
top: 0;
bottom: 0;
width: 36px;
z-index: 3;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
}
.side-rail.right { right: 0; border-left: 1px solid var(--line-faint); }
.side-rail.left { left: 0; border-right: 1px solid var(--line-faint); }
.side-rail .rail-text {
font-family: var(--sans);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.42em;
text-transform: uppercase;
color: var(--ink-faint);
writing-mode: vertical-rl;
transform: rotate(180deg);
white-space: nowrap;
}
.side-rail.right .rail-text { transform: rotate(180deg); }
.side-rail.left .rail-text { writing-mode: vertical-rl; transform: none; }
/*
* Sticky chrome bar — wraps the editorial metadata strip + main nav as a
* single Headroom-style unit. Keeping them welded means the language
* switcher stays reachable while reading the page (you can flip locales
* mid-scroll without scrolling back to the top), and the masthead +
* navigation always read as a single nameplate rather than two unrelated
* rows.
*
* Sticky/transform/transition lived on `.nav` historically — they belong
* here now so both rows slide together. The `data-chrome-headroom` hook
* is what `header-enhancer.astro` (and the homepage inline script)
* toggle `is-hidden` on.
*/
.site-chrome {
/* Fixed so the bar stays pinned to the top and condenses on scroll
(was `absolute`, which let it scroll away). At the very top it is a
transparent overlay on the hero; past the scroll threshold the inline
script adds `.is-condensed`, which shrinks the bar and turns on the
Liquid Glass material below. */
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 50;
background: transparent;
transform: translateY(0);
transition: transform 360ms cubic-bezier(0.22, 0.61, 0.36, 1),
box-shadow 220ms ease,
border-color 220ms ease;
will-change: transform;
}
.site-chrome.is-hidden {
transform: translateY(-100%);
pointer-events: none;
box-shadow: none;
}
/* top metadata strip */
.topbar {
border-bottom: 1px solid transparent;
padding: 10px 0;
background: transparent;
/*
* Stays a positioned context so the locale menu has an absolute
* anchor. The z-index is small but non-zero so the topbar — and the
* menu that drops out of it — paint above the sibling nav inside
* `.site-chrome`. Without this the menu would be obscured by the
* nav's download/star buttons.
*/
position: relative;
z-index: 2;
}
.topbar-inner {
display: flex;
justify-content: space-between;
align-items: center;
gap: 24px;
font-family: var(--sans);
font-size: 10.5px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--ink-faint);
}
.topbar-inner b { color: var(--ink); font-weight: 600; }
.topbar-inner .coral { color: var(--coral); }
.topbar-inner > span { white-space: nowrap; }
.topbar-inner .mid { display: inline-flex; gap: 26px; }
.topbar-inner .mid > span { white-space: nowrap; }
.topbar-inner .right { display: inline-flex; gap: 18px; align-items: center; }
.topbar-inner .right > span,
.topbar-inner .right > a { white-space: nowrap; }
.topbar-link {
color: inherit;
text-decoration: none;
border-bottom: 1px solid transparent;
transition: color 160ms ease, border-color 160ms ease;
}
.topbar-link:hover { color: var(--coral); border-bottom-color: var(--coral); }
/*
* Editorial language switcher. The trigger reads as part of the
* masthead line ("● LIVE · V0.7.0 / LANG · ZH-TW") — uppercase
* tracked text, no pill, no border. The dropdown is a small
* floating panel that mirrors the topbar's paper/ink palette but
* drops the tracking so native scripts (中文 / 한국어 / العربية)
* read at their intended rhythm.
*/
.locale-switch {
display: inline-block;
position: relative;
}
.locale-trigger {
display: inline-flex;
align-items: center;
gap: 6px;
cursor: pointer;
color: inherit;
list-style: none;
border-bottom: 1px solid transparent;
transition: color 160ms ease, border-color 160ms ease;
user-select: none;
outline: none;
padding: 0;
}
.locale-trigger::-webkit-details-marker { display: none; }
.locale-trigger:hover,
.locale-switch[open] > .locale-trigger,
.locale-trigger:focus-visible {
color: var(--coral);
border-bottom-color: var(--coral);
}
.locale-trigger-prefix {
color: var(--ink-faint);
}
.locale-trigger:hover .locale-trigger-prefix,
.locale-switch[open] > .locale-trigger .locale-trigger-prefix,
.locale-trigger:focus-visible .locale-trigger-prefix {
color: var(--coral);
}
.locale-trigger-sep {
opacity: 0.55;
}
.locale-trigger-code {
font: inherit;
letter-spacing: inherit;
color: var(--ink);
font-weight: 600;
}
.locale-trigger:hover .locale-trigger-code,
.locale-switch[open] > .locale-trigger .locale-trigger-code,
.locale-trigger:focus-visible .locale-trigger-code {
color: var(--coral);
}
/* Iconic language switcher — Remix Icon "translate-2" glyph from the
skill font (see @font-face 'Remix Icon'). Replaces the 语言 · 简中 text. */
.locale-trigger-icon {
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--ink);
transition: color 160ms ease;
}
.locale-trigger-icon::before {
font-family: 'Remix Icon';
content: '\f226';
font-style: normal;
font-weight: normal;
font-size: 18px;
line-height: 1;
-webkit-font-smoothing: antialiased;
}
.locale-trigger-iconic { border-bottom: 0; }
.locale-trigger-iconic:hover .locale-trigger-icon,
.locale-switch[open] > .locale-trigger-iconic .locale-trigger-icon,
.locale-trigger-iconic:focus-visible .locale-trigger-icon {
color: var(--coral);
}
.locale-trigger-caret {
font-size: 12px;
flex-shrink: 0;
opacity: 0.7;
transition: transform 160ms ease, opacity 160ms ease;
}
.locale-switch[open] > .locale-trigger .locale-trigger-caret {
transform: rotate(180deg);
opacity: 1;
}
/*
* Two-column editorial catalogue. 17 locales × single-column would either
* scroll (ugly) or run most of the hero's height (worse). A 2-col grid
* keeps the panel under 9 rows so every language is visible at once and
* reads like a small index of nameplates rather than a dropdown.
*
* The shared top offset gives clear breathing room from the topbar baseline
* — without it the active row (which mirrors the trigger label) feels
* fused to the chrome above it.
*/
.locale-menu {
position: absolute;
top: calc(100% + var(--nav-flyout-gap, 12px));
right: 0;
z-index: 60;
display: grid;
grid-template-columns: repeat(2, minmax(168px, 1fr));
gap: 2px 4px;
padding: 10px;
background: var(--paper);
/* Match the sibling product `.nav-dropdown`: same border, radius, and shadow
so both header menus read as one panel style. */
border: 1px solid rgba(26, 26, 26, 0.12);
border-radius: 8px;
box-shadow: 0 12px 36px rgba(26, 26, 26, 0.08);
font-family: var(--sans);
letter-spacing: normal;
text-transform: none;
/* Subtle entrance — matches the rest of the site's 200ms ease-out */
animation: locale-menu-in 180ms cubic-bezier(0.23, 1, 0.32, 1);
}
@keyframes locale-menu-in {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
/* Invisible hover bridge across the gap between the trigger and the panel,
so moving the cursor down to the menu doesn't drop the hover and close it
(mirrors the sibling `.nav-dropdown::before`). */
.locale-menu::before {
content: '';
position: absolute;
top: calc(-1 * var(--nav-flyout-gap, 12px));
left: 0;
right: 0;
height: var(--nav-flyout-gap, 12px);
}
.locale-menu-item {
display: flex;
align-items: baseline;
gap: 12px;
padding: 8px 12px;
color: var(--ink);
text-decoration: none;
font-size: 12.5px;
line-height: 1.2;
/* Rounded so the hover/active fill reads as a pill-ish row inside the
8px panel rather than a hard-edged rectangle. */
border-radius: 6px;
transition: background 140ms ease, color 140ms ease;
}
/* Hover / active match the product `.nav-dropdown a`: coral-tint fill on hover,
a faint grey fill for the current locale — both keep ink text. */
.locale-menu-item:hover {
background: var(--coral-tint);
color: var(--ink);
}
.locale-menu-item:hover .locale-menu-code { color: var(--ink-faint); }
.locale-menu-item.is-active {
background: rgba(26, 26, 26, 0.04);
color: var(--ink);
}
.locale-menu-item.is-active .locale-menu-code {
color: var(--ink-faint);
}
.locale-menu-code {
flex-shrink: 0;
min-width: 46px;
font-size: 9.5px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--ink-faint);
transition: color 140ms ease;
}
.locale-menu-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@media (max-width: 520px) {
.locale-menu {
grid-template-columns: minmax(0, 1fr);
min-width: min(260px, calc(100vw - 32px));
}
.locale-menu-item { padding: 9px 12px; }
}
@media (prefers-reduced-motion: reduce) {
.locale-menu { animation: none; }
.locale-trigger-caret { transition: none; }
}
/* ---------- OpenDesign Cloud header account ---------- */
/* The avatar menu stays out of layout until a live cloud session is found. */
.nav-account {
display: inline-flex;
align-items: center;
flex-shrink: 0;
}
.nav-account [hidden] { display: none !important; }
/* Avatar menu — a borderless <details> whose summary is the avatar disc. */
.nav-account-menu {
position: relative;
display: inline-flex;
align-items: center;
}
.nav-account-trigger {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
cursor: pointer;
list-style: none;
border-radius: 999px;
}
.nav-account-trigger::-webkit-details-marker { display: none; }
.nav-account-trigger:focus-visible {
outline: 2px solid var(--coral);
outline-offset: 2px;
}
/* Circular avatar — a disc reads unmistakably as a user avatar. Dark tile +
single initial mirrors OpenDesign Cloud's identity styling. */
.nav-avatar,
.nav-avatar-fallback {
width: 32px;
height: 32px;
border-radius: 50%;
display: block;
flex-shrink: 0;
object-fit: cover;
}
.nav-avatar-fallback {
display: inline-flex;
align-items: center;
justify-content: center;
background: #1f1f1f;
color: #fafafa;
font-family: var(--sans);
font-size: 14px;
font-weight: 500;
line-height: 1;
}
/* Dropdown panel — mirrors `.locale-menu` so both header menus read as one
panel style (same border, radius, shadow, and entrance). */
.nav-account-dropdown {
position: absolute;
top: calc(100% + var(--nav-flyout-gap, 12px));
right: 0;
z-index: 60;
min-width: 240px;
display: flex;
flex-direction: column;
gap: 2px;
padding: 8px;
background: var(--paper);
border: 1px solid rgba(26, 26, 26, 0.12);
border-radius: 8px;
box-shadow: 0 12px 36px rgba(26, 26, 26, 0.08);
font-family: var(--sans);
letter-spacing: normal;
text-transform: none;
animation: locale-menu-in 180ms cubic-bezier(0.23, 1, 0.32, 1);
}
/* Hover bridge across the gap, like `.locale-menu::before`. */
.nav-account-dropdown::before {
content: '';
position: absolute;
top: calc(-1 * var(--nav-flyout-gap, 12px));
left: 0;
right: 0;
height: var(--nav-flyout-gap, 12px);
}
.nav-account-id {
display: flex;
flex-direction: column;
gap: 2px;
padding: 8px 12px 10px;
margin-bottom: 4px;
border-bottom: 1px solid var(--line-soft);
}
.nav-account-name {
color: var(--ink);
font-size: 13px;
font-weight: 600;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nav-account-name:empty { display: none; }
.nav-account-email {
color: var(--ink-faint);
font-size: 11.5px;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nav-account-email:empty { display: none; }
.nav-account-item {
display: flex;
align-items: center;
width: 100%;
padding: 8px 12px;
border: 0;
border-radius: 6px;
background: transparent;
color: var(--ink);
font-family: var(--sans);
font-size: 12.5px;
line-height: 1.2;
text-align: left;
text-decoration: none;
cursor: pointer;
transition: background 140ms ease, color 140ms ease;
}
.nav-account-item:hover,
.nav-account-item:focus-visible {
background: var(--coral-tint);
color: var(--ink);
}
.nav-account-signout {
color: var(--ink-mute);
}
@media (prefers-reduced-motion: reduce) {
.nav-account-dropdown { animation: none; }
}
.topbar .pulse {
width: 6px; height: 6px;
border-radius: 50%;
background: var(--coral);
display: inline-block;
margin-right: 6px;
animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.35; }
}
/* nav */
/*
* The sticky + headroom behavior lives on the outer `.site-chrome`
* wrapper now (so the topbar + nav slide as one unit). This rule keeps
* only the spacing and surface treatment.
*/
.nav {
/* Explicit 100% (not auto) so the width can animate to the condensed
capsule instead of snapping — `auto` widths don't transition. */
width: 100%;
/* Inline margins are `auto` in BOTH states so the width shrink stays
centered every frame (shrinks toward the middle). If the base were
`margin: 0`, the 0 → auto transition isn't interpolable and the left
edge stays pinned while the right pulls in — reads as a left-to-right
collapse. */
margin-inline: auto;
padding: 22px 0;
background: transparent;
border-bottom: 1px solid transparent;
transition:
width 420ms cubic-bezier(0.22, 0.61, 0.36, 1),
margin 420ms cubic-bezier(0.22, 0.61, 0.36, 1),
padding 420ms cubic-bezier(0.22, 0.61, 0.36, 1),
border-radius 420ms cubic-bezier(0.22, 0.61, 0.36, 1),
background 260ms ease,
border-color 260ms ease,
box-shadow 260ms ease;
}
/* Off-screen host for the Liquid Glass `<filter>` defs (referenced by id
from the nav's backdrop-filter). Kept in flow with zero size rather than
display:none, since some engines drop filter references on hidden subtrees. */
.nav-glass-defs {
position: absolute;
width: 0;
height: 0;
overflow: hidden;
pointer-events: none;
}
/*
* Condensed bar — the Liquid Glass material. Two `backdrop-filter`
* declarations: the plain blur is the Safari/Firefox fallback (the
* component's SVG displacement filter is unsupported there); the
* `url(#nav-liquid-glass)` line is the Chromium chromatic-refraction
* material and overrides it where supported. The bar also shrinks its
* vertical padding for the condensed feel.
*/
.site-chrome.is-condensed .nav {
/* Detaches from the edges into a centered floating capsule: a gap from
the top, side margins, and fully rounded ends. 1280px (not 1080px) so
the longest translated nav rows (ja/ru/uk) still hold a single line —
the row must never wrap into a second line over the hero. */
margin: 14px auto 0;
width: min(1280px, calc(100% - 32px));
padding: 9px 0;
border-radius: 999px;
border: 1px solid rgba(21, 20, 15, 0.08);
background: hsl(0 0% 100% / 0.42);
-webkit-backdrop-filter: blur(12px) saturate(1.4);
backdrop-filter: blur(12px) saturate(1.4);
backdrop-filter: url(#nav-liquid-glass) blur(7px) saturate(1.4);
box-shadow:
inset 0 0 2px 1px rgba(255, 255, 255, 0.55),
inset 0 0 10px 4px rgba(255, 255, 255, 0.22),
0 6px 24px rgba(17, 17, 26, 0.06),
0 12px 40px rgba(17, 17, 26, 0.05);
}
/* The inner gutter eases in lockstep with the capsule width. */
.nav > .container {
transition: padding 420ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* Tighten the inner container padding inside the capsule (the full 64px
container gutter would over-inset the brand / links / actions). */
.site-chrome.is-condensed .nav > .container {
max-width: none;
padding: 0 28px;
}
.nav-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
}
/*
* Desktop bar layout (≥1367px, above the hamburger breakpoint): three
* equal-flanked columns — brand logo pinned flush-left, the nav links
* centred, and the action cluster (download / star / locale) flush-right.
* The two 1fr side tracks are symmetric, which keeps the centre column
* optically centred regardless of how wide either side gets. Mobile keeps
* the base flex layout untouched.
*/
@media (min-width: 1367px) {
.nav-inner {
display: grid;
grid-template-columns: 1fr auto 1fr;
}
.nav-inner > .brand {
order: 1;
justify-self: start;
}
.nav-inner > nav[data-nav-primary] {
order: 2;
justify-self: center;
}
.nav-inner > .nav-side {
order: 3;
justify-self: end;
}
/* Translated labels (ja ソリューション, ru Сообщество, …) run longer than
the English originals. A trigger must never break between its label and
the ▾ caret — wrapping stacks the bar into two lines that overlap the
hero. The bar widens / tightens instead (capsule cap + the band below). */
.nav-links a {
white-space: nowrap;
}
}
.brand {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: var(--sans);
font-weight: 700;
letter-spacing: -0.01em;
color: var(--ink);
text-decoration: none;
font-size: 18px;
}
/*
* Combined brand lockup — the glyph + "OpenDesign" wordmark as a single
* SVG (`/logo-lockup.svg`, viewBox 225×83). Sized by height so the glyph
* keeps roughly its previous 44px optical weight while the wordmark sets
* its own width. Replaces the old `.brand-mark` glyph + `.brand-name`
* text pair in the header and both footers.
*/
.brand-logo {
height: 40px;
width: auto;
display: block;
flex: 0 0 auto;
transition: height 420ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* Shrinks alongside the condensed bar (eased in lockstep with the
capsule's width/padding so nothing jumps ahead). */
.site-chrome.is-condensed .brand-logo {
height: 33px;
}
.brand-mark {
/*
* Bumped from 36px → 44px (≈22% larger). The new black brand mark
* needs the extra optical weight against the wordmark, especially
* given the speech-bubble negative space visually shrinks the glyph.
* (PR #2588's brand polish, restored on top of #2469.)
*/
width: 44px; height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
}
.brand-name {
white-space: nowrap;
}
.brand-mark img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
/* Round the corners of the brand glyph (~22% of side, app-icon
* silhouette convention) so the solid-fill square reads as a
* brand mark next to the wordmark rather than a screenshot. */
border-radius: 10px;
}
.brand-meta {
font-family: var(--sans);
font-size: 10px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--ink-faint);
line-height: 1.3;
margin-left: 4px;
border-left: 1px solid var(--line);
padding-left: 14px;
}
.brand-meta b { display: block; color: var(--ink); font-weight: 600; }
.nav-links {
display: flex;
gap: 38px;
list-style: none;
}
.nav-links a {
color: var(--ink);
text-decoration: none;
font-family: var(--sans);
font-size: 14px;
font-weight: 500;
transition: color 0.18s ease;
position: relative;
}
.nav-links a:hover { color: var(--coral); }
.nav-links a .num {
font-size: 9px;
color: var(--ink-faint);
position: absolute;
top: -7px;
right: -16px;
letter-spacing: 0.04em;
}
/* Product / Resources are category labels, not pages — they render as
disclosure buttons that toggle their own dropdown instead of navigating
(Product used to bounce to the homepage, Resources to /blog/). Style the
button to read identically to the sibling <a> nav links, then reset the
native button chrome. */
.nav-links .nav-trigger {
appearance: none;
-webkit-appearance: none;
background: none;
border: 0;
margin: 0;
padding: 0;
cursor: pointer;
color: var(--ink);
font-family: var(--sans);
font-size: 14px;
font-weight: 500;
transition: color 0.18s ease;
position: relative;
display: inline-flex;
align-items: center;
}
.nav-links .nav-trigger:hover { color: var(--coral); }
/*
* Product dropdown — CSS-only, no JS. The submenu is hidden by default and
* revealed on `:hover` of the parent `li.has-dropdown`, or on `:focus-within`
* for keyboard navigation. This keeps the static export self-contained.
*/
.nav-links li.has-dropdown {
position: relative;
--nav-flyout-gap: 12px;
}
/*
* Invisible hover bridge across the 38px flex gap between nav triggers so
* sweeping between items keeps something hovered — otherwise the dropdown and
* the :has() page blur flick off/on in the dead gap. Last item excluded.
*/
.nav-links li.has-dropdown:not(:last-child)::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 100%;
width: 38px;
}
.nav-links .dropdown-caret {
display: inline-flex;
align-items: center;
margin-left: 5px;
font-size: 11px;
line-height: 1;
/* nudge up a hair: the triangle glyph's ink sits low in its em box */
transform: translateY(-1px);
color: var(--ink-faint);
transition: transform 0.18s ease, color 0.18s ease;
}
.nav-links li.has-dropdown:hover .dropdown-caret,
.nav-links li.has-dropdown:focus-within .dropdown-caret {
/* keep the -1px optical baseline; nudge down 1px more as the open affordance */
transform: translateY(0);
color: var(--coral);
}
.nav-dropdown {
position: absolute;
top: calc(100% + var(--nav-flyout-gap, 12px));
left: -16px;
margin: 0;
padding: 10px;
list-style: none;
min-width: 320px;
background: var(--paper);
border: 1px solid rgba(26, 26, 26, 0.12);
border-radius: 8px;
box-shadow: 0 12px 36px rgba(26, 26, 26, 0.08);
opacity: 0;
visibility: hidden;
transform: translateY(-4px);
transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
z-index: 50;
}
.nav-links li.has-dropdown:hover .nav-dropdown,
.nav-links li.has-dropdown:focus-within .nav-dropdown {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
/*
* Frosted backdrop behind an open header menu. Opening a nav dropdown — or the
* language switcher — frosts the page (blueprint / glass look) while the nav
* bar and its menu stay sharp on top. Pure CSS via :has().
*
* Anchored to the viewport top (`top: 0`, full-viewport height) so it covers
* the WHOLE screen behind the chrome — including the transparent topbar strip
* above the nav pill, where page content used to bleed through while a menu was
* open. It lives in the chrome's stacking context (z-index 50 in .shell) at the
* back (z-index -1), so it covers page content while the topbar, nav links,
* dropdown, and locale menu all paint above it; the opaque condensed nav pill
* hides the frost behind itself, so the bar still reads as a clean bar.
* Desktop only: the mobile nav is a vertical drawer, not a hover panel.
*/
@media (min-width: 1367px) {
.site-chrome::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 100vh;
z-index: -1;
background: rgba(255, 255, 255, 0.62);
backdrop-filter: blur(26px) saturate(108%);
-webkit-backdrop-filter: blur(26px) saturate(108%);
opacity: 0;
pointer-events: none;
/* Delay the fade-OUT so a brief hover loss (crossing a gap) doesn't flash
the blur; opening stays instant via the override below. */
transition: opacity 220ms cubic-bezier(0.23, 1, 0.32, 1) 120ms;
}
.site-chrome:has(.nav-links li.has-dropdown:hover)::after,
.site-chrome:has(.nav-links li.has-dropdown:focus-within)::after,
.site-chrome:has(.locale-switch[open])::after {
opacity: 1;
transition-delay: 0ms;
}
}
/* invisible bridge so cursor can travel from trigger to menu without
* crossing a gap that closes the menu mid-traverse */
.nav-dropdown::before {
content: '';
position: absolute;
top: calc(-1 * var(--nav-flyout-gap, 12px));
left: 0;
right: 0;
height: var(--nav-flyout-gap, 12px);
}
.nav-dropdown a {
display: block;
padding: 10px 12px;
border-radius: 6px;
color: var(--ink);
text-decoration: none;
transition: background 0.12s ease;
}
.nav-dropdown a:hover {
background: var(--coral-tint);
color: var(--ink);
}
.nav-dropdown a.is-active {
background: rgba(26, 26, 26, 0.04);
}
.nav-dropdown .dropdown-name {
display: block;
font-family: var(--sans);
font-size: 14px;
font-weight: 600;
margin-bottom: 2px;
}
.nav-dropdown .dropdown-blurb {
display: block;
font-size: 12px;
color: var(--ink-faint);
line-height: 1.4;
}
/* Grouped dropdown (Solution): small-caps group labels between the rows.
Rules mirror upstream main's header CSS 1:1. */
.nav-dropdown .nav-dropdown-group {
padding: 8px 12px 2px;
}
.nav-dropdown .nav-dropdown-group:first-child {
padding-top: 4px;
}
.nav-dropdown .nav-dropdown-group-label {
display: block;
font-family: var(--sans);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-faint);
}
/* Solution panel lists 11 rows + 2 group labels; cap the height so it never
grows past the viewport, scrolling inside instead. */
.nav-dropdown-solution {
max-height: min(70vh, 520px);
overflow-y: auto;
}