@gemboss/website-ui 0.10.0 → 0.11.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.
@@ -44,8 +44,8 @@ the spotlight (0.9.0). It comes in two entries built from one source
44
44
  to the page (Escape, the outside click, the busy-button guard, the system theme, the scroll) are installed once.
45
45
  - **`destroy(root = document)`** takes back what `init` wired under `root`: its listeners and its marks. An open
46
46
  mega menu or mobile menu is closed first, so an unmounted menu cannot leave the page locked.
47
- - **`version`**: `'0.10.0'`. The classic script exposes the three as `window.gwWebsite`.
48
- - **`data-gw-ready`**: on each wired element, the parts wired on it (`mega`, `menu`, `lang`, `theme`, `reveal`,
47
+ - **`version`**: `'0.11.0'`. The classic script exposes the three as `window.gwWebsite`.
48
+ - **`data-gw-ready`**: on each wired element, the parts wired on it (`mega`, on the `.gw-header` since 0.11.0, `menu`, `lang`, `theme`, `reveal`,
49
49
  `header`, `segmented`, `toc`, `table`, `fold`, `spot`). A drill-down menu is wired with its toggle (`menu`). It is a mark for people and tests, not a switch: markup copied from a live page, attribute and all,
50
50
  is still wired.
51
51
  - **One copy per page.** The first copy that runs owns the page (`window.gwWebsite`); another one (the classic
@@ -92,7 +92,7 @@ The script tells the page's own code what changed. Every event is a `CustomEvent
92
92
 
93
93
  | event | target | `detail` | fires when |
94
94
  |---|---|---|---|
95
- | `gw:mega` | the trigger (`[data-gw-mega]`) | `{ id, open }`: the panel's `id`, `true` when it opened | a panel opens or closes: click, hover, Escape, a click outside, focus leaving, a link inside, `destroy` of an open one. Moving to another menu fires a close, then an open |
95
+ | `gw:mega` | the panel's name (`.gw-nav__trigger`, or the `[data-gw-mega]` of a 0.10.0 header) | `{ id, open }`: the panel's `id`, `true` when it opened | a panel opens or closes: the pointer resting on a name or leaving it, the keyboard's focus, ArrowDown, Escape, a click on a `<button>` name, a link inside, a click outside, another panel opening (in this header or another), `destroy` of an open one. Moving to another menu fires a close, then an open |
96
96
  | `gw:theme` | `<html>` | `{ theme: 'light' \| 'dark' }`: the theme the page now shows | a press on a theme toggle; also a change of the system's scheme while the page follows it (no `data-theme`) |
97
97
  | `gw:segmented` | the group (`[data-gw-segmented]`) | `{ name, value }`: the group's name, the chosen option's `value` | the reader chooses an option (click, arrows, Home, End) |
98
98
  | `gw:menu` | the mobile menu (`.gw-mobile-menu`) | `{ open, level }`: `true` while it is open; `level` is the title of the drill-down level in view, `''` for the top level (and for a 0.7.0 menu) | the menu opens or closes (the toggle, Escape, a link inside, resizing past 1080px, `destroy` of an open one), and a drill-down menu moves to a level or back to the top. Closing from a level fires only the close (0.8.0) |
@@ -108,43 +108,59 @@ document.addEventListener('gw:menu', function (e) { if (e.detail.level) analytic
108
108
 
109
109
  ```html
110
110
  <header class="gw-header gw-header--float?" data-gw-header> <!-- --float: a brand's header profile (0.3.0) -->
111
- <div class="gw-wrap gw-header__bar">
112
- <a class="gw-brand" href="/"><img class="gw-brand__mark" src="…" alt="" width="26" height="26"><span class="gw-brand__name">GemBoss</span></a>
111
+ <div class="gw-header__bar"> <!-- 0.11.0: no .gw-wrap; the bar is wrap + 12px -->
112
+ <a class="gw-brand" href="/"><img class="gw-brand__mark" src="…" alt="" width="34" height="34"><span class="gw-brand__name">GemBoss</span></a>
113
113
  <nav class="gw-nav" aria-label="Main">
114
114
  <ul class="gw-nav__list">
115
115
  <li class="gw-nav__item">
116
- <button class="gw-nav__trigger" type="button" aria-expanded="false" aria-controls="gw-mega-product" data-gw-mega="gw-mega-product">Product</button>
117
- <div class="gw-mega" id="gw-mega-product" hidden>
118
- <div class="gw-wrap gw-mega__grid">
119
- <div class="gw-mega__col"><p class="gw-mega__heading">Build</p>
120
- <a class="gw-mega__link" href="…"><span class="gw-mega__icon" aria-hidden="true">svg</span><span><b>Title</b><i>One line</i></span></a>
116
+ <a class="gw-nav__trigger" href="/product" aria-current?="true" aria-expanded="false" aria-controls="gw-mega-product">Product <svg class="gw-nav__chev" aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6"><path d="M6 9l6 6 6-6"/></svg></a>
117
+ <div class="gw-mega" id="gw-mega-product" style="--gw-mega-w: 1120px; --gw-mega-pv: 380px; --gw-mega-cols?: 2" hidden>
118
+ <div class="gw-mega__body">
119
+ <div class="gw-mega__grid">
120
+ <div class="gw-mega__col"><p class="gw-mega__heading">Build</p>
121
+ <a class="gw-mega__link" href="…" data-gw-preview?="pv-funnel"><span><b>Title</b><i>One line</i></span></a>
122
+ </div>
123
+ </div>
124
+ <div class="gw-mega__aside"> <!-- optional: the pictures -->
125
+ <a class="gw-mega__card" href="…"><span class="gw-mega__art">…on the Stage…</span><span class="gw-mega__card-text"><b>Title</b><i>One line</i><span class="gw-mega__go">See it →</span></span></a>
121
126
  </div>
122
- <a class="gw-mega__feature" href="…">…</a> <!-- optional card on the right -->
123
127
  </div>
128
+ <div class="gw-mega__foot">…</div> <!-- optional strip: __foot-start, __foot-end, a.gw-mega__cta, .gw-mega__new -->
124
129
  </div>
125
130
  </li>
126
131
  <li class="gw-nav__item"><a class="gw-nav__link" href="/pricing" aria-current?="page">Pricing</a></li>
127
132
  </ul>
128
133
  </nav>
129
134
  <div class="gw-header__actions">
130
- <button class="gw-theme-toggle" type="button" data-gw-theme-toggle aria-label="Switch to dark theme">svg</button>
131
135
  <div class="gw-lang" data-gw-lang hidden> … </div> <!-- optional: the language switcher (0.8.0, below) -->
132
136
  <a class="gw-nav__link gw-header__signin" href="…">Sign in</a>
133
- <a class="gw-btn gw-btn--secondary gw-btn--sm" href="…">Book a demo</a>
137
+ <a class="gw-nav__link gw-header__link" href="…">Book a demo</a> <!-- 0.11.0: a second text action, from 1181px -->
134
138
  <a class="gw-btn gw-btn--sm" href="…">Start free</a>
135
139
  <button class="gw-menu-toggle" type="button" aria-expanded="false" aria-controls="gw-mobile-menu" data-gw-menu-toggle aria-label="Open menu">svg</button>
136
140
  </div>
137
141
  </div>
138
142
  <div class="gw-mobile-menu" id="gw-mobile-menu" data-gw-menu?="drill" hidden> …the same links as lists, and the actions… </div>
143
+ <template id="pv-funnel">…the row's preview, for the Stage…</template>
139
144
  </header>
140
145
  ```
141
146
 
142
147
  Script behaviour:
143
- - **Mega menu**: click on a trigger toggles its panel (`hidden` removed, `aria-expanded="true"`, `.is-open` on the
144
- `.gw-nav__item`). Hover opens on fine pointers after 80ms and closes 160ms after leaving item and panel. Only one
145
- open at a time on the page, across every header `init` wired. `Escape` closes and returns focus to the trigger. A
146
- click outside closes. Arrow keys are not required. Each change fires `gw:mega`.
147
- - **Mobile menu** (below 1080px the `.gw-nav` and sign-in hide, `.gw-menu-toggle` shows): the toggle flips `hidden`
148
+ - **Mega menu** (0.11.0, gemboss.ai's own, from 1081px; one record per header, the `mega` mark on the `.gw-header`):
149
+ the script marks the header `.has-mega`, takes `hidden` off the panels and keeps a closed one `inert`, and draws
150
+ `.gw-mega-surface`, `.gw-mega-caret` and `.gw-nav__pill` in the header and `.gw-scrim` in the body (all
151
+ `aria-hidden`). A section's name is a link to its page: the pointer resting on it 200ms opens its panel (90ms to
152
+ switch once one is open; a touch is ignored), the keyboard's focus opens it at once, ArrowDown opens it and puts the
153
+ focus on its first row; 350ms after the pointer leaves it closes. Open: `aria-expanded="true"`, `.is-open` on the
154
+ `.gw-nav__item`, `.is-mega-open` on the header, the scrim on; the surface takes the panel's size and place (centred
155
+ under the name, 8px inside the bar's edges) and the panels crossfade on it. A row under the pointer or the focus is
156
+ lit (`.gw-mega__hl`) and shows its preview: the `<template>` its `data-gw-preview` names, cloned once into a
157
+ `.gw-mega__card` (its title the row's, `aria-hidden`, out of the Tab order); `data-gw-preview="feature"`, the
158
+ panel's own card. A card's pictures load the first time it shows: `data-gw-bg="url"` becomes its background,
159
+ `<template data-gw-hydrate>` is added to its parent (`.is-hydrated` on both). `Escape` closes and gives the focus
160
+ back to the name; a link inside or a click outside closes. One panel open on the page, across every header. A
161
+ `<button>` name (0.10.0 markup) also opens its panel with a click and keeps it open when the pointer leaves; a click
162
+ on it again closes it. Each change fires `gw:mega`. `destroy` takes all of it back.
163
+ - **Mobile menu** (up to 1080px the `.gw-nav` and sign-in hide, `.gw-menu-toggle` shows): the toggle flips `hidden`
148
164
  on the menu its `aria-controls` names (`#gw-mobile-menu`), `aria-expanded`, `.is-menu-open` on `<html>` (the CSS
149
165
  locks scroll), and its `aria-label` (Open menu / Close menu). `Escape` closes. Resizing above 1080px closes it.
150
166
  - **Labels in other languages**: the script never writes English over a translated page. Give the toggles their
@@ -160,6 +176,8 @@ Script behaviour:
160
176
  - **Header**: `.is-scrolled` on `[data-gw-header]` once the page scrolled more than 8px (the CSS may use it for the
161
177
  hairline only; the bar does not change height or colour). The floating header needs nothing more from the script:
162
178
  it is the same element with one more class.
179
+ - **Current section** (0.11.0): a section's name with `aria-current` in the markup (`"true"`: the page is in that
180
+ section) is drawn like the current page's link, in the accent's strong ink at weight 600.
163
181
  - **Current page**: the script marks `aria-current="page"` on `.gw-nav__link` / `.gw-mega__link` and the mobile
164
182
  menu's links whose `href` pathname equals `location.pathname` (when the markup did not already). Each `init` takes
165
183
  its own marks back first, so after a client-side navigation they follow the new path; a mark in the markup stays.
@@ -217,6 +235,14 @@ top-level links and the actions are children of the sheet's `<nav>`.
217
235
  - `check` reports a group of a drill menu that is not a `<details>`, has no `<summary>`, no back button, no title or a
218
236
  title without `tabindex="-1"`, and `data-gw-menu` with another value (`menu-markup`).
219
237
 
238
+ **0.11.0, as gemboss.ai's phone menu:** the sheet is 24px in from both edges and its links are set at 18px, weight
239
+ 500, 0.005em tighter. A level may end with its panel's card (the same `.gw-mega__card` markup as in the panel: its
240
+ pictures load the first time the level shows) and `.gw-mobile-menu__all`, the link to the section's own page ("All of
241
+ Product →", the arrow drawn after a space). A row may carry a 48x32 picture (`<img>`) instead of its icon. A row under
242
+ the pointer or the keyboard takes the soft ground, moves 2px right, its icon tile in the accent. In the action bar,
243
+ `.gw-mobile-menu__link--muted` is a quieter link before Sign in (What's new); the buttons are 8px apart. The current
244
+ page is not marked on the phone.
245
+
220
246
  ### Language switcher (0.8.0)
221
247
 
222
248
  ```html
@@ -232,7 +258,7 @@ top-level links and the actions are children of the sheet's `<nav>`.
232
258
  </div>
233
259
  ```
234
260
 
235
- - **Where**: in `.gw-header__actions`, before Sign in (drawn from 1080px), and a second one, with its own list `id`, in
261
+ - **Where**: in `.gw-header__actions`, before Sign in (drawn from 1081px), and a second one, with its own list `id`, in
236
262
  the mobile menu's `.gw-mobile-menu__actions` (its list opens upward there). Both are filled the same way.
237
263
  - **The languages are the head's** `<link rel="alternate" hreflang href>`, not `x-default`, the first link of each
238
264
  `hreflang`: the links search engines read. The current language is the one whose `hreflang` is `<html lang>`, else the
package/js/website.js CHANGED
@@ -1,11 +1,11 @@
1
- /*! @gemboss/website-ui 0.10.0 website.js | GENERATED by scripts/build.mjs from js/website.mjs (the source, with its comments): edit that file | spec: docs/markup-contract.md | load with defer */
1
+ /*! @gemboss/website-ui 0.11.0 website.js | GENERATED by scripts/build.mjs from js/website.mjs (the source, with its comments): edit that file | spec: docs/markup-contract.md | load with defer */
2
2
  (function () {
3
3
  'use strict';
4
4
  if (window.gwWebsite) return;
5
5
 
6
- var version = '0.10.0';
6
+ var version = '0.11.0';
7
7
  var api = { version: version, init: init, destroy: destroy };
8
- var doc, root, mine, fine, sys, wide, mid, live = [], megaOpen = null, megaTimer = 0, queued = false;
8
+ var doc, root, mine, fine, sys, wide, desk, mid, live = [], queued = false;
9
9
  var still, lights;
10
10
  var READY = 'data-gw-ready';
11
11
 
@@ -46,46 +46,286 @@ function drop(r) {
46
46
  }
47
47
  function records(kind, fn) { live.slice().forEach(function (r) { if (r.kind === kind) fn(r); }); }
48
48
 
49
- function setMega(m, show, mode) {
50
- m.p.hidden = !show;
51
- m.t.setAttribute('aria-expanded', String(show));
52
- m.item.classList.toggle('is-open', show);
53
- m.mode = mode;
54
- megaOpen = show ? m : megaOpen === m ? null : megaOpen;
55
- emit(m.t, 'gw:mega', { id: m.p.id, open: show });
56
- }
57
- function showMega(m, mode) {
58
- clearTimeout(megaTimer);
59
- if (megaOpen === m) { if (mode === 'click') m.mode = mode; return; }
60
- if (megaOpen) setMega(megaOpen, false);
61
- setMega(m, true, mode);
62
- }
63
- function hideMega(focus) {
64
- var m = megaOpen;
65
- clearTimeout(megaTimer);
66
- if (!m) return;
67
- setMega(m, false);
68
- if (focus === true) m.t.focus();
49
+ var hydrated = new WeakSet();
50
+ function hydrate(card) {
51
+ if (!card || hydrated.has(card)) return;
52
+ hydrated.add(card);
53
+ within('[data-gw-bg]', card).forEach(function (el) {
54
+ el.style.backgroundImage = 'url("' + attr(el, 'data-gw-bg', '') + '")';
55
+ el.classList.add('is-hydrated');
56
+ });
57
+ within('template[data-gw-hydrate]', card).forEach(function (t) {
58
+ t.parentNode.appendChild(t.content.cloneNode(true));
59
+ t.parentNode.classList.add('is-hydrated');
60
+ });
69
61
  }
70
- function inside(m, el) { return !!el && (m.item.contains(el) || m.p.contains(el)); }
71
62
  function mega(ctx) {
72
- each(ctx, '[data-gw-mega]', 'mega', function (t, on) {
73
- var p = doc.getElementById(attr(t, 'data-gw-mega', attr(t, 'aria-controls', '')));
74
- if (!p) return;
75
- var m = { t: t, p: p, item: t.closest('.gw-nav__item') || t.parentNode };
76
- on(t, 'click', function () { if (megaOpen === m && m.mode !== 'hover') hideMega(); else showMega(m, 'click'); });
77
- (m.item.contains(p) ? [m.item] : [m.item, p]).forEach(function (el) {
78
- on(el, 'mouseenter', function () {
79
- if (fine.matches) { clearTimeout(megaTimer); if (megaOpen !== m) megaTimer = setTimeout(function () { showMega(m, 'hover'); }, 80); }
63
+ each(ctx, '.gw-header', 'mega', function (header, on) {
64
+ var items = [];
65
+ all('.gw-nav__item', header).forEach(function (item) {
66
+ var t = item.querySelector('[data-gw-mega], .gw-nav__trigger');
67
+ var p = t && doc.getElementById(attr(t, 'data-gw-mega', attr(t, 'aria-controls', '')));
68
+ if (p) items.push({ item: item, t: t, p: p, body: p.querySelector('.gw-mega__body'), foot: p.querySelector('.gw-mega__foot'),
69
+ grid: p.querySelector('.gw-mega__grid'), rows: all('.gw-mega__link', p), pane: p.querySelector('.gw-mega__aside') });
70
+ });
71
+ if (!items.length) return;
72
+ var nav = header.querySelector('.gw-nav') || header;
73
+ var bar = header.querySelector('.gw-header__bar') || header;
74
+ var mk = function (tag, cls) { var n = doc.createElement(tag); n.className = cls; n.setAttribute('aria-hidden', 'true'); return n; };
75
+ var scrim = mk('div', 'gw-scrim'), bg = mk('div', 'gw-mega-surface'), caret = mk('div', 'gw-mega-caret'), pill = mk('span', 'gw-nav__pill');
76
+ var made = [scrim, bg, caret, pill], cards = new Map(), cur = null, openT = 0, closeT = 0, pvT = 0, pvTok = 0, quiet = false, rec;
77
+ doc.body.appendChild(scrim);
78
+ header.appendChild(pill);
79
+ header.appendChild(bg);
80
+ header.appendChild(caret);
81
+ header.classList.add('has-mega');
82
+ items.forEach(function (m) {
83
+ m.hid = m.p.hidden;
84
+ m.p.hidden = false;
85
+ m.p.inert = true;
86
+ m.feat = m.pane && m.pane.querySelector('.gw-mega__card');
87
+ });
88
+ var still2 = function () { return still.matches; };
89
+ function snap(els, fn) {
90
+ els.forEach(function (n) { if (n) n.style.transition = 'none'; });
91
+ fn();
92
+ void header.offsetWidth;
93
+ els.forEach(function (n) { if (n) n.style.transition = ''; });
94
+ }
95
+ function cardFor(m, row) {
96
+ var k = attr(row, 'data-gw-preview', '');
97
+ if (k === 'feature') return m.feat || null;
98
+ if (cards.has(row)) return cards.get(row);
99
+ var tpl = k && doc.getElementById(k);
100
+ if (!tpl || !tpl.content || !m.pane) return null;
101
+ var a = doc.createElement('a'), art = doc.createElement('span'), txt = doc.createElement('span'), go = doc.createElement('span');
102
+ var b = row.querySelector('b'), t = b ? b.cloneNode(true) : doc.createElement('b'), line = row.querySelector('i');
103
+ a.className = 'gw-mega__card';
104
+ a.href = attr(row, 'href', '#');
105
+ if (row.rel) a.rel = row.rel;
106
+ a.tabIndex = -1;
107
+ a.setAttribute('aria-hidden', 'true');
108
+ within('[aria-hidden="true"]', t).forEach(function (n) { if (n !== t) n.remove(); });
109
+ go.className = 'gw-mega__go';
110
+ go.textContent = /^https?:/.test(a.getAttribute('href')) ? '\u2197' : '\u2192';
111
+ t.appendChild(go);
112
+ art.className = 'gw-mega__art';
113
+ art.appendChild(tpl.content.cloneNode(true));
114
+ txt.className = 'gw-mega__card-text';
115
+ txt.appendChild(t);
116
+ if (line) txt.appendChild(line.cloneNode(true));
117
+ a.appendChild(art);
118
+ a.appendChild(txt);
119
+ m.pane.appendChild(a);
120
+ cards.set(row, a);
121
+ return a;
122
+ }
123
+ function build(m) {
124
+ if (m.built) return;
125
+ m.built = true;
126
+ if (m.grid) { m.hl = mk('span', 'gw-mega__hl'); m.grid.insertBefore(m.hl, m.grid.firstChild); made.push(m.hl); }
127
+ hydrate(m.feat);
128
+ if (m.pane && !m.feat && m.rows[0]) m.feat = cardFor(m, m.rows[0]);
129
+ var idle = window.requestIdleCallback || function (f) { return setTimeout(f, 200); };
130
+ if (m.pane) idle(function () { if (header.classList.contains('has-mega')) m.rows.forEach(function (row) { cardFor(m, row); }); });
131
+ }
132
+ function preview(m, row, now) {
133
+ clearTimeout(pvT);
134
+ var tok = ++pvTok;
135
+ var go = function () {
136
+ var c = row ? cardFor(m, row) : m.feat;
137
+ if (!c || c === m.on) return;
138
+ var swap = function () {
139
+ if (tok !== pvTok || m.on === c) return;
140
+ if (m.on) m.on.classList.remove('is-on');
141
+ c.classList.add('is-on');
142
+ m.on = c;
143
+ };
144
+ var wait = [].filter.call(c.querySelectorAll('img'), function (i) { return !i.complete && i.decode; });
145
+ if (!wait.length || !window.Promise) { swap(); return; }
146
+ var t = setTimeout(swap, 240);
147
+ Promise.all(wait.map(function (i) { return i.decode().catch(function () {}); })).then(function () { clearTimeout(t); swap(); });
148
+ };
149
+ if (now) go(); else pvT = setTimeout(go, 80);
150
+ }
151
+ function light(m, row) {
152
+ if (!m.hl) return;
153
+ if (!row) { m.hl.classList.remove('is-on'); return; }
154
+ var place = function () {
155
+ m.hl.style.translate = row.offsetLeft + 'px ' + row.offsetTop + 'px';
156
+ m.hl.style.width = row.offsetWidth + 'px';
157
+ m.hl.style.height = row.offsetHeight + 'px';
158
+ };
159
+ if (m.hl.classList.contains('is-on') && !still2()) place(); else snap([m.hl], place);
160
+ m.hl.classList.add('is-on');
161
+ }
162
+ function movePill(t) {
163
+ if (!t) { pill.classList.remove('is-on'); return; }
164
+ var hr = header.getBoundingClientRect(), r = t.getBoundingClientRect();
165
+ var place = function () {
166
+ pill.style.translate = Math.round(r.left - hr.left) + 'px ' + Math.round(r.top - hr.top) + 'px';
167
+ pill.style.width = Math.round(r.width) + 'px';
168
+ pill.style.height = Math.round(r.height) + 'px';
169
+ };
170
+ if (pill.classList.contains('is-on') && !still2()) place(); else snap([pill], place);
171
+ pill.classList.add('is-on');
172
+ }
173
+ function measure() {
174
+ var hr = header.getBoundingClientRect(), inner = bar.getBoundingClientRect();
175
+ var lo = inner.left - hr.left + 8, hi = inner.right - hr.left - 8;
176
+ header.style.setProperty('--gw-mega-top', (header.clientHeight + 8) + 'px');
177
+ items.forEach(function (m) {
178
+ var t = m.t.getBoundingClientRect(), w = m.p.offsetWidth, h = m.p.offsetHeight;
179
+ var cx = t.left + t.width / 2 - hr.left;
180
+ var x = Math.round(Math.max(lo, Math.min(cx - w / 2, hi - w)));
181
+ m.x = x; m.w = w; m.h = h;
182
+ m.cx = Math.round(Math.max(x + 24, Math.min(cx, x + w - 24)));
183
+ m.p.style.left = x + 'px';
184
+ m.p.style.transformOrigin = (m.cx - x) + 'px 0';
185
+ });
186
+ }
187
+ function clipTo(m, r) {
188
+ if (!r) return 'inset(0px 0px 0px 0px round 20px)';
189
+ var l = Math.max(0, r.x - m.x), rt = Math.max(0, m.x + m.w - (r.x + r.w)), b = Math.max(0, m.h - r.h);
190
+ return 'inset(0px ' + Math.round(rt) + 'px ' + Math.round(b) + 'px ' + Math.round(l) + 'px round 20px)';
191
+ }
192
+ function surface(m, y) {
193
+ bg.style.left = m.x + 'px';
194
+ bg.style.translate = '0px ' + y + 'px';
195
+ bg.style.width = m.w + 'px';
196
+ bg.style.height = m.h + 'px';
197
+ bg.style.transformOrigin = (m.cx - m.x) + 'px 0';
198
+ caret.style.translate = (m.cx - 6) + 'px ' + y + 'px';
199
+ }
200
+ function show(m) {
201
+ clearTimeout(closeT);
202
+ if (cur === m) return;
203
+ var prev = cur;
204
+ cur = m;
205
+ if (prev) {
206
+ prev.pinned = false;
207
+ prev.item.classList.remove('is-open');
208
+ prev.t.setAttribute('aria-expanded', 'false');
209
+ prev.p.inert = true;
210
+ emit(prev.t, 'gw:mega', { id: prev.p.id, open: false });
211
+ }
212
+ if (!m) {
213
+ header.classList.remove('is-mega-open');
214
+ scrim.classList.remove('is-on');
215
+ if (prev) { bg.style.translate = '0px -6px'; caret.style.translate = (prev.cx - 6) + 'px -6px'; }
216
+ movePill(nav.querySelector('.gw-nav__trigger:hover, .gw-nav__link:hover'));
217
+ return;
218
+ }
219
+ build(m);
220
+ records('mega', function (o) { if (o !== rec && o.open()) o.close(); });
221
+ preview(m, null, true);
222
+ light(m, null);
223
+ if (!prev) {
224
+ measure();
225
+ snap([bg, caret, m.p, m.body, m.foot], function () {
226
+ surface(m, -6);
227
+ m.p.style.clipPath = clipTo(m, null);
228
+ m.p.style.setProperty('--gw-mega-dx', '0px');
229
+ });
230
+ header.classList.add('is-mega-open');
231
+ surface(m, 0);
232
+ } else {
233
+ var hr = header.getBoundingClientRect(), br = bg.getBoundingClientRect();
234
+ var from = { x: br.left - hr.left, w: br.width, h: br.height };
235
+ var dir = items.indexOf(m) > items.indexOf(prev) ? 1 : -1;
236
+ if (!still2()) {
237
+ snap([m.p, m.body, m.foot], function () {
238
+ m.p.style.clipPath = clipTo(m, from);
239
+ m.p.style.setProperty('--gw-mega-dx', (dir * 8) + 'px');
240
+ });
241
+ prev.p.style.clipPath = clipTo(prev, m);
242
+ prev.p.style.setProperty('--gw-mega-dx', (-dir * 8) + 'px');
243
+ }
244
+ m.p.style.clipPath = clipTo(m, null);
245
+ m.p.style.setProperty('--gw-mega-dx', '0px');
246
+ surface(m, 0);
247
+ }
248
+ m.p.inert = false;
249
+ m.item.classList.add('is-open');
250
+ m.t.setAttribute('aria-expanded', 'true');
251
+ scrim.classList.add('is-on');
252
+ movePill(m.t);
253
+ emit(m.t, 'gw:mega', { id: m.p.id, open: true });
254
+ }
255
+ function hideSoon() { clearTimeout(openT); clearTimeout(closeT); closeT = setTimeout(function () { show(null); }, 350); }
256
+ items.forEach(function (m) {
257
+ on(m.p, 'transitionend', function (e) {
258
+ if (e.target === m.p && e.propertyName === 'opacity' && !m.item.classList.contains('is-open')) m.p.style.clipPath = '';
259
+ });
260
+ on(m.item, 'pointerenter', function (e) {
261
+ if (!desk.matches || e.pointerType === 'touch') return;
262
+ clearTimeout(closeT);
263
+ clearTimeout(openT);
264
+ openT = setTimeout(function () { show(m); }, cur ? 90 : 200);
265
+ });
266
+ on(m.item, 'pointerleave', function () { if (desk.matches && !m.pinned) hideSoon(); });
267
+ on(m.item, 'focusin', function () { if (desk.matches && !quiet) show(m); });
268
+ on(m.item, 'focusout', function (e) { if (desk.matches && !m.item.contains(e.relatedTarget)) hideSoon(); });
269
+ on(m.t, 'keydown', function (e) {
270
+ if (!desk.matches || e.key !== 'ArrowDown') return;
271
+ e.preventDefault();
272
+ show(m);
273
+ if (m.rows[0]) m.rows[0].focus();
80
274
  });
81
- on(el, 'mouseleave', function () {
82
- if (fine.matches) { clearTimeout(megaTimer); if (megaOpen === m && m.mode === 'hover') megaTimer = setTimeout(hideMega, 160); }
275
+ if (m.t.tagName === 'BUTTON') on(m.t, 'click', function () {
276
+ if (!desk.matches) return;
277
+ clearTimeout(openT);
278
+ if (cur === m && m.pinned) { show(null); return; }
279
+ show(m);
280
+ m.pinned = true;
83
281
  });
282
+ on(m.p, 'click', function (e) { if (link(e)) show(null); });
283
+ m.rows.forEach(function (row) {
284
+ on(row, 'pointerenter', function () { if (!desk.matches || cur !== m) return; light(m, row); preview(m, row, false); });
285
+ on(row, 'focus', function () { if (!desk.matches) return; if (cur !== m) show(m); light(m, row); preview(m, row, true); });
286
+ });
287
+ if (m.feat && m.feat.tagName === 'A') on(m.feat, 'focus', function () { if (!desk.matches) return; light(m, null); preview(m, null, true); });
288
+ });
289
+ all('.gw-nav__trigger, .gw-nav__link', nav).forEach(function (t) {
290
+ on(t, 'pointerenter', function (e) { if (desk.matches && e.pointerType !== 'touch') movePill(t); });
291
+ on(t, 'focus', function () { if (desk.matches) movePill(t); });
84
292
  });
85
- on(m.item, 'focusout', function (e) { if (megaOpen === m && e.relatedTarget && !inside(m, e.relatedTarget)) hideMega(); });
86
- on(p, 'click', function (e) { if (link(e)) hideMega(); });
87
- m.stop = function () { if (megaOpen === m) hideMega(); };
88
- return m;
293
+ on(nav, 'pointerleave', function () { if (desk.matches && !cur) movePill(null); });
294
+ on(nav, 'focusout', function (e) { if (desk.matches && !cur && !nav.contains(e.relatedTarget)) movePill(null); });
295
+ on(window, 'resize', function () {
296
+ if (!desk.matches) { show(null); movePill(null); return; }
297
+ if (cur) { measure(); snap([bg, caret], function () { surface(cur, 0); }); movePill(cur.t); }
298
+ });
299
+ rec = {
300
+ open: function () { return !!cur; },
301
+ has: function (el) { return !!cur && !!el && (cur.item.contains(el) || cur.p.contains(el)); },
302
+ close: function (focus) {
303
+ if (!cur) return;
304
+ var t = cur.t;
305
+ quiet = true;
306
+ show(null);
307
+ if (focus) t.focus();
308
+ quiet = false;
309
+ if (focus) movePill(t);
310
+ },
311
+ stop: function () {
312
+ clearTimeout(openT); clearTimeout(closeT); clearTimeout(pvT);
313
+ if (cur) show(null);
314
+ made.forEach(function (n) { n.remove(); });
315
+ cards.forEach(function (c) { c.remove(); });
316
+ items.forEach(function (m) {
317
+ m.p.hidden = m.hid;
318
+ m.p.inert = false;
319
+ ['left', 'transformOrigin', 'clipPath'].forEach(function (k) { m.p.style[k] = ''; });
320
+ m.p.style.removeProperty('--gw-mega-dx');
321
+ if (m.on) m.on.classList.remove('is-on');
322
+ m.built = false;
323
+ });
324
+ header.style.removeProperty('--gw-mega-top');
325
+ header.classList.remove('has-mega', 'is-mega-open');
326
+ }
327
+ };
328
+ return rec;
89
329
  });
90
330
  }
91
331
 
@@ -117,6 +357,7 @@ function mobile(ctx) {
117
357
  if (g === cur) return;
118
358
  cur = g;
119
359
  groups.forEach(function (x) { x.open = x === g; });
360
+ if (g) hydrate(g.querySelector('.gw-mega__card'));
120
361
  menu.classList.toggle('is-drilled', !!g);
121
362
  menu.classList.toggle('is-back', !g && r.open());
122
363
  menu.scrollTop = 0;
@@ -428,13 +669,15 @@ function install() {
428
669
  fine = mq('(hover: hover) and (pointer: fine)'); sys = mq('(prefers-color-scheme: dark)');
429
670
  var stored = null;
430
671
  wide = mq('(min-width: 1080px)');
672
+ desk = mq('(min-width: 1081px)');
431
673
  mid = mq('(min-width: 621px)');
432
- on(doc, 'click', function (e) { if (megaOpen && !inside(megaOpen, e.target)) hideMega(); });
433
- on(doc, 'keydown', function (e) { if (megaOpen && esc(e)) hideMega(inside(megaOpen, doc.activeElement) || megaOpen.mode === 'click'); });
674
+ on(doc, 'click', function (e) { records('mega', function (r) { if (r.open() && !r.has(e.target)) r.close(); }); });
675
+ on(doc, 'keydown', function (e) { if (esc(e)) records('mega', function (r) { if (r.open()) r.close(true); }); });
434
676
  on(doc, 'keydown', function (e) { if (esc(e)) closeMenus(true); });
435
677
  on(doc, 'keydown', function (e) { if (e.key === 'Tab') records('menu', function (r) { if (r.trap && r.open()) r.trap(e); }); });
436
678
  on(doc, 'click', function (e) { records('lang', function (r) { if (r.open() && !r.el.contains(e.target)) r.set(false); }); });
437
- watch(wide, function () { if (wide.matches) closeMenus(); records('toc', function (r) { r.fit(); }); });
679
+ watch(wide, function () { records('toc', function (r) { r.fit(); }); });
680
+ watch(desk, function () { if (desk.matches) closeMenus(); });
438
681
  watch(mid, function () { records('fold', function (r) { r.fit(); }); });
439
682
  still = mq('(prefers-reduced-motion: reduce)');
440
683
  var css = window.CSS;