@getvitops/generator 8.3.0-alpha.4 → 8.3.0-alpha.5
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.
- package/CHANGELOG.md +34 -0
- package/assets/css/index.css +1 -0
- package/assets/css/patterns/masthead.css +201 -0
- package/assets/css/patterns/sitenav.css +103 -0
- package/config.schema.json +221 -4
- package/dist/config-BaMpGSUZ.mjs +1856 -0
- package/dist/config-BaMpGSUZ.mjs.map +1 -0
- package/dist/config-C1p83v26.mjs +1832 -0
- package/dist/config-C1p83v26.mjs.map +1 -0
- package/dist/config.d.mts +258 -0
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +14 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# @getvitops/generator
|
|
2
2
|
|
|
3
|
+
## 8.3.0-alpha.5
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- bb61a35: **Call tracking numbers: show an ad visitor a number that attributes the phone call.** Two additive config blocks, no CSS, no breaking change — all formats unaffected.
|
|
8
|
+
|
|
9
|
+
- **`site.ads.google.callTracking`** — Google Ads _website call conversions_. `{ label, number, cache? }` makes the consent-gated Google tag fetch a Google forwarding number (Canada supported) and swap it in for visitors who arrived from a Google ad. Google replaces text only, so the capture script also rewrites the matching `tel:` href. Needs `pixelId` and `site.tracking.enabled`; rejected on any other platform (only Google can put a number on the website — Microsoft's exist only inside the ad, in the US/UK/AU).
|
|
10
|
+
- **`site.tracking.phoneNumbers`** — per-platform numbers for every other platform: the real `number`, `sources` keyed by `site.ads` platform (plus `default-paid`), and an informational `provider`. You provision the numbers with a call-tracking service (Twilio, CallRail, …). The landing URL decides first, then the `_ac` cookie, so a visitor who declines `marketing` sees a tracking number on the landing page only. An organic visitor is never swapped, and the server-rendered HTML always carries the real number.
|
|
11
|
+
- The `tel:` tap beacon now carries which number was shown (`phoneSource`), and the conversion notification says so.
|
|
12
|
+
- `vitops ads lint` flags a call-tracking provider that is not in `site.legal.privacyPolicy.processors`, and notes that `callTracking` plus `conversionLabel.call` can count one call twice (make one secondary in Google Ads).
|
|
13
|
+
- `@getvitops/utils/tracking` exports the pure `selectPhoneNumber()` and helpers; the capture script mirrors it and `tracking.test.ts` pins the two.
|
|
14
|
+
- `@getvitops/skills` gains a **`vitops-ads`** skill (overview plus a `call-tracking.md` topic). The templates gitignore synced skills by prefix (`vitops-*`, `emdash-*`), so it needs no `.gitignore` line of its own.
|
|
15
|
+
|
|
16
|
+
`@getvitops/core`, `@getvitops/vite` and `@getvitops/emdash` — no consumer-facing change in these packages.
|
|
17
|
+
|
|
18
|
+
- 4d94c8b: **Added: `site.chrome` — a configurable masthead and nav behaviour.** Non-breaking.
|
|
19
|
+
|
|
20
|
+
- `site.chrome.masthead` (new `@getvitops/config` schema): `position` (`static|sticky|fixed`), `behavior` (`always|hide-on-scroll`), `surface` (`solid|glass|transparent|transparent-until-scrolled`), `tone` (a `colors.roles` key), `layout` (`full|inset|floating`), `width`, `elevation` (`shadow`, `border`, `onScroll`), `height` and `inset`. `height` and `inset` take fluid lengths (`calc()`, `clamp()`, `min()`, `max()`, with `cqi`/`vw`/… units), checked against a closed grammar because they are written into CSS. `validateConfig` rejects a `tone` or `shadow` the design system does not define (only when the default theme states them directly, not via `extends`).
|
|
21
|
+
- `site.chrome.nav`: `compact` (`drawer|dropdown`), `drawerSide`, `breakpoint`/`mobileBreakpoint`/`megaBreakpoint`, `depth`, `megaDepth`.
|
|
22
|
+
- `<Masthead config={site}>` (`@getvitops/astro`, slots `brand`/default/`actions`) and `resolveMasthead()` / `resolveNavOptions()`. A prop overrides one field per layout. The key is `chrome`/`masthead` on purpose: `<header>` is valid in any section and `banner` already means the announcement bar and the landmark role.
|
|
23
|
+
- `<SiteNav>` gains `compact="dropdown"` (a columnar panel anchored under the toggle), `drawerSide` (previously hardcoded to the end edge in Astro), `megaDepth` and `megaPanel`. `.sitenav--mega` now styles second-level items as column headings and has an optional featured column.
|
|
24
|
+
- `@getvitops/emdash`: `VitopsMegaPanel` fills that column from the widget area `mega-<item url slug>` (or a `mega-area:<name>` class on the menu item). No area means no column.
|
|
25
|
+
- `@getvitops/create` (emdash template): the layout renders through `<Masthead>`; `site.json` ships a sticky glass masthead with a shadow once scrolled. The hand-written `.navbar { block-size: 64px }` is gone (use `masthead.height`).
|
|
26
|
+
- CSS (`patterns/masthead.css`, `patterns/sitenav.css`): **all formats**, including tailwind, where the patterns are inlined into `@layer components`. Hide-on-scroll and the `*-on-scroll` states are `scroll-state()` container queries that only subtract from a settled (solid, elevated, visible) bar, so a browser without them (Firefox today) shows that bar rather than a transparent one. A sticky or fixed masthead sets `--header-offset` / `--top-offset` so anchors land below it.
|
|
27
|
+
- `@getvitops/generator`: new `TIERS.masthead`; `TIERS.sitenav` documents the new props.
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- Updated dependencies [785b8b3]
|
|
32
|
+
- Updated dependencies [bb61a35]
|
|
33
|
+
- Updated dependencies [4d94c8b]
|
|
34
|
+
- @getvitops/config@8.3.0-alpha.5
|
|
35
|
+
- @getvitops/utils@8.3.0-alpha.5
|
|
36
|
+
|
|
3
37
|
## 8.3.0-alpha.4
|
|
4
38
|
|
|
5
39
|
### Minor Changes
|
package/assets/css/index.css
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
/* navbar before sitenav: sitenav's wide state composes it. */
|
|
41
41
|
@import './patterns/navbar.css';
|
|
42
42
|
@import './patterns/sitenav.css';
|
|
43
|
+
@import './patterns/masthead.css';
|
|
43
44
|
/* After sitenav (the nav family stays contiguous) and after drawer.css, whose
|
|
44
45
|
panel surface navshell's desktop block has to undo. */
|
|
45
46
|
@import './patterns/navshell.css';
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Masthead — the site's top bar: brand, navigation, actions. Pure CSS (tier 1); `<Masthead>`
|
|
3
|
+
* (`@getvitops/astro`) only emits the classes below from `site.chrome.masthead`.
|
|
4
|
+
*
|
|
5
|
+
* <header class="masthead masthead--sticky masthead--glass masthead--shadow-on-scroll">
|
|
6
|
+
* <div class="masthead__row navbar centered">brand · nav · actions</div>
|
|
7
|
+
* </header>
|
|
8
|
+
*
|
|
9
|
+
* Named "masthead", not "header"/"banner": `<header>` is valid in any section, and `.banner` is
|
|
10
|
+
* the announcement bar. The element is a plain `<header>` with no explicit `role`.
|
|
11
|
+
*
|
|
12
|
+
* ── SAFE BASE, SUBTRACTIVE ENHANCEMENTS ──
|
|
13
|
+
* The un-modified rule is the settled state: visible, solid (or glass), elevated, in flow.
|
|
14
|
+
* Scroll behaviour is layered on with `scroll-state()` container queries that only ever
|
|
15
|
+
* SUBTRACT from it — `*-on-scroll` clears the surface/shadow/border while the page is at the
|
|
16
|
+
* top; `hide-on-scroll` slides the bar away. Where the query is unsupported (Firefox today)
|
|
17
|
+
* nothing subtracts, so the bar stays visible, solid and elevated: a query can change how the
|
|
18
|
+
* bar looks, never leave it unreadable or take the nav away. (Writing these the other way
|
|
19
|
+
* round — base clear, query reveals — leaves an unsupporting browser with a transparent bar
|
|
20
|
+
* over body text forever.)
|
|
21
|
+
*
|
|
22
|
+
* Hooks (all optional custom properties): `--masthead-height`, `--masthead-bg`, `--masthead-fg`,
|
|
23
|
+
* `--masthead-blur`, `--masthead-glass-opacity`, `--masthead-inset`, `--masthead-shadow`,
|
|
24
|
+
* `--masthead-border`. `site.chrome.masthead` sets `--masthead-height` and `--masthead-inset`
|
|
25
|
+
* on `:root` (see `mastheadRootCss()`), so a clamp()/cqi length there reaches `--header-offset`.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/* Zero-specificity colour defaults, so a `.tone` (or any class) on the element wins. */
|
|
29
|
+
:where(.masthead) {
|
|
30
|
+
--_masthead-base: var(--tone-bg, var(--color-bg-surface));
|
|
31
|
+
|
|
32
|
+
background-color: var(--masthead-bg, var(--_masthead-base));
|
|
33
|
+
color: var(--masthead-fg, var(--tone-fg, var(--color-text-surface)));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.masthead {
|
|
37
|
+
--_masthead-h: var(--masthead-height, 4rem);
|
|
38
|
+
--_masthead-inset: 0px;
|
|
39
|
+
|
|
40
|
+
position: relative;
|
|
41
|
+
z-index: var(--z-tier-sticky);
|
|
42
|
+
display: block;
|
|
43
|
+
box-sizing: border-box;
|
|
44
|
+
transition:
|
|
45
|
+
translate 0.25s ease,
|
|
46
|
+
background-color 0.25s ease,
|
|
47
|
+
box-shadow 0.25s ease,
|
|
48
|
+
backdrop-filter 0.25s ease;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.masthead__row {
|
|
52
|
+
display: block;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* The row's flex child carries the height; `.centered` (when used) is the grid around it. */
|
|
56
|
+
.masthead__row > .navbar {
|
|
57
|
+
min-block-size: var(--_masthead-h);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* `width: full` has no track to supply a gutter. */
|
|
61
|
+
.masthead__row:not(.centered) > .navbar {
|
|
62
|
+
padding-inline: var(--masthead-gutter, 1rem);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ── Position ──────────────────────────────────────────────────────────────── */
|
|
66
|
+
.masthead--sticky {
|
|
67
|
+
position: sticky;
|
|
68
|
+
inset-block-start: var(--_masthead-inset);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Out of flow: overlays the first section. Pair with `--transparent*`. */
|
|
72
|
+
.masthead--fixed {
|
|
73
|
+
position: fixed;
|
|
74
|
+
inset-block-start: var(--_masthead-inset);
|
|
75
|
+
inset-inline: 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Anchors and `scroll-padding` land below a pinned masthead. Sticky keeps its space in
|
|
79
|
+
flow, fixed does not, but both cover the top of the viewport. */
|
|
80
|
+
:root:has(.masthead--sticky, .masthead--fixed) {
|
|
81
|
+
--header-offset: calc(var(--masthead-height, 4rem) + var(--masthead-inset, 0px));
|
|
82
|
+
--top-offset: var(--header-offset);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ── Surface ───────────────────────────────────────────────────────────────── */
|
|
86
|
+
.masthead--glass {
|
|
87
|
+
background-color: color-mix(
|
|
88
|
+
in oklch,
|
|
89
|
+
var(--masthead-bg, var(--_masthead-base)) var(--masthead-glass-opacity, 72%),
|
|
90
|
+
transparent
|
|
91
|
+
);
|
|
92
|
+
backdrop-filter: blur(var(--masthead-blur, 12px));
|
|
93
|
+
-webkit-backdrop-filter: blur(var(--masthead-blur, 12px));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* A browser without backdrop-filter would show page content through a tint. Go solid. */
|
|
97
|
+
@supports not (backdrop-filter: blur(1px)) {
|
|
98
|
+
.masthead--glass {
|
|
99
|
+
background-color: var(--masthead-bg, var(--_masthead-base));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.masthead--transparent {
|
|
104
|
+
background-color: transparent;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* ── Layout ────────────────────────────────────────────────────────────────── */
|
|
108
|
+
.masthead--inset,
|
|
109
|
+
.masthead--floating {
|
|
110
|
+
--_masthead-inset: var(--masthead-inset, 0.75rem);
|
|
111
|
+
|
|
112
|
+
margin-inline: var(--_masthead-inset);
|
|
113
|
+
margin-block-start: var(--_masthead-inset);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* `fixed` has no flow to margin against: pin the box in by its insets instead. */
|
|
117
|
+
.masthead--fixed.masthead--inset,
|
|
118
|
+
.masthead--fixed.masthead--floating {
|
|
119
|
+
margin: 0;
|
|
120
|
+
inset-inline: var(--_masthead-inset);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.masthead--inset {
|
|
124
|
+
border-radius: var(--br-control, 0.375rem);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.masthead--floating {
|
|
128
|
+
border-radius: var(--br-panel, 1rem);
|
|
129
|
+
overflow: clip;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ── Elevation ─────────────────────────────────────────────────────────────── */
|
|
133
|
+
.masthead--shadow {
|
|
134
|
+
box-shadow: var(--masthead-shadow, var(--shadow-sm));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.masthead--border {
|
|
138
|
+
border-block-end: var(
|
|
139
|
+
--masthead-border,
|
|
140
|
+
1px solid var(--tone-border, var(--color-border-surface))
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* A full outline once the masthead is detached from the viewport edge. */
|
|
145
|
+
.masthead--border:is(.masthead--inset, .masthead--floating) {
|
|
146
|
+
border: var(--masthead-border, 1px solid var(--tone-border, var(--color-border-surface)));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* ── Scroll behaviour ───────────────────────────────────────────────────────
|
|
150
|
+
The page is the scroll-state container. Only when a masthead asks for one, so a
|
|
151
|
+
site that uses none pays nothing. */
|
|
152
|
+
:root:has(
|
|
153
|
+
.masthead--hide-on-scroll,
|
|
154
|
+
.masthead--transparent-until-scrolled,
|
|
155
|
+
.masthead--shadow-on-scroll,
|
|
156
|
+
.masthead--border-on-scroll
|
|
157
|
+
) {
|
|
158
|
+
container: masthead-page / scroll-state;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* At the top of the page, the *-on-scroll modifiers subtract their settled look. `not
|
|
162
|
+
scroll-state(scrollable: top)` is "nothing above the viewport", i.e. scrollY = 0. */
|
|
163
|
+
@container masthead-page not scroll-state(scrollable: top) {
|
|
164
|
+
.masthead--transparent-until-scrolled {
|
|
165
|
+
background-color: transparent;
|
|
166
|
+
/* A glass bar would otherwise blur the hero behind an otherwise clear bar. */
|
|
167
|
+
backdrop-filter: none;
|
|
168
|
+
-webkit-backdrop-filter: none;
|
|
169
|
+
}
|
|
170
|
+
.masthead--shadow-on-scroll {
|
|
171
|
+
box-shadow: none;
|
|
172
|
+
}
|
|
173
|
+
.masthead--border-on-scroll {
|
|
174
|
+
border-color: transparent;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Slide away while the last scroll was downward; back when it reverses. Never while the
|
|
179
|
+
visitor is inside it: focus, an open popover or an open disclosure keeps it on screen. */
|
|
180
|
+
@container masthead-page scroll-state(scrolled: block-end) {
|
|
181
|
+
.masthead--hide-on-scroll:not(:focus-within, :has(:popover-open, details[open])) {
|
|
182
|
+
translate: 0 calc(-100% - var(--_masthead-inset) - 1px);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@media (prefers-reduced-motion: reduce) {
|
|
187
|
+
.masthead {
|
|
188
|
+
transition: none;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Shadows and tints disappear in forced-colors; keep a real edge. */
|
|
193
|
+
@media (forced-colors: active) {
|
|
194
|
+
.masthead {
|
|
195
|
+
border-block-end: 1px solid CanvasText;
|
|
196
|
+
}
|
|
197
|
+
.masthead--inset,
|
|
198
|
+
.masthead--floating {
|
|
199
|
+
border: 1px solid CanvasText;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -179,6 +179,77 @@
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
/* ── Compact dropdown — NARROW ONLY ─────────────────────────────────────────
|
|
183
|
+
`compact: 'dropdown'` swaps the edge drawer for a panel anchored under the toggle,
|
|
184
|
+
laid out as columns (each top-level item a column, its sub-links beneath). Same
|
|
185
|
+
markup; `navClass` emits `sitenav--panel-dropdown` INSTEAD of a `drawer-*` class,
|
|
186
|
+
so nothing here competes with the drawer's equal-specificity edge rules. Scoped to
|
|
187
|
+
the narrow state for the reason the whole file is (see "TWO BRANCHES"). */
|
|
188
|
+
.sitenav--panel-dropdown {
|
|
189
|
+
anchor-scope: --sitenav-toggle;
|
|
190
|
+
}
|
|
191
|
+
.sitenav--panel-dropdown > .sitenav__toggle {
|
|
192
|
+
anchor-name: --sitenav-toggle;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@container style(--_sitenav-wide: 0) {
|
|
196
|
+
.sitenav--panel-dropdown .sitenav__panel {
|
|
197
|
+
position-anchor: --sitenav-toggle;
|
|
198
|
+
position-area: block-end span-inline-start;
|
|
199
|
+
inset: auto;
|
|
200
|
+
margin: var(--sitenav-dropdown-gap, 0.5rem) 0;
|
|
201
|
+
inline-size: min(calc(100dvw - 1.5rem), var(--sitenav-dropdown-size, 36rem));
|
|
202
|
+
max-block-size: calc(100dvh - 6rem);
|
|
203
|
+
padding: 1.25rem;
|
|
204
|
+
border: var(--b-panel);
|
|
205
|
+
border-radius: var(--br-panel);
|
|
206
|
+
box-shadow: var(--ds-panel);
|
|
207
|
+
translate: 0 -0.5rem;
|
|
208
|
+
}
|
|
209
|
+
.sitenav--panel-dropdown .sitenav__panel:popover-open {
|
|
210
|
+
translate: 0 0;
|
|
211
|
+
}
|
|
212
|
+
@starting-style {
|
|
213
|
+
.sitenav--panel-dropdown .sitenav__panel:popover-open {
|
|
214
|
+
opacity: 0;
|
|
215
|
+
translate: 0 -0.5rem;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/* The dropdown is anchored, not modal: no close button, no dimmed page. */
|
|
219
|
+
.sitenav--panel-dropdown .sitenav__close {
|
|
220
|
+
display: none;
|
|
221
|
+
}
|
|
222
|
+
.sitenav--panel-dropdown .sitenav__panel {
|
|
223
|
+
padding-block-start: 1.25rem;
|
|
224
|
+
}
|
|
225
|
+
.sitenav--panel-dropdown .sitenav__panel::backdrop {
|
|
226
|
+
background: transparent;
|
|
227
|
+
}
|
|
228
|
+
.sitenav--panel-dropdown .sitenav__panel:popover-open::backdrop {
|
|
229
|
+
background: transparent;
|
|
230
|
+
}
|
|
231
|
+
.sitenav--panel-dropdown .sitenav__list {
|
|
232
|
+
display: grid;
|
|
233
|
+
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
|
234
|
+
gap: 1rem 1.5rem;
|
|
235
|
+
align-items: start;
|
|
236
|
+
}
|
|
237
|
+
/* Columns, not accordions: every sub-list is open, so the caret has nothing to do. */
|
|
238
|
+
.sitenav--panel-dropdown .sitenav__item--branch {
|
|
239
|
+
display: block;
|
|
240
|
+
}
|
|
241
|
+
.sitenav--panel-dropdown .sitenav__item--branch > .sitenav__submenu {
|
|
242
|
+
overflow: visible;
|
|
243
|
+
padding-inline-start: 0;
|
|
244
|
+
}
|
|
245
|
+
.sitenav--panel-dropdown .sitenav__disclosure {
|
|
246
|
+
display: none;
|
|
247
|
+
}
|
|
248
|
+
.sitenav--panel-dropdown .sitenav__list > .sitenav__item--branch > .sitenav__link {
|
|
249
|
+
font-weight: 600;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
182
253
|
/* ── Lists + links ─────────────────────────────────────────────────────────── */
|
|
183
254
|
.sitenav__list,
|
|
184
255
|
.sitenav__submenu {
|
|
@@ -534,6 +605,38 @@
|
|
|
534
605
|
.sitenav--mega .sitenav__submenu .sitenav__disclosure {
|
|
535
606
|
display: none;
|
|
536
607
|
}
|
|
608
|
+
/* Second level reads as a column heading; its description (`titleAttr`) stays. */
|
|
609
|
+
.sitenav--mega
|
|
610
|
+
.sitenav__list
|
|
611
|
+
> .sitenav__item--branch
|
|
612
|
+
> .sitenav__submenu
|
|
613
|
+
> .sitenav__item--branch
|
|
614
|
+
> .sitenav__link {
|
|
615
|
+
font-weight: 600;
|
|
616
|
+
padding-inline: 0;
|
|
617
|
+
}
|
|
618
|
+
.sitenav--mega .sitenav__submenu .sitenav__item--branch > .sitenav__submenu {
|
|
619
|
+
padding-inline: 0;
|
|
620
|
+
}
|
|
621
|
+
/* Optional featured column (EmDash widget area via `megaPanel`). */
|
|
622
|
+
.sitenav--mega .sitenav__mega-extra {
|
|
623
|
+
flex: 1 1 14rem;
|
|
624
|
+
margin-inline-start: auto;
|
|
625
|
+
max-inline-size: 22rem;
|
|
626
|
+
padding-inline-start: 1.5rem;
|
|
627
|
+
border-inline-start: 1px solid var(--color-border-surface, currentColor);
|
|
628
|
+
}
|
|
629
|
+
.sitenav--mega .sitenav__mega-extra:empty {
|
|
630
|
+
display: none;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/* Extra column is a wide-mega feature: in a drawer (a mega menu that is the only tier) it
|
|
635
|
+
would be an unlabelled block between accordions. Empty (no widget area) shows nothing. */
|
|
636
|
+
@container style(--_sitenav-wide: 0) {
|
|
637
|
+
.sitenav__mega-extra {
|
|
638
|
+
display: none;
|
|
639
|
+
}
|
|
537
640
|
}
|
|
538
641
|
|
|
539
642
|
/* ── In-page menu (`vitops_nav` block) ─────────────────────────────────────────
|
package/config.schema.json
CHANGED
|
@@ -2338,6 +2338,160 @@
|
|
|
2338
2338
|
},
|
|
2339
2339
|
"additionalProperties": false
|
|
2340
2340
|
},
|
|
2341
|
+
"chrome": {
|
|
2342
|
+
"description": "Page furniture: the masthead (top bar — position, hide-on-scroll, glass/transparent surface, inset/floating layout, shadow/border) and how its navigation behaves at each width (compact drawer or dropdown, mega tier). Read by `<Masthead />` and `<SiteNav />` via `resolveMasthead()` / `resolveNavOptions()`; a prop overrides a field per layout.",
|
|
2343
|
+
"type": "object",
|
|
2344
|
+
"properties": {
|
|
2345
|
+
"masthead": {
|
|
2346
|
+
"description": "The top bar: position, scroll behaviour, surface, layout, elevation.",
|
|
2347
|
+
"type": "object",
|
|
2348
|
+
"properties": {
|
|
2349
|
+
"position": {
|
|
2350
|
+
"description": "`static` scrolls away; `sticky` stays in flow and pins to the top; `fixed` is out of flow and overlays the first section (for a transparent masthead over a hero). Default `static`.",
|
|
2351
|
+
"type": "string",
|
|
2352
|
+
"enum": [
|
|
2353
|
+
"static",
|
|
2354
|
+
"sticky",
|
|
2355
|
+
"fixed"
|
|
2356
|
+
]
|
|
2357
|
+
},
|
|
2358
|
+
"behavior": {
|
|
2359
|
+
"description": "`hide-on-scroll` slides the masthead away while scrolling down and back on scrolling up. Ignored when `position` is `static`. It never hides while a menu inside it is open or has focus. Uses `scroll-state()`; in a browser without it (Firefox today) the masthead just stays visible. Default `always`.",
|
|
2360
|
+
"type": "string",
|
|
2361
|
+
"enum": [
|
|
2362
|
+
"always",
|
|
2363
|
+
"hide-on-scroll"
|
|
2364
|
+
]
|
|
2365
|
+
},
|
|
2366
|
+
"surface": {
|
|
2367
|
+
"description": "`solid`, `glass` (blurred + tinted via `--surface-glass`), `transparent`, or `transparent-until-scrolled` (clear while the page is at the top, solid once it scrolls; a browser without `scroll-state()` shows it solid throughout). Default `solid`.",
|
|
2368
|
+
"type": "string",
|
|
2369
|
+
"enum": [
|
|
2370
|
+
"solid",
|
|
2371
|
+
"glass",
|
|
2372
|
+
"transparent",
|
|
2373
|
+
"transparent-until-scrolled"
|
|
2374
|
+
]
|
|
2375
|
+
},
|
|
2376
|
+
"tone": {
|
|
2377
|
+
"description": "A `colors.roles` key, optionally `<role>:solid`, applied as the masthead `.tone` so text contrast stays the generator's guarantee.",
|
|
2378
|
+
"type": "string"
|
|
2379
|
+
},
|
|
2380
|
+
"layout": {
|
|
2381
|
+
"description": "`full` is flush to the viewport edges; `inset` leaves a gap to them; `floating` is inset with a panel radius. Default `full`.",
|
|
2382
|
+
"type": "string",
|
|
2383
|
+
"enum": [
|
|
2384
|
+
"full",
|
|
2385
|
+
"inset",
|
|
2386
|
+
"floating"
|
|
2387
|
+
]
|
|
2388
|
+
},
|
|
2389
|
+
"width": {
|
|
2390
|
+
"description": "`content` keeps the masthead row on the page's `.centered` track; `full` lets it span the masthead. Default `content`.",
|
|
2391
|
+
"type": "string",
|
|
2392
|
+
"enum": [
|
|
2393
|
+
"full",
|
|
2394
|
+
"content"
|
|
2395
|
+
]
|
|
2396
|
+
},
|
|
2397
|
+
"elevation": {
|
|
2398
|
+
"description": "Shadow and border.",
|
|
2399
|
+
"type": "object",
|
|
2400
|
+
"properties": {
|
|
2401
|
+
"shadow": {
|
|
2402
|
+
"description": "A `designSystem.shadows` key (e.g. `sm`). Omit for no shadow.",
|
|
2403
|
+
"type": "string"
|
|
2404
|
+
},
|
|
2405
|
+
"border": {
|
|
2406
|
+
"description": "A bottom border (a full outline when `layout` is not `full`).",
|
|
2407
|
+
"type": "boolean"
|
|
2408
|
+
},
|
|
2409
|
+
"onScroll": {
|
|
2410
|
+
"description": "Apply the shadow/border only once the page has scrolled, so the masthead sits flush with the page at the top. Uses `scroll-state()` container queries; in a browser without them (Firefox today) the elevation is simply always on.",
|
|
2411
|
+
"type": "boolean"
|
|
2412
|
+
}
|
|
2413
|
+
},
|
|
2414
|
+
"additionalProperties": false
|
|
2415
|
+
},
|
|
2416
|
+
"height": {
|
|
2417
|
+
"description": "CSS length for the masthead row (default `4rem`). `calc()`, `clamp()`, `min()` and `max()` are accepted, so it can be fluid: `clamp(3.5rem, 8cqi, 4.5rem)`. `cqi` resolves against the nearest size container (`body`, the same one the nav breakpoints use), `vw` against the viewport. Becomes `--masthead-height` and, when the masthead is sticky or fixed, `--header-offset` so in-page anchors land below it.",
|
|
2418
|
+
"type": "string"
|
|
2419
|
+
},
|
|
2420
|
+
"inset": {
|
|
2421
|
+
"description": "Gap between a `layout: inset` or `floating` masthead and the viewport edge (default `0.75rem`). Same length grammar as `height`, e.g. `clamp(0.5rem, 2cqi, 1.5rem)`. Ignored when `layout` is `full`. Becomes `--masthead-inset` and is counted in `--header-offset`.",
|
|
2422
|
+
"type": "string"
|
|
2423
|
+
}
|
|
2424
|
+
},
|
|
2425
|
+
"additionalProperties": false
|
|
2426
|
+
},
|
|
2427
|
+
"nav": {
|
|
2428
|
+
"description": "How the masthead navigation behaves at each width.",
|
|
2429
|
+
"type": "object",
|
|
2430
|
+
"properties": {
|
|
2431
|
+
"breakpoint": {
|
|
2432
|
+
"description": "Container width where the default `primary` menu promotes from the compact control to a navbar. Default `md`.",
|
|
2433
|
+
"type": "string",
|
|
2434
|
+
"enum": [
|
|
2435
|
+
"sm",
|
|
2436
|
+
"md",
|
|
2437
|
+
"lg",
|
|
2438
|
+
"xl"
|
|
2439
|
+
]
|
|
2440
|
+
},
|
|
2441
|
+
"mobileBreakpoint": {
|
|
2442
|
+
"description": "Where a `primary-mobile` menu hands over to `primary`. Default = `breakpoint`.",
|
|
2443
|
+
"type": "string",
|
|
2444
|
+
"enum": [
|
|
2445
|
+
"sm",
|
|
2446
|
+
"md",
|
|
2447
|
+
"lg",
|
|
2448
|
+
"xl"
|
|
2449
|
+
]
|
|
2450
|
+
},
|
|
2451
|
+
"megaBreakpoint": {
|
|
2452
|
+
"description": "Where a `primary-mega` menu takes over. Default `xl`.",
|
|
2453
|
+
"type": "string",
|
|
2454
|
+
"enum": [
|
|
2455
|
+
"sm",
|
|
2456
|
+
"md",
|
|
2457
|
+
"lg",
|
|
2458
|
+
"xl"
|
|
2459
|
+
]
|
|
2460
|
+
},
|
|
2461
|
+
"depth": {
|
|
2462
|
+
"description": "Levels in the default nav (1–3). Default 3.",
|
|
2463
|
+
"type": "integer",
|
|
2464
|
+
"minimum": 1,
|
|
2465
|
+
"maximum": 3
|
|
2466
|
+
},
|
|
2467
|
+
"megaDepth": {
|
|
2468
|
+
"description": "Levels in the mega panel (1–3). Default 3.",
|
|
2469
|
+
"type": "integer",
|
|
2470
|
+
"minimum": 1,
|
|
2471
|
+
"maximum": 3
|
|
2472
|
+
},
|
|
2473
|
+
"compact": {
|
|
2474
|
+
"description": "What the toggle opens below its breakpoint: an edge `drawer`, or a columnar `dropdown` panel anchored under the toggle. Default `drawer`.",
|
|
2475
|
+
"type": "string",
|
|
2476
|
+
"enum": [
|
|
2477
|
+
"drawer",
|
|
2478
|
+
"dropdown"
|
|
2479
|
+
]
|
|
2480
|
+
},
|
|
2481
|
+
"drawerSide": {
|
|
2482
|
+
"description": "Edge the drawer slides from when `compact` is `drawer`. Default `end`.",
|
|
2483
|
+
"type": "string",
|
|
2484
|
+
"enum": [
|
|
2485
|
+
"start",
|
|
2486
|
+
"end"
|
|
2487
|
+
]
|
|
2488
|
+
}
|
|
2489
|
+
},
|
|
2490
|
+
"additionalProperties": false
|
|
2491
|
+
}
|
|
2492
|
+
},
|
|
2493
|
+
"additionalProperties": false
|
|
2494
|
+
},
|
|
2341
2495
|
"seo": {
|
|
2342
2496
|
"description": "SEO defaults: site name, title/description defaults, robots policy, verification tokens, Open Graph + Twitter cards, and post-deploy indexing notification. Read by `<Seo />`, `vitops robots` and `vitops search notify`.",
|
|
2343
2497
|
"type": "object",
|
|
@@ -2714,6 +2868,46 @@
|
|
|
2714
2868
|
"items": {
|
|
2715
2869
|
"type": "string"
|
|
2716
2870
|
}
|
|
2871
|
+
},
|
|
2872
|
+
"phoneNumbers": {
|
|
2873
|
+
"description": "Show a visitor from an ad the tracking number for that platform instead of the real one. Swapped in by the capture script from the landing URL (immediately) or the `_ac` cookie (later pages, once `marketing` is granted); the matching `tel:` links are rewritten too. An organic visitor is never swapped. Needs `enabled: true`.",
|
|
2874
|
+
"type": "object",
|
|
2875
|
+
"properties": {
|
|
2876
|
+
"number": {
|
|
2877
|
+
"type": "string",
|
|
2878
|
+
"description": "The real phone number exactly as the site displays it. Server-rendered HTML always carries this one, so crawlers, structured data and visitors without JavaScript see it."
|
|
2879
|
+
},
|
|
2880
|
+
"sources": {
|
|
2881
|
+
"type": "object",
|
|
2882
|
+
"propertyNames": {
|
|
2883
|
+
"type": "string",
|
|
2884
|
+
"enum": [
|
|
2885
|
+
"google",
|
|
2886
|
+
"meta",
|
|
2887
|
+
"linkedin",
|
|
2888
|
+
"reddit",
|
|
2889
|
+
"tiktok",
|
|
2890
|
+
"microsoft",
|
|
2891
|
+
"pinterest",
|
|
2892
|
+
"snapchat",
|
|
2893
|
+
"default-paid"
|
|
2894
|
+
]
|
|
2895
|
+
},
|
|
2896
|
+
"additionalProperties": {
|
|
2897
|
+
"type": "string"
|
|
2898
|
+
},
|
|
2899
|
+
"description": "Tracking numbers keyed by ad platform (the `site.ads` provider names), each provisioned with a call-tracking provider and forwarding to `number`. `default-paid` is the fallback for a paid visit from a platform with no number of its own. Google needs no entry when `site.ads.google.callTracking` is set — Google supplies its own."
|
|
2900
|
+
},
|
|
2901
|
+
"provider": {
|
|
2902
|
+
"description": "The call-tracking provider (e.g. `twilio`, `callrail`). Informational, but it records callers’ numbers, so it must be named in the privacy policy — declare it in `site.legal.privacyPolicy.processors` unless it is a known one.",
|
|
2903
|
+
"type": "string"
|
|
2904
|
+
}
|
|
2905
|
+
},
|
|
2906
|
+
"required": [
|
|
2907
|
+
"number",
|
|
2908
|
+
"sources"
|
|
2909
|
+
],
|
|
2910
|
+
"additionalProperties": false
|
|
2717
2911
|
}
|
|
2718
2912
|
},
|
|
2719
2913
|
"additionalProperties": false
|
|
@@ -2746,7 +2940,7 @@
|
|
|
2746
2940
|
"type": "string"
|
|
2747
2941
|
},
|
|
2748
2942
|
"conversionLabel": {
|
|
2749
|
-
"description": "Google Ads only: the conversion action label(s) paired with the `AW-…` conversion ID — either one bare label (back-compat, treated as the `call` action) or a map of action → label, so a call, an email click and a form submission each fire their own Google Ads conversion action instead of all three reporting as the same one.",
|
|
2943
|
+
"description": "Google Ads only: the conversion action label(s) paired with the `AW-…` conversion ID — the category (Phone call lead, Contact, …) is a property of the action in Google Ads, not of the tag call, so a wrong category can't be fixed from code; either one bare label (back-compat, treated as the `call` action) or a map of action → label, so a call, an email click and a form submission each fire their own Google Ads conversion action instead of all three reporting as the same one.",
|
|
2750
2944
|
"anyOf": [
|
|
2751
2945
|
{
|
|
2752
2946
|
"type": "string"
|
|
@@ -2755,15 +2949,15 @@
|
|
|
2755
2949
|
"type": "object",
|
|
2756
2950
|
"properties": {
|
|
2757
2951
|
"call": {
|
|
2758
|
-
"description": "Fired on a tel: link click.",
|
|
2952
|
+
"description": "Fired on a tel: link click. Create the action in Google Ads with category \"Phone call lead\".",
|
|
2759
2953
|
"type": "string"
|
|
2760
2954
|
},
|
|
2761
2955
|
"email": {
|
|
2762
|
-
"description": "Fired on a mailto: link click.",
|
|
2956
|
+
"description": "Fired on a mailto: link click. Create the action in Google Ads with category \"Contact\".",
|
|
2763
2957
|
"type": "string"
|
|
2764
2958
|
},
|
|
2765
2959
|
"form": {
|
|
2766
|
-
"description": "Fired client-side by `vitopsFormConversion()` — the consumer's own submit handler calls it once a form POST succeeds.",
|
|
2960
|
+
"description": "Fired client-side by `vitopsFormConversion()` — the consumer's own submit handler calls it once a form POST succeeds. Create the action in Google Ads with category \"Submit lead form\". Don't fire it from a success-page query parameter: that replays on refresh and the honeypot returns the same success page.",
|
|
2767
2961
|
"type": "string"
|
|
2768
2962
|
}
|
|
2769
2963
|
},
|
|
@@ -2797,6 +2991,29 @@
|
|
|
2797
2991
|
}
|
|
2798
2992
|
]
|
|
2799
2993
|
},
|
|
2994
|
+
"callTracking": {
|
|
2995
|
+
"description": "Google Ads only: **website call conversions**. The Google tag fetches a Google forwarding number for visitors who arrived from a Google ad and swaps it in for `number`, so a phone call (not just a `tel:` tap) is attributed to the click. Google decides who sees it; this toolchain also rewrites the matching `tel:` links, which Google does not. Counts the same call as `conversionLabel.call` if both are primary — make one secondary in Google Ads. Any other platform has no equivalent: see `site.tracking.phoneNumbers`.",
|
|
2996
|
+
"type": "object",
|
|
2997
|
+
"properties": {
|
|
2998
|
+
"label": {
|
|
2999
|
+
"type": "string",
|
|
3000
|
+
"description": "The conversion action label of a **separate** Google Ads action of type \"Calls from a phone number on your website\" (not the `conversionLabel.call` action, which counts `tel:` taps). Paired with `pixelId` as `AW-…/<label>`."
|
|
3001
|
+
},
|
|
3002
|
+
"number": {
|
|
3003
|
+
"type": "string",
|
|
3004
|
+
"description": "The phone number exactly as the site displays it, e.g. `613-555-0100`. Google swaps its forwarding number in only where the page text matches these digits, so a differently formatted number on the page is silently never replaced."
|
|
3005
|
+
},
|
|
3006
|
+
"cache": {
|
|
3007
|
+
"description": "Set false to stop Google caching the fetched forwarding number in a cookie (`phone_conversion_options.cache`). Default true, so the number follows the visitor across pages.",
|
|
3008
|
+
"type": "boolean"
|
|
3009
|
+
}
|
|
3010
|
+
},
|
|
3011
|
+
"required": [
|
|
3012
|
+
"label",
|
|
3013
|
+
"number"
|
|
3014
|
+
],
|
|
3015
|
+
"additionalProperties": false
|
|
3016
|
+
},
|
|
2800
3017
|
"reporting": {
|
|
2801
3018
|
"description": "Google Ads only: `\"client\"` (default) fires the conversion via `gtag.js` in the browser at click/submit time; `\"server\"` uploads it via the Ads API from the conversion endpoint instead (see `conversionActionId`). An action with no matching `conversionActionId` keeps firing client-side regardless of this setting, so setting it before `conversionActionId` is filled in never silently drops a conversion.",
|
|
2802
3019
|
"type": "string",
|