@astronox/core 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +16 -1
  2. package/package.json +18 -4
  3. package/src/admin/authController.ts +166 -0
  4. package/src/admin/cache.ts +44 -17
  5. package/src/admin/index.ts +3 -0
  6. package/src/admin/preparedDestination.ts +114 -0
  7. package/src/admin/resourceClient.ts +107 -0
  8. package/src/ads/adsense/index.ts +1 -0
  9. package/src/ads/adsense.ts +112 -0
  10. package/src/ads/contracts.ts +11 -0
  11. package/src/ads/index.ts +2 -0
  12. package/src/ads/slot.ts +16 -0
  13. package/src/auth/contracts.ts +1 -4
  14. package/src/auth/firebase/client.ts +19 -6
  15. package/src/auth/firebase/index.ts +1 -0
  16. package/src/client/formSubmission.ts +6 -1
  17. package/src/client/preferences.ts +8 -4
  18. package/src/components/actions/Button/Button.astro +30 -42
  19. package/src/components/actions/Button/Button.css +1 -1
  20. package/src/components/actions/CardAction/CardAction.css +8 -13
  21. package/src/components/actions/IdentityProviderAction/IdentityProviderAction.astro +6 -8
  22. package/src/components/actions/MediaTrigger/MediaTrigger.astro +1 -1
  23. package/src/components/actions/ScrollToTop/ScrollToTop.astro +2 -1
  24. package/src/components/actions/ScrollToTop/ScrollToTop.css +2 -6
  25. package/src/components/actions/ThemeToggle/ThemeToggle.astro +59 -0
  26. package/src/components/actions/ThemeToggle/ThemeToggle.css +83 -0
  27. package/src/components/actions/ThemeToggle/themeToggleController.ts +110 -0
  28. package/src/components/content/CardFooter/CardFooter.css +2 -4
  29. package/src/components/content/CardHeader/CardHeader.astro +1 -4
  30. package/src/components/content/CardLink/CardLink.astro +3 -15
  31. package/src/components/content/CardLink/CardLink.css +4 -15
  32. package/src/components/content/CodeBlock/CodeBlock.astro +7 -6
  33. package/src/components/content/CodeBlock/CodeBlock.css +2 -5
  34. package/src/components/content/CodeBlock/codeBlockController.ts +1 -2
  35. package/src/components/content/MarkdownContent/MarkdownContent.astro +1 -3
  36. package/src/components/content/MarkdownContent/MarkdownContent.css +7 -2
  37. package/src/components/content/MarkdownContent/MarkdownInline.astro +11 -3
  38. package/src/components/content/MarkdownContent/MarkdownNode.astro +28 -33
  39. package/src/components/content/MarkdownContent/MarkdownSymbol.astro +30 -19
  40. package/src/components/content/MarkdownContent/contracts.ts +15 -15
  41. package/src/components/content/MarkdownContent/markdownContentRenderer.ts +27 -83
  42. package/src/components/feedback/PreferenceBootstrap/PreferenceBootstrap.astro +19 -22
  43. package/src/components/feedback/Spinner/Spinner.astro +1 -2
  44. package/src/components/feedback/ThemeBootstrap/ThemeBootstrap.astro +31 -0
  45. package/src/components/feedback/Tooltip/tooltipController.ts +1 -4
  46. package/src/components/forms/ChoiceGroup/ChoiceGroup.css +2 -5
  47. package/src/components/forms/DateTimeInput/DateTimeInput.astro +2 -1
  48. package/src/components/forms/PreparedFormBootstrap/PreparedFormBootstrap.astro +10 -8
  49. package/src/components/forms/PreparedFormBootstrap/parsePreparedFormState.ts +48 -0
  50. package/src/components/forms/Textarea/Textarea.astro +2 -2
  51. package/src/components/icons/BurnIcon.astro +2 -1
  52. package/src/components/icons/CircleSymbolIcon.css +3 -3
  53. package/src/components/icons/FieldIcon.astro +10 -4
  54. package/src/components/icons/GrassIcon.astro +3 -2
  55. package/src/components/icons/SlowIcon.astro +1 -2
  56. package/src/components/icons/StunIcon.astro +2 -1
  57. package/src/components/icons/WaterIcon.astro +3 -2
  58. package/src/components/index.ts +4 -0
  59. package/src/components/media/CanvasFrame/CanvasFrame.astro +1 -2
  60. package/src/components/media/CanvasFrame/canvasFrameController.ts +1 -4
  61. package/src/components/media/EmbedFrame/EmbedFrame.astro +5 -12
  62. package/src/components/media/EmbedFrame/embedFrameController.ts +4 -4
  63. package/src/components/monetization/AdSlot/AdSlot.astro +33 -0
  64. package/src/components/monetization/AdSlot/AdSlot.css +53 -0
  65. package/src/components/navigation/DesktopNavigation.css +5 -3
  66. package/src/components/navigation/Footer/Footer.css +3 -9
  67. package/src/components/navigation/Header.astro +20 -2
  68. package/src/components/navigation/MobileNavigation.astro +107 -43
  69. package/src/components/navigation/MobileNavigation.css +102 -26
  70. package/src/components/navigation/NavigationBrand.css +1 -1
  71. package/src/components/navigation/NavigationControl.css +16 -2
  72. package/src/components/navigation/NavigationItemContent.astro +6 -1
  73. package/src/components/navigation/mobileNavigationController.ts +0 -16
  74. package/src/components/navigation/navigationTypes.ts +6 -1
  75. package/src/components/seo/SeoHead.astro +1 -6
  76. package/src/composition/SurfaceGroup/SurfaceGroup.css +4 -14
  77. package/src/config/environment.ts +52 -0
  78. package/src/config/index.ts +3 -0
  79. package/src/config/integrations.ts +9 -0
  80. package/src/config/siteConfig.ts +5 -0
  81. package/src/config/storage.ts +16 -0
  82. package/src/content/admin.ts +354 -0
  83. package/src/content/cache.ts +177 -0
  84. package/src/content/catalog.ts +72 -0
  85. package/src/content/contracts.ts +77 -0
  86. package/src/content/drafts.ts +49 -0
  87. package/src/content/index.ts +7 -0
  88. package/src/content/markdown.ts +146 -0
  89. package/src/content/snapshot.ts +139 -0
  90. package/src/data/firestore/client/contracts.ts +10 -2
  91. package/src/data/index.ts +1 -0
  92. package/src/data/memoizedLoader.ts +13 -0
  93. package/src/display/contracts.ts +2 -5
  94. package/src/i18n/config.ts +2 -8
  95. package/src/i18n/coreMessages.ts +2 -9
  96. package/src/i18n/preference.ts +2 -6
  97. package/src/layouts/BaseLayout.astro +5 -0
  98. package/src/patterns/Combobox/Combobox.astro +9 -12
  99. package/src/patterns/Combobox/comboboxController.ts +14 -8
  100. package/src/patterns/Fullscreen/FullscreenControl.astro +5 -1
  101. package/src/patterns/Fullscreen/fullscreenController.ts +1 -3
  102. package/src/patterns/MapViewport/MapViewport.css +3 -3
  103. package/src/patterns/MapViewport/geometry.ts +1 -4
  104. package/src/patterns/Modal/Modal.css +1 -4
  105. package/src/patterns/Modal/ModalOpen.astro +5 -1
  106. package/src/patterns/Table/Table.astro +10 -10
  107. package/src/patterns/Table/TableBody.astro +1 -4
  108. package/src/patterns/Table/TableBody.css +2 -4
  109. package/src/patterns/Table/TablePagination.astro +23 -30
  110. package/src/patterns/Table/TableSkeleton.astro +1 -5
  111. package/src/patterns/Table/tableClientController.ts +2 -2
  112. package/src/patterns/Table/tableFilterController.ts +1 -3
  113. package/src/patterns/Table/tableHeaderOverflowController.ts +1 -4
  114. package/src/patterns/Table/tableProgressiveController.ts +62 -60
  115. package/src/patterns/Table/tableScrollController.ts +3 -2
  116. package/src/primitives/text/TextBlock/TextBlock.astro +1 -3
  117. package/src/publication/contracts.ts +11 -0
  118. package/src/publication/http.ts +31 -0
  119. package/src/publication/index.ts +2 -0
  120. package/src/routing/localized.ts +9 -12
  121. package/src/seo/localizedMeta.ts +2 -6
  122. package/src/styles/interaction.css +7 -7
  123. package/src/styles/style.css +12 -20
  124. package/src/styles/tokens.css +55 -15
  125. package/src/theme/contracts.ts +3 -6
  126. package/src/theme/index.ts +1 -0
  127. package/src/theme/storage.ts +1 -0
@@ -0,0 +1,33 @@
1
+ ---
2
+ import type { HTMLAttributes } from 'astro/types';
3
+ import type { AdSlotState } from '../../../ads/contracts';
4
+
5
+ type Props = Omit<HTMLAttributes<'aside'>, 'aria-label'> & {
6
+ label: string;
7
+ size?: 'compact' | 'standard' | 'tall';
8
+ state?: AdSlotState;
9
+ };
10
+
11
+ const {
12
+ class: className,
13
+ label,
14
+ size = 'standard',
15
+ state = 'idle',
16
+ ...attrs
17
+ }: Props = Astro.props;
18
+ ---
19
+
20
+ <aside
21
+ {...attrs}
22
+ class:list={['ad-slot', className]}
23
+ data-ad-size={size}
24
+ data-ad-state={state}
25
+ data-widget="ad-slot"
26
+ aria-label={label}
27
+ aria-busy={state === 'loading' ? 'true' : 'false'}
28
+ >
29
+ <div class="ad-slot__content"><slot /></div>
30
+ <div class="ad-slot__fallback">
31
+ <slot name="fallback"><div class="ad-slot__fallback-canvas"></div></slot>
32
+ </div>
33
+ </aside>
@@ -0,0 +1,53 @@
1
+ .ad-slot {
2
+ --ad-slot-min-block-size: 6rem;
3
+
4
+ display: grid;
5
+ inline-size: 100%;
6
+ min-block-size: var(--ad-slot-min-block-size);
7
+ overflow: hidden;
8
+ place-items: center;
9
+ }
10
+
11
+ .ad-slot[data-ad-size='compact'] {
12
+ --ad-slot-min-block-size: 4rem;
13
+ }
14
+
15
+ .ad-slot[data-ad-size='tall'] {
16
+ --ad-slot-min-block-size: 15rem;
17
+ }
18
+
19
+ .ad-slot__content,
20
+ .ad-slot__fallback {
21
+ grid-area: 1 / 1;
22
+ inline-size: 100%;
23
+ }
24
+
25
+ .ad-slot__fallback {
26
+ display: none;
27
+ block-size: 100%;
28
+ }
29
+
30
+ .ad-slot__fallback-canvas {
31
+ block-size: 100%;
32
+ min-block-size: var(--ad-slot-min-block-size);
33
+ background:
34
+ linear-gradient(
35
+ 135deg,
36
+ transparent 0 46%,
37
+ color-mix(in srgb, var(--nox-theme-border-base) 55%, transparent) 46% 54%,
38
+ transparent 54% 100%
39
+ ),
40
+ var(--nox-surface-muted);
41
+ border: 1px solid var(--nox-theme-border-base);
42
+ inline-size: 100%;
43
+ }
44
+
45
+ .ad-slot[data-ad-state='blocked'] .ad-slot__content,
46
+ .ad-slot[data-ad-state='unavailable'] .ad-slot__content {
47
+ display: none;
48
+ }
49
+
50
+ .ad-slot[data-ad-state='blocked'] .ad-slot__fallback,
51
+ .ad-slot[data-ad-state='unavailable'] .ad-slot__fallback {
52
+ display: block;
53
+ }
@@ -81,11 +81,13 @@
81
81
 
82
82
  .site-nav__action > * {
83
83
  min-width: 5.75rem;
84
- border-radius: var(--nox-radius-md);
85
84
  }
86
85
 
87
- .site-nav__action
88
- > .dropdown[data-dropdown-trigger-appearance='bar'] {
86
+ .site-nav__action > [data-theme-toggle-appearance='circle'] {
87
+ min-width: 4.75rem;
88
+ }
89
+
90
+ .site-nav__action > .dropdown[data-dropdown-trigger-appearance='bar'] {
89
91
  min-width: 0;
90
92
  block-size: 100%;
91
93
  border-radius: 0;
@@ -33,15 +33,11 @@
33
33
  column-gap: var(--nox-space-4);
34
34
  }
35
35
 
36
- .site-footer__content[data-footer-columns='2'][
37
- data-footer-column-separator='divider'
38
- ] {
36
+ .site-footer__content[data-footer-columns='2'][data-footer-column-separator='divider'] {
39
37
  column-gap: 0;
40
38
  }
41
39
 
42
- .site-footer__content[data-footer-columns='2'][
43
- data-footer-column-separator='divider'
44
- ]
40
+ .site-footer__content[data-footer-columns='2'][data-footer-column-separator='divider']
45
41
  > .site-footer__group:nth-child(even)::before {
46
42
  display: inline-grid;
47
43
  width: calc(
@@ -146,9 +142,7 @@
146
142
  grid-column: auto;
147
143
  }
148
144
 
149
- .site-footer__content[data-footer-columns='2'][
150
- data-footer-column-separator='divider'
151
- ]
145
+ .site-footer__content[data-footer-columns='2'][data-footer-column-separator='divider']
152
146
  > .site-footer__group:nth-child(even)::before {
153
147
  content: none;
154
148
  }
@@ -1,8 +1,14 @@
1
1
  ---
2
2
  import type { HTMLAttributes } from 'astro/types';
3
3
  import type { SiteIdentity } from '../../config/siteConfig';
4
- import type { MobileNavigationLabels } from './navigationTypes';
5
- import type { NavigationItem } from '../../display/contracts';
4
+ import type {
5
+ MobileNavigationControlItem,
6
+ MobileNavigationLabels,
7
+ } from './navigationTypes';
8
+ import type {
9
+ NavigationItem,
10
+ NavigationLinkItem,
11
+ } from '../../display/contracts';
6
12
  import type { Tone } from '../../theme/contracts';
7
13
  import DesktopNavigation from './DesktopNavigation.astro';
8
14
  import MobileNavigation from './MobileNavigation.astro';
@@ -15,6 +21,9 @@ type Props = HTMLAttributes<'header'> & {
15
21
  identity: SiteIdentity;
16
22
  leftItems?: readonly NavigationItem[];
17
23
  menu: MobileNavigationLabels;
24
+ mobileFooterItems?: readonly MobileNavigationControlItem[];
25
+ mobileItems?: readonly NavigationLinkItem[];
26
+ mobileUtilityItems?: readonly MobileNavigationControlItem[];
18
27
  rightItems?: readonly NavigationItem[];
19
28
  sticky?: boolean;
20
29
  tone?: Tone;
@@ -30,12 +39,16 @@ const {
30
39
  identity,
31
40
  leftItems = [],
32
41
  menu,
42
+ mobileFooterItems = [],
43
+ mobileItems,
44
+ mobileUtilityItems = [],
33
45
  rightItems = [],
34
46
  sticky = false,
35
47
  tone,
36
48
  ...attrs
37
49
  } = Astro.props;
38
50
  const resolvedTone = tone ?? dataTone;
51
+ const hasMobileUtilityActions = Astro.slots.has('mobile-utility-actions');
39
52
  ---
40
53
 
41
54
  <header
@@ -52,10 +65,15 @@ const resolvedTone = tone ?? dataTone;
52
65
  homeLabel={homeLabel}
53
66
  leftItems={leftItems}
54
67
  menu={menu}
68
+ mobileFooterItems={mobileFooterItems}
69
+ mobileItems={mobileItems}
70
+ mobileUtilityItems={mobileUtilityItems}
71
+ hasUtilityActions={hasMobileUtilityActions}
55
72
  rightItems={rightItems}
56
73
  currentPath={currentPath}
57
74
  >
58
75
  <slot name="mobile-actions" slot="actions" />
76
+ <slot name="mobile-utility-actions" slot="utility-actions" />
59
77
  </MobileNavigation>
60
78
 
61
79
  <DesktopNavigation
@@ -1,52 +1,56 @@
1
1
  ---
2
2
  import type { SiteIdentity } from '../../config/siteConfig';
3
- import type { NavigationItem } from '../../display/contracts';
4
- import type { MobileNavigationLabels } from './navigationTypes';
3
+ import type {
4
+ NavigationItem,
5
+ NavigationLinkItem,
6
+ } from '../../display/contracts';
7
+ import type {
8
+ MobileNavigationControlItem,
9
+ MobileNavigationLabels,
10
+ } from './navigationTypes';
11
+ import CardLink from '../content/CardLink/CardLink.astro';
5
12
  import NavigationBrand from './NavigationBrand.astro';
6
13
  import NavigationLink from './NavigationLink.astro';
7
- import { isNavigationGroupItem } from './navigationState';
14
+ import {
15
+ isNavigationGroupItem,
16
+ isNavigationItemActive,
17
+ } from './navigationState';
8
18
 
9
19
  type Props = {
10
20
  currentPath: string;
21
+ hasUtilityActions?: boolean;
11
22
  homeHref: string;
12
23
  homeLabel: string;
13
24
  identity: SiteIdentity;
14
25
  leftItems: readonly NavigationItem[];
15
26
  menu: MobileNavigationLabels;
27
+ mobileFooterItems: readonly MobileNavigationControlItem[];
28
+ mobileItems?: readonly NavigationLinkItem[];
29
+ mobileUtilityItems: readonly MobileNavigationControlItem[];
16
30
  rightItems: readonly NavigationItem[];
17
31
  };
18
32
 
19
- type MobileNavigationGroup = {
20
- items: readonly NavigationItem[];
21
- label?: string;
22
- };
23
-
24
33
  const {
25
34
  currentPath,
35
+ hasUtilityActions,
26
36
  homeHref,
27
37
  homeLabel,
28
38
  identity,
29
39
  leftItems,
30
40
  menu,
41
+ mobileFooterItems,
42
+ mobileItems,
43
+ mobileUtilityItems,
31
44
  rightItems,
32
45
  } = Astro.props;
33
46
  const panelId = 'site-nav-mobile-panel';
34
47
  const closeLabel = menu.closeLabel ?? menu.openLabel;
35
- const mobileGroups: MobileNavigationGroup[] = [
36
- {
37
- items: leftItems,
38
- },
39
- ...rightItems.map((item) =>
40
- isNavigationGroupItem(item)
41
- ? {
42
- label: item.label,
43
- items: item.items,
44
- }
45
- : {
46
- items: [item],
47
- },
48
- ),
49
- ];
48
+ const fallbackItems = [...leftItems, ...rightItems].flatMap((item) =>
49
+ isNavigationGroupItem(item) ? [...item.items] : [item],
50
+ );
51
+ const resolvedMobileItems = mobileItems ?? fallbackItems;
52
+ const resolvedHasUtilityActions =
53
+ hasUtilityActions ?? Astro.slots.has('utility-actions');
50
54
  ---
51
55
 
52
56
  <div class="site-nav__mobile" data-mobile-navigation>
@@ -88,28 +92,88 @@ const mobileGroups: MobileNavigationGroup[] = [
88
92
  >
89
93
  <ul class="site-nav__mobile-list">
90
94
  {
91
- mobileGroups.map((group) => (
92
- <>
93
- {group.label ? (
94
- <li class="site-nav__mobile-group-label">
95
- <span>{group.label}</span>
96
- </li>
97
- ) : null}
98
- {group.items.map((item) => (
99
- <li>
100
- <NavigationLink
101
- item={item}
102
- currentPath={currentPath}
103
- accent="start"
104
- appearance="menu"
105
- class="site-nav__mobile-link"
106
- />
107
- </li>
108
- ))}
109
- </>
110
- ))
95
+ resolvedMobileItems.map((item) => {
96
+ const active = isNavigationItemActive(item, currentPath);
97
+ const rel =
98
+ item.rel ??
99
+ (item.target === '_blank' ? 'noopener noreferrer' : undefined);
100
+
101
+ return (
102
+ <li>
103
+ <CardLink
104
+ class="site-nav__mobile-link"
105
+ disabled={item.disabled}
106
+ href={item.href}
107
+ media={item.media}
108
+ mediaSize="compact"
109
+ rel={rel}
110
+ target={item.target}
111
+ textTreatment="heading"
112
+ title={item.label}
113
+ aria-current={active ? 'page' : undefined}
114
+ />
115
+ </li>
116
+ );
117
+ })
111
118
  }
112
119
  </ul>
120
+
121
+ {
122
+ mobileUtilityItems.length > 0 ||
123
+ resolvedHasUtilityActions ||
124
+ mobileFooterItems.length > 0 ? (
125
+ <div class="site-nav__mobile-controls">
126
+ {mobileUtilityItems.length > 0 || resolvedHasUtilityActions ? (
127
+ <ul class="site-nav__mobile-utility">
128
+ {mobileUtilityItems.map((item) => (
129
+ <li>
130
+ <NavigationLink
131
+ item={item}
132
+ currentPath={currentPath}
133
+ appearance="menu"
134
+ class="site-nav__mobile-utility-link"
135
+ data-mobile-navigation-label-visibility={
136
+ item.labelVisibility
137
+ }
138
+ />
139
+ </li>
140
+ ))}
141
+ {resolvedHasUtilityActions ? (
142
+ <li>
143
+ <slot name="utility-actions" />
144
+ </li>
145
+ ) : null}
146
+ </ul>
147
+ ) : null}
148
+
149
+ {mobileFooterItems.length > 0 ? (
150
+ <ul class="site-nav__mobile-footer">
151
+ {mobileFooterItems.map((item) => (
152
+ <li>
153
+ <NavigationLink
154
+ item={item}
155
+ currentPath={currentPath}
156
+ appearance="menu"
157
+ class="site-nav__mobile-footer-link"
158
+ data-mobile-navigation-accent={
159
+ item.accentColor ? true : undefined
160
+ }
161
+ data-mobile-navigation-label-visibility={
162
+ item.labelVisibility
163
+ }
164
+ style={
165
+ item.accentColor
166
+ ? `--mobile-navigation-accent: ${item.accentColor};`
167
+ : undefined
168
+ }
169
+ />
170
+ </li>
171
+ ))}
172
+ </ul>
173
+ ) : null}
174
+ </div>
175
+ ) : null
176
+ }
113
177
  </div>
114
178
  </div>
115
179
 
@@ -4,14 +4,18 @@
4
4
  }
5
5
 
6
6
  .site-nav__mobile-bar {
7
+ --site-nav-mobile-side-size: 4.75rem;
8
+
7
9
  position: relative;
8
10
  display: grid;
9
- grid-template-columns: auto minmax(0, 1fr) auto;
11
+ grid-template-columns:
12
+ var(--site-nav-mobile-side-size)
13
+ minmax(0, 1fr)
14
+ var(--site-nav-mobile-side-size);
10
15
  align-items: center;
11
- width: var(--nox-page-width);
16
+ width: 100%;
12
17
  min-height: var(--nox-site-header-compact-height);
13
- margin: 0 auto;
14
- gap: clamp(var(--nox-space-1), 2vw, var(--nox-space-3));
18
+ gap: 0;
15
19
  }
16
20
 
17
21
  .site-nav__menu-button {
@@ -29,6 +33,7 @@
29
33
  background: var(--nox-surface);
30
34
  color: var(--nox-text);
31
35
  cursor: pointer;
36
+ justify-self: center;
32
37
  }
33
38
 
34
39
  .site-nav__menu-icon {
@@ -72,12 +77,16 @@
72
77
 
73
78
  .site-nav__brand--mobile {
74
79
  z-index: 1;
75
- justify-self: stretch;
80
+ padding-inline: var(--nox-page-gutter);
81
+ justify-self: center;
76
82
  }
77
83
 
78
84
  .site-nav__mobile-action {
79
85
  z-index: 2;
80
- justify-self: end;
86
+ display: flex;
87
+ width: 100%;
88
+ justify-content: center;
89
+ justify-self: center;
81
90
  }
82
91
 
83
92
  .site-nav__mobile-action > * {
@@ -85,8 +94,7 @@
85
94
  border-radius: var(--nox-radius-md);
86
95
  }
87
96
 
88
- .site-nav__mobile-action
89
- > .dropdown[data-dropdown-trigger-appearance='bar'] {
97
+ .site-nav__mobile-action > .dropdown[data-dropdown-trigger-appearance='bar'] {
90
98
  min-width: 0;
91
99
  block-size: var(--nox-site-header-compact-height);
92
100
  border-radius: 0;
@@ -101,13 +109,15 @@
101
109
  .site-nav__mobile-panel {
102
110
  position: absolute;
103
111
  z-index: 2;
112
+ display: grid;
104
113
  inset-inline: 0;
105
114
  top: 100%;
106
115
  width: 100%;
107
- max-height: calc(100vh - var(--nox-site-header-compact-height));
108
- max-height: calc(100dvh - var(--nox-site-header-compact-height));
116
+ height: calc(100vh - var(--nox-site-header-compact-height));
117
+ height: calc(100dvh - var(--nox-site-header-compact-height));
118
+ grid-template-rows: minmax(0, 1fr) auto;
109
119
  overflow-x: hidden;
110
- overflow-y: auto;
120
+ overflow-y: hidden;
111
121
  padding: var(--nox-space-3);
112
122
  border-top: 3px solid var(--nox-link-active);
113
123
  border-bottom: var(--nox-border-width) solid var(--nox-border);
@@ -125,30 +135,90 @@
125
135
  .site-nav__mobile-list {
126
136
  display: grid;
127
137
  width: 100%;
138
+ min-height: 0;
128
139
  margin: 0;
129
140
  padding: 0;
130
- gap: var(--nox-space-1);
141
+ align-content: start;
142
+ gap: var(--nox-space-3);
131
143
  list-style: none;
144
+ overflow-x: hidden;
145
+ overflow-y: auto;
132
146
  }
133
147
 
134
148
  .site-nav__mobile-link {
135
- min-height: 3.125rem;
136
- padding-block: 0;
137
- border-radius: var(--nox-radius-sm);
149
+ --surface-radius: var(--nox-radius-md);
138
150
  }
139
151
 
140
- .site-nav__mobile-group-label {
141
- display: flex;
142
- min-height: 1.5rem;
143
- align-items: flex-end;
152
+ .site-nav__mobile-link[aria-current='page'] .text-pair__primary {
153
+ position: relative;
154
+ }
155
+
156
+ .site-nav__mobile-link[aria-current='page'] .text-pair__primary::after {
157
+ position: absolute;
158
+ right: 0;
159
+ bottom: calc(0px - var(--nox-space-2));
160
+ left: 0;
161
+ height: 2px;
162
+ border-radius: var(--nox-radius-pill);
163
+ background: var(--nox-source-tone-success);
164
+ content: '';
165
+ }
166
+
167
+ .site-nav__mobile-controls {
168
+ display: grid;
144
169
  margin-top: var(--nox-space-2);
145
- padding: var(--nox-space-2) var(--nox-space-4) 0;
146
- color: var(--nox-text-subtle);
147
- font-size: var(--nox-font-size-xs);
148
- font-weight: var(--nox-font-weight-strong);
149
- letter-spacing: var(--nox-letter-spacing-wider);
150
- line-height: 1;
151
- text-transform: uppercase;
170
+ padding: var(--nox-space-2) 0 0;
171
+ gap: var(--nox-space-3);
172
+ }
173
+
174
+ .site-nav__mobile-utility,
175
+ .site-nav__mobile-footer {
176
+ display: grid;
177
+ margin: 0;
178
+ padding: 0;
179
+ list-style: none;
180
+ }
181
+
182
+ .site-nav__mobile-utility {
183
+ grid-template-columns: repeat(2, minmax(0, 1fr));
184
+ gap: var(--nox-space-3);
185
+ }
186
+
187
+ .site-nav__mobile-footer {
188
+ grid-template-columns: repeat(3, minmax(0, 1fr));
189
+ gap: var(--nox-space-3);
190
+ }
191
+
192
+ .site-nav__mobile-utility-link,
193
+ .site-nav__mobile-footer-link {
194
+ min-height: var(--nox-navigation-block-size);
195
+ justify-content: center;
196
+ padding: 0;
197
+ border: var(--nox-border-width) solid var(--nox-border);
198
+ border-radius: var(--nox-radius-md);
199
+ background: var(--nox-surface-muted);
200
+ }
201
+
202
+ .site-nav__mobile-footer-link[data-mobile-navigation-accent] {
203
+ border-color: var(--mobile-navigation-accent);
204
+ background: var(--mobile-navigation-accent);
205
+ color: #fff;
206
+ }
207
+
208
+ [data-mobile-navigation-label-visibility='accessible']
209
+ [data-navigation-control-label] {
210
+ position: absolute;
211
+ overflow: hidden;
212
+ width: 1px;
213
+ height: 1px;
214
+ clip: rect(0 0 0 0);
215
+ white-space: nowrap;
216
+ }
217
+
218
+ .site-nav__mobile-footer-link [data-navigation-control-media] {
219
+ inline-size: 1.5rem;
220
+ block-size: 1.5rem;
221
+ border-radius: 0;
152
222
  }
153
223
 
154
224
  html[data-mobile-navigation-open],
@@ -166,6 +236,12 @@ html[data-mobile-navigation-open] body {
166
236
  .site-nav__menu-button:hover {
167
237
  background: var(--nox-surface-muted);
168
238
  }
239
+
240
+ .site-nav__mobile-footer-link[data-mobile-navigation-accent]:hover {
241
+ border-color: color-mix(in srgb, var(--mobile-navigation-accent) 88%, #000);
242
+ background: color-mix(in srgb, var(--mobile-navigation-accent) 88%, #000);
243
+ color: #fff;
244
+ }
169
245
  }
170
246
 
171
247
  @media (prefers-reduced-motion: no-preference) {
@@ -33,7 +33,7 @@
33
33
  .site-nav__brand--mobile {
34
34
  --site-nav-brand-block: var(--nox-site-header-compact-height);
35
35
  --site-nav-brand-inline: 100%;
36
- --site-nav-brand-image-scale: 56%;
36
+ --site-nav-brand-image-scale: 64%;
37
37
  }
38
38
 
39
39
  .site-nav__brand-name {
@@ -39,6 +39,20 @@
39
39
  block-size: 100%;
40
40
  }
41
41
 
42
+ [data-navigation-control-icon] {
43
+ display: grid;
44
+ inline-size: 1.5rem;
45
+ block-size: 1.5rem;
46
+ flex: 0 0 auto;
47
+ place-items: center;
48
+ }
49
+
50
+ [data-navigation-control-icon] > svg {
51
+ display: block;
52
+ inline-size: 100%;
53
+ block-size: 100%;
54
+ }
55
+
42
56
  [data-navigation-control-media-frame='bordered'] {
43
57
  border: var(--nox-border-width) solid var(--nox-border);
44
58
  }
@@ -92,11 +106,11 @@
92
106
  [data-navigation-control][data-navigation-control-appearance='bar']::after {
93
107
  position: absolute;
94
108
  right: var(--nox-space-4);
95
- bottom: var(--nox-space-2);
109
+ bottom: var(--nox-space-4);
96
110
  left: var(--nox-space-4);
97
111
  height: 3px;
98
112
  border-radius: var(--nox-radius-pill);
99
- background: var(--nox-link-active);
113
+ background: var(--nox-source-tone-success);
100
114
  content: '';
101
115
  opacity: 0;
102
116
  transform: translateY(var(--nox-space-1));
@@ -7,10 +7,15 @@ type Props = {
7
7
  };
8
8
 
9
9
  const { item } = Astro.props;
10
+ const Icon = item.icon;
10
11
  ---
11
12
 
12
13
  {
13
- item.media ? (
14
+ Icon ? (
15
+ <span data-navigation-control-icon aria-hidden="true">
16
+ <Icon />
17
+ </span>
18
+ ) : item.media ? (
14
19
  <span
15
20
  data-navigation-control-media
16
21
  data-navigation-control-media-frame={item.mediaFrame}
@@ -82,20 +82,6 @@ export function initMobileNavigation(root: HTMLElement): () => void {
82
82
  }
83
83
  };
84
84
 
85
- const handlePanelClick = (event: MouseEvent): void => {
86
- const target = event.target;
87
-
88
- if (!(target instanceof Element)) {
89
- return;
90
- }
91
-
92
- const link = target.closest('a[href]');
93
-
94
- if (link && panel.contains(link)) {
95
- close(true);
96
- }
97
- };
98
-
99
85
  const handleDesktopMediaChange = (): void => {
100
86
  if (desktopMedia.matches) {
101
87
  close(false);
@@ -105,7 +91,6 @@ export function initMobileNavigation(root: HTMLElement): () => void {
105
91
  setOpen(isOpen);
106
92
 
107
93
  trigger.addEventListener('click', handleTriggerClick);
108
- panel.addEventListener('click', handlePanelClick);
109
94
  document.addEventListener('click', handleDocumentClick);
110
95
  document.addEventListener('keydown', handleDocumentKeydown);
111
96
  desktopMedia.addEventListener('change', handleDesktopMediaChange);
@@ -113,7 +98,6 @@ export function initMobileNavigation(root: HTMLElement): () => void {
113
98
  return () => {
114
99
  setOpen(false);
115
100
  trigger.removeEventListener('click', handleTriggerClick);
116
- panel.removeEventListener('click', handlePanelClick);
117
101
  document.removeEventListener('click', handleDocumentClick);
118
102
  document.removeEventListener('keydown', handleDocumentKeydown);
119
103
  desktopMedia.removeEventListener('change', handleDesktopMediaChange);