@atelic-action/ui 0.17.2 → 1.0.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 (184) hide show
  1. package/README.md +428 -44
  2. package/bin/atelic-check-base-path.ts +5 -0
  3. package/bin/atelic-clean-sitemap.ts +5 -0
  4. package/bin/atelic-contact-canary.ts +5 -0
  5. package/bin/atelic-print-sheets.sh +1 -1
  6. package/bin/atelic-seal-document.mjs +1 -1
  7. package/bin/atelic-serve-static.ts +1 -1
  8. package/dist/gate/index.d.ts +1 -1
  9. package/dist/gate/index.js +1 -1
  10. package/dist/gate/middleware.d.ts +4 -1
  11. package/dist/gate/middleware.js +14 -5
  12. package/dist/gate/wall.d.ts +25 -0
  13. package/dist/gate/wall.js +41 -2
  14. package/dist/routing/basePath.d.ts +24 -0
  15. package/dist/routing/basePath.js +54 -0
  16. package/dist/vite/index.d.ts +3 -2
  17. package/dist/vite/index.js +3 -2
  18. package/dist/vite/marketing-config.d.ts +50 -0
  19. package/dist/vite/marketing-config.js +83 -0
  20. package/package.json +164 -5
  21. package/src/app/AppProvider.tsx +41 -0
  22. package/src/app/AuthGuard.tsx +27 -0
  23. package/src/app/CardErrorBoundary.tsx +39 -0
  24. package/src/app/ErrorFallback.tsx +39 -0
  25. package/src/app/GoogleGlyph.tsx +22 -0
  26. package/src/app/config.ts +54 -0
  27. package/src/app/configure.ts +18 -0
  28. package/src/app/context/AppContext.tsx +52 -0
  29. package/src/app/context/SidebarContext.tsx +124 -0
  30. package/src/app/context/UserContext.tsx +139 -0
  31. package/src/app/hooks/useDebounce.ts +12 -0
  32. package/src/app/hooks/useIsDesktop.ts +31 -0
  33. package/src/app/hooks/useWakeLock.ts +49 -0
  34. package/src/app/index.ts +64 -0
  35. package/src/app/layout/ActionButton.tsx +67 -0
  36. package/src/app/layout/AppLayout.tsx +110 -0
  37. package/src/app/layout/AppPage.tsx +268 -0
  38. package/src/app/layout/Breadcrumbs.tsx +57 -0
  39. package/src/app/layout/DesktopRail.tsx +168 -0
  40. package/src/app/layout/FormActionButtons.tsx +69 -0
  41. package/src/app/layout/MobileMenu.tsx +156 -0
  42. package/src/app/layout/MobileTabBar.tsx +43 -0
  43. package/src/app/layout/PageError.tsx +30 -0
  44. package/src/app/layout/PageSkeleton.tsx +65 -0
  45. package/src/app/layout/SectionCards.tsx +33 -0
  46. package/src/app/layout/UserMenu.tsx +104 -0
  47. package/src/app/layout/identity.ts +22 -0
  48. package/src/app/layout/index.ts +20 -0
  49. package/src/app/layout/nav.ts +52 -0
  50. package/src/app/lib/dates.ts +57 -0
  51. package/src/app/lib/env.ts +37 -0
  52. package/src/app/lib/format.ts +61 -0
  53. package/src/app/lib/http.ts +205 -0
  54. package/src/app/lib/lastAuth.ts +45 -0
  55. package/src/app/lib/oauth.ts +32 -0
  56. package/src/app/lib/publicRoutes.ts +33 -0
  57. package/src/app/lib/queryClient.ts +38 -0
  58. package/src/app/lib/search.ts +17 -0
  59. package/src/app/lib/session.ts +36 -0
  60. package/src/app/lib/tokenRefresh.ts +137 -0
  61. package/src/app/lib/transformKeys.ts +60 -0
  62. package/src/app/lib/units.ts +228 -0
  63. package/src/app/lib/unwrap.ts +48 -0
  64. package/src/app/lib/utils.ts +10 -0
  65. package/src/app/stores/authStore.ts +80 -0
  66. package/src/app/stores/storage.ts +40 -0
  67. package/src/app/stores/unitsStore.ts +54 -0
  68. package/src/app/types/index.ts +72 -0
  69. package/src/app/types/query.ts +11 -0
  70. package/src/app/ui/accordion.tsx +54 -0
  71. package/src/app/ui/alert-dialog.tsx +114 -0
  72. package/src/app/ui/alert.tsx +49 -0
  73. package/src/app/ui/avatar.tsx +45 -0
  74. package/src/app/ui/badge-list.tsx +51 -0
  75. package/src/app/ui/badge.tsx +33 -0
  76. package/src/app/ui/button.tsx +49 -0
  77. package/src/app/ui/callout.tsx +22 -0
  78. package/src/app/ui/card.tsx +55 -0
  79. package/src/app/ui/checkbox.tsx +26 -0
  80. package/src/app/ui/clear-button.tsx +42 -0
  81. package/src/app/ui/command.tsx +140 -0
  82. package/src/app/ui/country-dropdown.tsx +184 -0
  83. package/src/app/ui/dialog.tsx +104 -0
  84. package/src/app/ui/dropdown-menu.tsx +185 -0
  85. package/src/app/ui/form.tsx +172 -0
  86. package/src/app/ui/index.ts +36 -0
  87. package/src/app/ui/input.tsx +22 -0
  88. package/src/app/ui/label.tsx +19 -0
  89. package/src/app/ui/option-select.tsx +56 -0
  90. package/src/app/ui/pagination.tsx +90 -0
  91. package/src/app/ui/popover.tsx +29 -0
  92. package/src/app/ui/progress.tsx +25 -0
  93. package/src/app/ui/search-input.tsx +63 -0
  94. package/src/app/ui/select.tsx +151 -0
  95. package/src/app/ui/separator.tsx +24 -0
  96. package/src/app/ui/sheet.tsx +119 -0
  97. package/src/app/ui/slider.tsx +23 -0
  98. package/src/app/ui/switch.tsx +27 -0
  99. package/src/app/ui/textarea.tsx +21 -0
  100. package/src/app/ui/tooltip.tsx +32 -0
  101. package/src/artifact/ArtifactShell.tsx +2 -2
  102. package/src/artifact/Gate.tsx +2 -2
  103. package/src/artifact/Writeup.tsx +7 -7
  104. package/src/chrome/SiteShell.tsx +141 -0
  105. package/src/chrome/index.ts +2 -0
  106. package/src/components/Disclosure.tsx +80 -14
  107. package/src/components/GateLogin.tsx +1 -1
  108. package/src/components/Watermark.tsx +1 -1
  109. package/src/components/index.ts +6 -2
  110. package/src/gate/index.ts +1 -1
  111. package/src/gate/middleware.ts +17 -6
  112. package/src/gate/wall.ts +60 -2
  113. package/src/lib/basePathAudit.ts +96 -0
  114. package/src/lib/displayDate.ts +25 -0
  115. package/src/lib/honeypot.ts +11 -0
  116. package/src/lib/hours.ts +28 -0
  117. package/src/lib/sitemap.ts +34 -0
  118. package/src/lib/trackEvent.ts +26 -0
  119. package/src/lib/useOpenHeight.ts +28 -0
  120. package/src/primitives/Button.tsx +3 -1
  121. package/src/primitives/Chip.tsx +3 -1
  122. package/src/primitives/ImageFrame.tsx +48 -0
  123. package/src/primitives/ProseBlocks.tsx +67 -0
  124. package/src/primitives/QuoteCard.tsx +20 -0
  125. package/src/primitives/StarRow.tsx +37 -0
  126. package/src/primitives/TickList.tsx +24 -0
  127. package/src/primitives/index.ts +5 -0
  128. package/src/routing/BasePathProvider.tsx +47 -0
  129. package/src/routing/basePath.ts +57 -0
  130. package/src/routing/index.ts +8 -0
  131. package/src/scripts/check-base-path.ts +130 -0
  132. package/src/scripts/clean-sitemap.ts +27 -0
  133. package/src/scripts/contact-canary.ts +98 -0
  134. package/src/scripts/serve-static.ts +29 -3
  135. package/src/sections/BlogIndex.tsx +57 -0
  136. package/src/sections/BlogPost.tsx +65 -0
  137. package/src/sections/CTABanner.tsx +93 -0
  138. package/src/sections/CTASection.tsx +60 -0
  139. package/src/sections/ComparisonTable.tsx +114 -0
  140. package/src/sections/ContactForm.tsx +163 -0
  141. package/src/sections/ContactInfoCard.tsx +92 -0
  142. package/src/sections/CredentialList.tsx +28 -0
  143. package/src/sections/DeviceShowcase.tsx +39 -0
  144. package/src/sections/ExpandList.tsx +58 -0
  145. package/src/sections/FAQBrowser.tsx +283 -0
  146. package/src/sections/FeatureDetail.tsx +60 -0
  147. package/src/sections/FeatureGrid.tsx +83 -0
  148. package/src/sections/Gallery.tsx +84 -0
  149. package/src/sections/Hero.tsx +110 -0
  150. package/src/sections/HoursTable.tsx +23 -0
  151. package/src/sections/JumpRail.tsx +35 -0
  152. package/src/sections/LegalPage.tsx +43 -0
  153. package/src/sections/LocationSection.tsx +66 -0
  154. package/src/sections/Locations.tsx +71 -0
  155. package/src/sections/LogoMarquee.tsx +27 -0
  156. package/src/sections/MapEmbed.tsx +75 -0
  157. package/src/sections/MediaBand.tsx +20 -0
  158. package/src/sections/MenuList.tsx +44 -0
  159. package/src/sections/PageHero.tsx +25 -0
  160. package/src/sections/PressQuotes.tsx +51 -0
  161. package/src/sections/PricingBand.tsx +43 -0
  162. package/src/sections/PricingCard.tsx +36 -0
  163. package/src/sections/ProcessSteps.tsx +45 -0
  164. package/src/sections/ProfileCards.tsx +73 -0
  165. package/src/sections/RevealSlider.tsx +156 -0
  166. package/src/sections/ReviewCarousel.tsx +223 -0
  167. package/src/sections/ReviewWall.tsx +220 -0
  168. package/src/sections/SocialStrip.tsx +83 -0
  169. package/src/sections/SplitSection.tsx +56 -0
  170. package/src/sections/index.ts +76 -0
  171. package/src/sections/siteConfig.ts +201 -0
  172. package/src/styles/app.css +673 -0
  173. package/src/styles/artifact.css +472 -399
  174. package/src/styles/base.css +49 -47
  175. package/src/styles/chrome.css +71 -71
  176. package/src/styles/components.css +150 -140
  177. package/src/styles/gate.css +29 -29
  178. package/src/styles/primitives.css +190 -25
  179. package/src/styles/sections.css +2652 -0
  180. package/src/styles/star-rating.css +2 -2
  181. package/src/vite/index.ts +9 -2
  182. package/src/vite/marketing-config.ts +128 -0
  183. package/src/components/Collapsible.tsx +0 -43
  184. /package/src/scripts/{build-gate-payload.mjs → seal-document.mjs} +0 -0
@@ -1,6 +1,8 @@
1
1
  /* =================================================================
2
2
  @atelic-action/ui BASE: the few unscoped resets, then the canvas,
3
- typography, and layout helpers, scoped to .mkt.
3
+ typography, and layout helpers, scoped to .atelic-ui. The class is the
4
+ one root a consumer wears; what it gets is decided by the sheets it
5
+ imports, so an app that imports only components.css gets none of this.
4
6
  Tokens are read here and never defined: each site's theme.css owns
5
7
  them. Everything sits in the atelic-ui layer, so any rule a site
6
8
  writes outside a layer wins without a specificity fight.
@@ -29,12 +31,12 @@
29
31
 
30
32
  /* ---- Page canvas ---- */
31
33
  /* Horizontal overflow is clipped at the body (where it still propagates to
32
- the viewport) rather than on .mkt itself: an overflow-x on .mkt would
33
- silently break position: sticky for every descendant. */
34
- body:has(.mkt) {
34
+ the viewport) rather than on .atelic-ui itself: an overflow-x there
35
+ would silently break position: sticky for every descendant. */
36
+ body:has(.atelic-ui) {
35
37
  overflow-x: hidden;
36
38
  }
37
- .mkt {
39
+ .atelic-ui {
38
40
  font-family: var(--font-sans);
39
41
  background: var(--surface);
40
42
  color: var(--ink);
@@ -43,35 +45,35 @@
43
45
  -webkit-font-smoothing: antialiased;
44
46
  text-rendering: optimizeLegibility;
45
47
  }
46
- .mkt a {
48
+ .atelic-ui a {
47
49
  color: inherit;
48
50
  text-decoration: none;
49
51
  }
50
- .mkt button {
52
+ .atelic-ui button {
51
53
  font: inherit;
52
54
  cursor: pointer;
53
55
  border: none;
54
56
  background: none;
55
57
  color: inherit;
56
58
  }
57
- .mkt ul {
59
+ .atelic-ui ul {
58
60
  list-style: none;
59
61
  padding: 0;
60
62
  margin: 0;
61
63
  }
62
- .mkt img,
63
- .mkt picture,
64
- .mkt svg,
65
- .mkt video {
64
+ .atelic-ui img,
65
+ .atelic-ui picture,
66
+ .atelic-ui svg,
67
+ .atelic-ui video {
66
68
  display: block;
67
69
  max-width: 100%;
68
70
  }
69
71
 
70
72
  /* ---- Typography ---- */
71
- .mkt h1,
72
- .mkt h2,
73
- .mkt h3,
74
- .mkt h4 {
73
+ .atelic-ui h1,
74
+ .atelic-ui h2,
75
+ .atelic-ui h3,
76
+ .atelic-ui h4 {
75
77
  /* Sites pairing a display face with a body face set --font-heading in
76
78
  their theme; without it, headings share the body family. */
77
79
  font-family: var(--font-heading, var(--font-sans));
@@ -81,27 +83,27 @@
81
83
  text-wrap: balance;
82
84
  margin: 0;
83
85
  }
84
- .mkt h1 {
86
+ .atelic-ui h1 {
85
87
  font-size: clamp(2.6rem, 8vw, 5rem);
86
88
  }
87
- .mkt h2 {
89
+ .atelic-ui h2 {
88
90
  font-size: clamp(2rem, 5.5vw, 3.4rem);
89
91
  }
90
- .mkt h3 {
92
+ .atelic-ui h3 {
91
93
  font-size: clamp(1.35rem, 3vw, 1.85rem);
92
94
  letter-spacing: -0.015em;
93
95
  }
94
- .mkt h4 {
96
+ .atelic-ui h4 {
95
97
  font-size: 1.15rem;
96
98
  font-weight: 700;
97
99
  letter-spacing: -0.01em;
98
100
  }
99
- .mkt p {
101
+ .atelic-ui p {
100
102
  text-wrap: pretty;
101
103
  margin: 0;
102
104
  }
103
- .mkt em.serif,
104
- .mkt .serif-em {
105
+ .atelic-ui em.serif,
106
+ .atelic-ui .serif-em {
105
107
  font-family: var(--font-serif);
106
108
  font-style: italic;
107
109
  font-weight: 400;
@@ -111,93 +113,93 @@
111
113
  /* Serif accent words in headings carry the brand accent color (the
112
114
  wordmark's signature), so the italic word reads as the highlight it is.
113
115
  A page on a dark surface lifts its own to the bright primary. */
114
- .mkt h1 .serif,
115
- .mkt h2 .serif {
116
+ .atelic-ui h1 .serif,
117
+ .atelic-ui h2 .serif {
116
118
  color: var(--primary);
117
119
  }
118
120
 
119
121
  /* ---- Layout helpers ---- */
120
- .mkt .wrap {
122
+ .atelic-ui .wrap {
121
123
  width: 100%;
122
124
  max-width: var(--max-width);
123
125
  margin-inline: auto;
124
126
  padding-inline: var(--gutter);
125
127
  }
126
- .mkt .section {
128
+ .atelic-ui .section {
127
129
  padding-block: clamp(56px, 9vw, 120px);
128
130
  }
129
131
  /* A middle step, for a page whose alternating bands do the separating and
130
132
  whose default rhythm reads too airy as a result. */
131
- .mkt .section-md {
133
+ .atelic-ui .section-md {
132
134
  padding-block: clamp(44px, 6.5vw, 84px);
133
135
  }
134
- .mkt .section-sm {
136
+ .atelic-ui .section-sm {
135
137
  padding-block: clamp(40px, 6vw, 72px);
136
138
  }
137
139
  /* Section band modifiers, global because routes across chunks use them. */
138
- .mkt .section.surface-alt {
140
+ .atelic-ui .section.surface-alt {
139
141
  background: var(--surface-alt);
140
142
  }
141
- .mkt .section.tight-top {
143
+ .atelic-ui .section.tight-top {
142
144
  padding-top: clamp(20px, 3vw, 40px);
143
145
  }
144
- .mkt .section.tight-bottom {
146
+ .atelic-ui .section.tight-bottom {
145
147
  padding-bottom: clamp(20px, 3vw, 40px);
146
148
  }
147
- .mkt .stack-sm > * + * {
149
+ .atelic-ui .stack-sm > * + * {
148
150
  margin-top: 0.75rem;
149
151
  }
150
- .mkt .stack > * + * {
152
+ .atelic-ui .stack > * + * {
151
153
  margin-top: 1.25rem;
152
154
  }
153
- .mkt .center {
155
+ .atelic-ui .center {
154
156
  text-align: center;
155
157
  }
156
- .mkt .muted {
158
+ .atelic-ui .muted {
157
159
  color: var(--neutral-600);
158
160
  }
159
- .mkt .nowrap {
161
+ .atelic-ui .nowrap {
160
162
  white-space: nowrap;
161
163
  }
162
164
 
163
165
  /* ---- CTA row (pairs of buttons under headlines) ---- */
164
- .mkt .cta-row {
166
+ .atelic-ui .cta-row {
165
167
  display: flex;
166
168
  flex-wrap: wrap;
167
169
  gap: 14px;
168
170
  margin-top: 32px;
169
171
  }
170
- .mkt .cta-row.center {
172
+ .atelic-ui .cta-row.center {
171
173
  justify-content: center;
172
174
  }
173
175
 
174
176
  /* ---- Chip row ---- */
175
- .mkt .chip-row {
177
+ .atelic-ui .chip-row {
176
178
  display: flex;
177
179
  flex-wrap: wrap;
178
180
  gap: 10px;
179
181
  }
180
182
 
181
183
  /* ---- Prose (long form copy) ---- */
182
- .mkt .prose {
184
+ .atelic-ui .prose {
183
185
  max-width: 68ch;
184
186
  }
185
- .mkt .prose p {
187
+ .atelic-ui .prose p {
186
188
  margin-bottom: 1.1rem;
187
189
  color: var(--ink-soft);
188
190
  font-size: 1.08rem;
189
191
  line-height: 1.7;
190
192
  }
191
- .mkt .prose h3 {
193
+ .atelic-ui .prose h3 {
192
194
  margin: 2.2rem 0 0.8rem;
193
195
  }
194
- .mkt .prose strong {
196
+ .atelic-ui .prose strong {
195
197
  color: var(--ink);
196
198
  }
197
199
 
198
200
  /* Anchored sections sit under the fixed header; give every id an offset
199
201
  so jump links do not land beneath it. */
200
- .mkt [id] {
202
+ .atelic-ui [id] {
201
203
  scroll-margin-top: calc(var(--nav-height) + 16px);
202
204
  }
203
205
 
@@ -209,12 +211,12 @@
209
211
  whole ask was a link nobody could see. Scoped to paragraph links so a button
210
212
  dropped into a prose block is untouched, and placed last so it reads after
211
213
  the lighter anchor rules above. */
212
- .mkt .prose p a:not(.btn) {
214
+ .atelic-ui .prose p a:not(.btn) {
213
215
  color: var(--ink);
214
216
  border-bottom: 1px solid var(--neutral-300);
215
217
  padding-bottom: 1px;
216
218
  }
217
- .mkt .prose p a:not(.btn):hover {
219
+ .atelic-ui .prose p a:not(.btn):hover {
218
220
  color: var(--primary);
219
221
  border-color: var(--primary);
220
222
  }
@@ -7,7 +7,7 @@
7
7
  ================================================================= */
8
8
  @layer atelic-ui {
9
9
  /* ---- Skip link (a11y) ---- */
10
- .mkt .skip-link {
10
+ .atelic-ui .skip-link {
11
11
  position: absolute;
12
12
  left: -9999px;
13
13
  top: 0;
@@ -17,14 +17,14 @@
17
17
  z-index: 200;
18
18
  border-radius: 0 0 8px 0;
19
19
  }
20
- .mkt .skip-link:focus {
20
+ .atelic-ui .skip-link:focus {
21
21
  left: 0;
22
22
  }
23
23
 
24
24
  /* ---- Brand lockup ---- */
25
25
  /* The brand's trailing fade bar after the wordmark (logo.run), worn in the
26
26
  header and the footer. */
27
- .mkt .wordmark-run {
27
+ .atelic-ui .wordmark-run {
28
28
  display: inline-block;
29
29
  width: 1.7em;
30
30
  height: 2px;
@@ -38,7 +38,7 @@
38
38
  /* The glyph tile mark (logo.tile): the favicon's design as real DOM text,
39
39
  so the brand webfont renders it, which an SVG loaded through an img
40
40
  cannot do. Worn in the header and the footer. */
41
- .mkt .logo-tile {
41
+ .atelic-ui .logo-tile {
42
42
  display: inline-flex;
43
43
  align-items: center;
44
44
  justify-content: center;
@@ -54,7 +54,7 @@
54
54
  position: relative;
55
55
  flex: none;
56
56
  }
57
- .mkt .logo-tile::after {
57
+ .atelic-ui .logo-tile::after {
58
58
  content: "";
59
59
  position: absolute;
60
60
  left: 7px;
@@ -67,7 +67,7 @@
67
67
 
68
68
  /* ---- Header ---- */
69
69
  /* Fixed header: transparent over a hero, solid once scrolled, or dark. */
70
- .mkt .nav {
70
+ .atelic-ui .nav {
71
71
  position: fixed;
72
72
  top: 0;
73
73
  left: 0;
@@ -81,7 +81,7 @@
81
81
  box-shadow 0.3s ease,
82
82
  backdrop-filter 0.3s ease;
83
83
  }
84
- .mkt .nav-inner {
84
+ .atelic-ui .nav-inner {
85
85
  width: 100%;
86
86
  max-width: var(--max-width);
87
87
  margin-inline: auto;
@@ -91,7 +91,7 @@
91
91
  justify-content: space-between;
92
92
  gap: 20px;
93
93
  }
94
- .mkt .nav-logo {
94
+ .atelic-ui .nav-logo {
95
95
  display: flex;
96
96
  align-items: center;
97
97
  gap: 10px;
@@ -100,33 +100,33 @@
100
100
  letter-spacing: -0.02em;
101
101
  font-size: 1.02rem;
102
102
  }
103
- .mkt .nav-logo .mark {
103
+ .atelic-ui .nav-logo .mark {
104
104
  height: 30px;
105
105
  width: 30px;
106
106
  }
107
107
  /* The recipient half of a sent artifact's lockup (BrandLockup: recipient):
108
108
  a quiet times sign in the mono face, then the business's mark at
109
109
  wordmark height. */
110
- .mkt .nav-logo .nav-x {
110
+ .atelic-ui .nav-logo .nav-x {
111
111
  font-family: var(--font-mono);
112
112
  font-weight: 400;
113
113
  font-size: 0.9rem;
114
114
  color: color-mix(in srgb, currentColor 55%, transparent);
115
115
  margin: 0 2px;
116
116
  }
117
- .mkt .nav-logo .nav-recipient {
117
+ .atelic-ui .nav-logo .nav-recipient {
118
118
  /* Matches the sender's mark, so a square recipient mark reads at the
119
119
  same size as the sender's would; a wide wordmark simply runs longer. */
120
120
  height: 30px;
121
121
  width: auto;
122
122
  display: block;
123
123
  }
124
- .mkt .nav-links {
124
+ .atelic-ui .nav-links {
125
125
  display: none;
126
126
  align-items: center;
127
127
  gap: 4px;
128
128
  }
129
- .mkt .nav-links a {
129
+ .atelic-ui .nav-links a {
130
130
  padding: 8px 14px;
131
131
  border-radius: var(--radius-pill);
132
132
  font-weight: 600;
@@ -135,11 +135,11 @@
135
135
  background 0.15s,
136
136
  color 0.15s;
137
137
  }
138
- .mkt .nav-links a:hover {
138
+ .atelic-ui .nav-links a:hover {
139
139
  background: rgba(33, 28, 23, 0.06);
140
140
  }
141
141
  /* A page's own stops: the scroll spied active one. */
142
- .mkt .nav-links a.active {
142
+ .atelic-ui .nav-links a.active {
143
143
  color: var(--primary);
144
144
  }
145
145
  /* Desktop only, and hidden until the bar is solid: on small screens the
@@ -147,16 +147,16 @@
147
147
  width. The class sits on a wrapper around the button, because a site's
148
148
  .btn display rule lives outside this layer and would outrank a rule here
149
149
  that hid the button itself. */
150
- .mkt .nav .nav-cta {
150
+ .atelic-ui .nav .nav-cta {
151
151
  display: none;
152
152
  }
153
- .mkt .nav-burger {
153
+ .atelic-ui .nav-burger {
154
154
  display: inline-flex;
155
155
  flex-direction: column;
156
156
  gap: 5px;
157
157
  padding: 10px;
158
158
  }
159
- .mkt .nav-burger span {
159
+ .atelic-ui .nav-burger span {
160
160
  width: 24px;
161
161
  height: 2px;
162
162
  background: currentColor;
@@ -166,16 +166,16 @@
166
166
  opacity 0.2s;
167
167
  }
168
168
  /* A white logo image is tinted to ink when the bar is solid on light. */
169
- .mkt .nav.is-solid .nav-logo .mark {
169
+ .atelic-ui .nav.is-solid .nav-logo .mark {
170
170
  filter: brightness(0) saturate(1);
171
171
  }
172
- .mkt .nav.is-transparent {
172
+ .atelic-ui .nav.is-transparent {
173
173
  color: #fff;
174
174
  }
175
- .mkt .nav.is-transparent .nav-logo .wordmark {
175
+ .atelic-ui .nav.is-transparent .nav-logo .wordmark {
176
176
  color: #fff;
177
177
  }
178
- .mkt .nav.is-solid {
178
+ .atelic-ui .nav.is-solid {
179
179
  background: color-mix(in srgb, var(--surface) 86%, transparent);
180
180
  backdrop-filter: saturate(1.4) blur(14px);
181
181
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
@@ -185,40 +185,40 @@
185
185
  /* The dark bar (an artifact's stage, the audit): it matches the page
186
186
  instead of capping it with a light band. The white logo mark stays
187
187
  untinted, and the active stop lifts to the bright primary. */
188
- .mkt .nav.is-solid.nav-dark {
188
+ .atelic-ui .nav.is-solid.nav-dark {
189
189
  background: color-mix(in srgb, var(--surface-dark) 88%, transparent);
190
190
  box-shadow: 0 1px 0 color-mix(in srgb, var(--surface) 12%, transparent);
191
191
  color: color-mix(in srgb, var(--surface) 96%, white);
192
192
  }
193
- .mkt .nav.nav-dark .nav-logo .mark {
193
+ .atelic-ui .nav.nav-dark .nav-logo .mark {
194
194
  filter: none;
195
195
  }
196
- .mkt .nav.nav-dark .nav-links a:hover {
196
+ .atelic-ui .nav.nav-dark .nav-links a:hover {
197
197
  background: rgba(255, 255, 255, 0.08);
198
198
  }
199
- .mkt .nav.nav-dark .nav-links a.active {
199
+ .atelic-ui .nav.nav-dark .nav-links a.active {
200
200
  color: var(--primary-bright);
201
201
  }
202
202
  @media (min-width: 900px) {
203
- .mkt .nav-links {
203
+ .atelic-ui .nav-links {
204
204
  display: flex;
205
205
  }
206
- .mkt .nav-burger {
206
+ .atelic-ui .nav-burger {
207
207
  display: none;
208
208
  }
209
- .mkt .nav.is-solid .nav-cta {
209
+ .atelic-ui .nav.is-solid .nav-cta {
210
210
  display: inline-flex;
211
211
  }
212
212
  }
213
213
  /* The burger folds into an X while the menu is open. State lives on the
214
214
  button's own aria-expanded, so no extra class plumbing. */
215
- .mkt .nav-burger[aria-expanded="true"] span:nth-child(1) {
215
+ .atelic-ui .nav-burger[aria-expanded="true"] span:nth-child(1) {
216
216
  transform: translateY(7px) rotate(45deg);
217
217
  }
218
- .mkt .nav-burger[aria-expanded="true"] span:nth-child(2) {
218
+ .atelic-ui .nav-burger[aria-expanded="true"] span:nth-child(2) {
219
219
  opacity: 0;
220
220
  }
221
- .mkt .nav-burger[aria-expanded="true"] span:nth-child(3) {
221
+ .atelic-ui .nav-burger[aria-expanded="true"] span:nth-child(3) {
222
222
  transform: translateY(-7px) rotate(-45deg);
223
223
  }
224
224
 
@@ -226,7 +226,7 @@
226
226
  /* A native modal dialog, sized to the whole viewport over the dialog's own
227
227
  box. It renders in the top layer above the header, so it carries its own
228
228
  close button where the burger sits. */
229
- .mkt .mobile-menu {
229
+ .atelic-ui .mobile-menu {
230
230
  position: fixed;
231
231
  inset: 0;
232
232
  width: 100%;
@@ -243,20 +243,20 @@
243
243
  transform: translateY(-100%);
244
244
  transition: transform 0.4s cubic-bezier(0.7, 0, 0.2, 1);
245
245
  }
246
- .mkt .mobile-menu[open] {
246
+ .atelic-ui .mobile-menu[open] {
247
247
  display: flex;
248
248
  transform: translateY(0);
249
249
  }
250
250
  @starting-style {
251
- .mkt .mobile-menu[open] {
251
+ .atelic-ui .mobile-menu[open] {
252
252
  transform: translateY(-100%);
253
253
  }
254
254
  }
255
- .mkt .mobile-menu::backdrop {
255
+ .atelic-ui .mobile-menu::backdrop {
256
256
  background: transparent;
257
257
  }
258
258
  @media (prefers-reduced-motion: reduce) {
259
- .mkt .mobile-menu {
259
+ .atelic-ui .mobile-menu {
260
260
  transition: none;
261
261
  }
262
262
  }
@@ -264,7 +264,7 @@
264
264
  html:has(dialog.mobile-menu[open]) {
265
265
  overflow: hidden;
266
266
  }
267
- .mkt .mobile-menu .mm-close {
267
+ .atelic-ui .mobile-menu .mm-close {
268
268
  position: absolute;
269
269
  top: calc(var(--nav-height) / 2 - 14.5px);
270
270
  right: var(--gutter);
@@ -273,21 +273,21 @@
273
273
  gap: 5px;
274
274
  padding: 10px;
275
275
  }
276
- .mkt .mobile-menu .mm-close span {
276
+ .atelic-ui .mobile-menu .mm-close span {
277
277
  width: 24px;
278
278
  height: 2px;
279
279
  background: currentColor;
280
280
  border-radius: 2px;
281
281
  }
282
- .mkt .mobile-menu .mm-close span:nth-child(1) {
282
+ .atelic-ui .mobile-menu .mm-close span:nth-child(1) {
283
283
  transform: translateY(3.5px) rotate(45deg);
284
284
  }
285
- .mkt .mobile-menu .mm-close span:nth-child(2) {
285
+ .atelic-ui .mobile-menu .mm-close span:nth-child(2) {
286
286
  transform: translateY(-3.5px) rotate(-45deg);
287
287
  }
288
288
  /* Menu entries only (.mm-link), never bare anchors, so the .btn CTA below
289
289
  keeps its standard button styling. */
290
- .mkt .mobile-menu .mm-link {
290
+ .atelic-ui .mobile-menu .mm-link {
291
291
  font-size: 1.8rem;
292
292
  font-weight: 800;
293
293
  letter-spacing: -0.02em;
@@ -297,16 +297,16 @@
297
297
  justify-content: space-between;
298
298
  align-items: center;
299
299
  }
300
- .mkt .mobile-menu .mm-link .idx {
300
+ .atelic-ui .mobile-menu .mm-link .idx {
301
301
  font-family: var(--font-mono);
302
302
  font-size: 0.8rem;
303
303
  color: var(--primary-bright);
304
304
  font-weight: 500;
305
305
  }
306
- .mkt .mobile-menu .mm-cta {
306
+ .atelic-ui .mobile-menu .mm-cta {
307
307
  margin-top: 28px;
308
308
  }
309
- .mkt .mobile-menu .mm-contact {
309
+ .atelic-ui .mobile-menu .mm-contact {
310
310
  margin-top: auto;
311
311
  font-family: var(--font-mono);
312
312
  font-size: 0.8rem;
@@ -316,7 +316,7 @@
316
316
  }
317
317
 
318
318
  /* ---- Sticky CTA bar (phones only) ---- */
319
- .mkt .sticky-cta {
319
+ .atelic-ui .sticky-cta {
320
320
  position: fixed;
321
321
  left: 0;
322
322
  right: 0;
@@ -332,37 +332,37 @@
332
332
  transform: translateY(120%);
333
333
  transition: transform 0.35s cubic-bezier(0.7, 0, 0.2, 1);
334
334
  }
335
- .mkt .sticky-cta.show {
335
+ .atelic-ui .sticky-cta.show {
336
336
  transform: translateY(0);
337
337
  }
338
- .mkt .sticky-cta .btn {
338
+ .atelic-ui .sticky-cta .btn {
339
339
  flex: 1;
340
340
  }
341
- .mkt .sticky-cta .btn-call {
341
+ .atelic-ui .sticky-cta .btn-call {
342
342
  flex: 0 0 auto;
343
343
  }
344
344
  @media (min-width: 900px) {
345
- .mkt .sticky-cta {
345
+ .atelic-ui .sticky-cta {
346
346
  display: none;
347
347
  }
348
348
  }
349
349
 
350
350
  /* ---- Footer ---- */
351
- .mkt .footer {
351
+ .atelic-ui .footer {
352
352
  background: var(--surface-dark);
353
353
  color: var(--neutral-200);
354
354
  padding-block: clamp(48px, 7vw, 84px) 28px;
355
355
  }
356
356
  /* biome-ignore lint/style/noDescendingSpecificity: scoped to .footer, so it never meets the header's link rules */
357
- .mkt .footer a:hover {
357
+ .atelic-ui .footer a:hover {
358
358
  color: #fff;
359
359
  }
360
- .mkt .footer-grid {
360
+ .atelic-ui .footer-grid {
361
361
  display: grid;
362
362
  gap: 40px;
363
363
  grid-template-columns: 1fr;
364
364
  }
365
- .mkt .footer h4 {
365
+ .atelic-ui .footer h4 {
366
366
  color: #fff;
367
367
  font-family: var(--font-mono);
368
368
  font-size: 0.74rem;
@@ -371,41 +371,41 @@
371
371
  font-weight: 500;
372
372
  margin-bottom: 16px;
373
373
  }
374
- .mkt .footer-brand img {
374
+ .atelic-ui .footer-brand img {
375
375
  height: 38px;
376
376
  margin-bottom: 18px;
377
377
  }
378
- .mkt .footer-brand p {
378
+ .atelic-ui .footer-brand p {
379
379
  color: var(--neutral-300);
380
380
  max-width: 30ch;
381
381
  }
382
- .mkt .footer nav a {
382
+ .atelic-ui .footer nav a {
383
383
  display: block;
384
384
  padding: 6px 0;
385
385
  color: var(--neutral-200);
386
386
  font-weight: 500;
387
387
  }
388
- .mkt .footer .nap {
388
+ .atelic-ui .footer .nap {
389
389
  font-style: normal;
390
390
  line-height: 1.9;
391
391
  color: var(--neutral-200);
392
392
  }
393
- .mkt .footer .nap a {
393
+ .atelic-ui .footer .nap a {
394
394
  color: #fff;
395
395
  font-weight: 600;
396
396
  }
397
- .mkt .footer-hours {
397
+ .atelic-ui .footer-hours {
398
398
  font-family: var(--font-mono);
399
399
  font-size: 0.82rem;
400
400
  line-height: 1.9;
401
401
  color: var(--neutral-300);
402
402
  }
403
- .mkt .footer-social {
403
+ .atelic-ui .footer-social {
404
404
  display: flex;
405
405
  gap: 10px;
406
406
  margin-top: 20px;
407
407
  }
408
- .mkt .footer .footer-social-link {
408
+ .atelic-ui .footer .footer-social-link {
409
409
  display: inline-flex;
410
410
  align-items: center;
411
411
  justify-content: center;
@@ -415,11 +415,11 @@
415
415
  border-radius: var(--radius-sm);
416
416
  color: var(--neutral-300);
417
417
  }
418
- .mkt .footer .footer-social-link:hover {
418
+ .atelic-ui .footer .footer-social-link:hover {
419
419
  color: var(--primary-bright);
420
420
  border-color: var(--primary-bright);
421
421
  }
422
- .mkt .footer-bottom {
422
+ .atelic-ui .footer-bottom {
423
423
  margin-top: 48px;
424
424
  padding-top: 24px;
425
425
  border-top: 1px solid rgba(255, 255, 255, 0.1);
@@ -431,22 +431,22 @@
431
431
  font-size: 0.82rem;
432
432
  color: var(--neutral-500);
433
433
  }
434
- .mkt .footer .footer-bottom a {
434
+ .atelic-ui .footer .footer-bottom a {
435
435
  color: var(--neutral-300);
436
436
  }
437
- .mkt .footer-legal {
437
+ .atelic-ui .footer-legal {
438
438
  display: flex;
439
439
  gap: 20px;
440
440
  flex-wrap: wrap;
441
441
  }
442
442
  @media (min-width: 760px) {
443
- .mkt .footer-grid {
443
+ .atelic-ui .footer-grid {
444
444
  grid-template-columns: 1.4fr 1fr 1fr 1.1fr;
445
445
  gap: 48px;
446
446
  }
447
447
  }
448
448
  /* Text wordmark for a site with no mark image. */
449
- .mkt .footer-wordmark {
449
+ .atelic-ui .footer-wordmark {
450
450
  color: #fff;
451
451
  font-size: 1.3rem;
452
452
  font-weight: 600;
@@ -459,7 +459,7 @@
459
459
  small, and muted. Purely token themed (the hairline and hover lift derive
460
460
  from --surface through color-mix), so it rebrands with theme.css on any
461
461
  page it renders under, light or dark. */
462
- .mkt .credit-bar {
462
+ .atelic-ui .credit-bar {
463
463
  background: var(--surface-dark);
464
464
  border-top: 1px solid color-mix(in srgb, var(--surface) 10%, transparent);
465
465
  color: var(--neutral-500);
@@ -469,12 +469,12 @@
469
469
  padding: 16px 20px;
470
470
  }
471
471
  /* biome-ignore lint/style/noDescendingSpecificity: scoped to .credit-bar, so it never meets the footer's link rules */
472
- .mkt .credit-bar a {
472
+ .atelic-ui .credit-bar a {
473
473
  color: var(--neutral-300);
474
474
  font-weight: 500;
475
475
  }
476
476
  /* biome-ignore lint/style/noDescendingSpecificity: scoped to .credit-bar, so it never meets the header's link rules */
477
- .mkt .credit-bar a:hover {
477
+ .atelic-ui .credit-bar a:hover {
478
478
  color: color-mix(in srgb, var(--surface) 96%, white);
479
479
  }
480
480
  }