@agent-cms/agentcms 0.0.0-stage → 0.1.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.
- package/LICENSE +143 -0
- package/README.md +1119 -2
- package/changelog.json +20 -0
- package/dist/admin/admin-files.json +48 -0
- package/dist/admin/assets/CodeEditor-DcFzGUt8.js +24 -0
- package/dist/admin/assets/flexweg-logo-CdtuqAFA.png +0 -0
- package/dist/admin/assets/index-CLZ1gQZl.js +5460 -0
- package/dist/admin/assets/index-DMcGogR2.css +1 -0
- package/dist/admin/assets/sql-wasm-C1U8OeUW.wasm +0 -0
- package/dist/admin/assets/sql-wasm-CYl3LXH_.js +2 -0
- package/dist/admin/assets/sql-wasm-V5jTDcrT.js +1 -0
- package/dist/admin/config.js +11 -0
- package/dist/admin/external.default.json +72 -0
- package/dist/admin/index.html +116 -0
- package/dist/admin/plugins/core-seo/bundle.js +55 -0
- package/dist/admin/plugins/core-seo/manifest.json +7 -0
- package/dist/admin/plugins/flexweg-archives/bundle.js +1414 -0
- package/dist/admin/plugins/flexweg-archives/manifest.json +7 -0
- package/dist/admin/plugins/flexweg-multilang/bundle.js +1187 -0
- package/dist/admin/plugins/flexweg-multilang/manifest.json +7 -0
- package/dist/admin/plugins/flexweg-rss/bundle.js +1354 -0
- package/dist/admin/plugins/flexweg-rss/manifest.json +7 -0
- package/dist/admin/plugins/flexweg-search/bundle.js +1243 -0
- package/dist/admin/plugins/flexweg-search/manifest.json +7 -0
- package/dist/admin/plugins/flexweg-sitemaps/bundle.js +1293 -0
- package/dist/admin/plugins/flexweg-sitemaps/manifest.json +7 -0
- package/dist/admin/runtime/cms-runtime.js +164 -0
- package/dist/admin/runtime/react-dom-client.js +13 -0
- package/dist/admin/runtime/react-dom.js +16 -0
- package/dist/admin/runtime/react-i18next.js +21 -0
- package/dist/admin/runtime/react-jsx-runtime.js +17 -0
- package/dist/admin/runtime/react.js +55 -0
- package/dist/admin/themes/corporate/bundle.js +14465 -0
- package/dist/admin/themes/corporate/manifest.json +7 -0
- package/dist/admin/themes/corporate/theme.css +2 -0
- package/dist/admin/themes/magazine/bundle.js +10229 -0
- package/dist/admin/themes/magazine/manifest.json +7 -0
- package/dist/admin/themes/magazine/theme.css +2 -0
- package/dist/admin/themes/marketplace-core/bundle.js +11213 -0
- package/dist/admin/themes/marketplace-core/manifest.json +7 -0
- package/dist/admin/themes/marketplace-core/theme.css +1613 -0
- package/dist/admin/themes/portfolio/bundle.js +9195 -0
- package/dist/admin/themes/portfolio/manifest.json +7 -0
- package/dist/admin/themes/portfolio/theme.css +2 -0
- package/dist/admin/themes/storefront/bundle.js +13172 -0
- package/dist/admin/themes/storefront/manifest.json +7 -0
- package/dist/admin/themes/storefront/theme.css +2 -0
- package/dist/agent/agentcms.mjs +1711 -0
- package/dist/agent/chunks/hosting-D_uj-Mt1.mjs +117 -0
- package/dist/agent/chunks/index-CrvEoyi0.mjs +3608 -0
- package/dist/agent/chunks/install-DkCCcKAO.mjs +577 -0
- package/dist/agent/chunks/plan-EYKegfhp.mjs +245 -0
- package/dist/agent/chunks/registry-lkfb_E9O.mjs +53517 -0
- package/dist/agent/chunks/siteAdd-BqcYt8nu.mjs +118 -0
- package/dist/agent/chunks/sql-wasm-GjJDiefO.mjs +10 -0
- package/dist/agent/chunks/sqljs-DjSCStJ0.mjs +20 -0
- package/dist/agent/vendor/sql-wasm.cjs +188 -0
- package/dist/theme-assets/corporate-menu.js +266 -0
- package/dist/theme-assets/corporate-posts.js +571 -0
- package/dist/theme-assets/corporate.css +2 -0
- package/dist/theme-assets/default-menu.js +266 -0
- package/dist/theme-assets/default-posts.js +378 -0
- package/dist/theme-assets/default.css +1 -0
- package/dist/theme-assets/magazine-menu.js +227 -0
- package/dist/theme-assets/magazine-posts.js +361 -0
- package/dist/theme-assets/magazine.css +2 -0
- package/dist/theme-assets/portfolio-filters.js +104 -0
- package/dist/theme-assets/portfolio-menu.js +240 -0
- package/dist/theme-assets/portfolio-posts.js +358 -0
- package/dist/theme-assets/portfolio.css +2 -0
- package/dist/theme-assets/storefront-catalog.js +504 -0
- package/dist/theme-assets/storefront-menu.js +239 -0
- package/dist/theme-assets/storefront-posts.js +404 -0
- package/dist/theme-assets/storefront.css +2 -0
- package/dist/version.json +9 -0
- package/package.json +101 -4
- package/plugin/.claude-plugin/plugin.json +16 -0
- package/plugin/skills/agentcms-install/SKILL.md +110 -0
- package/plugin/skills/agentcms-install/references/cloudflare.md +15 -0
- package/plugin/skills/agentcms-install/references/flexweg.md +11 -0
- package/plugin/skills/agentcms-install/references/github.md +67 -0
- package/plugin/skills/agentcms-install/references/gitlab.md +15 -0
- package/plugin/skills/agentcms-install/references/s3.md +81 -0
- package/plugin/skills/agentcms-manage/SKILL.md +92 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storefront theme — runtime menu loader.
|
|
3
|
+
*
|
|
4
|
+
* Same contract as default / magazine / corporate: fetches /data/menu.json
|
|
5
|
+
* and paints every `[data-cms-menu]` container on the page. The storefront
|
|
6
|
+
* header ships TWO such containers — the inline horizontal nav (visible
|
|
7
|
+
* md+) and the off-canvas burger overlay (always present, opens on
|
|
8
|
+
* burger-toggle click). The storefront bottom-nav links also pick up the
|
|
9
|
+
* `aria-current="page"` state via the same `samePath` helper.
|
|
10
|
+
*
|
|
11
|
+
* Differences vs corporate:
|
|
12
|
+
* - Branding logo gets `h-8 w-auto` to fit the storefront header (h-16).
|
|
13
|
+
* - Burger menu links use the editorial serif + sage active color.
|
|
14
|
+
*/
|
|
15
|
+
(function () {
|
|
16
|
+
"use strict";
|
|
17
|
+
|
|
18
|
+
// ─── Base path ────────────────────────────────────────────────────
|
|
19
|
+
// Sites served from a sub-folder (e.g. GitHub Pages project sites)
|
|
20
|
+
// carry <meta name="x-cms-base" content="/repo/"> in <head>; no meta
|
|
21
|
+
// means the site lives at the domain root. URLs built at runtime from
|
|
22
|
+
// JSON data go through withBase() so they land under that folder.
|
|
23
|
+
var BASE = (function () {
|
|
24
|
+
var meta = document.querySelector('meta[name="x-cms-base"]');
|
|
25
|
+
var b = (meta && meta.getAttribute("content")) || "/";
|
|
26
|
+
if (b.charAt(0) !== "/") b = "/" + b;
|
|
27
|
+
if (b.charAt(b.length - 1) !== "/") b += "/";
|
|
28
|
+
return b;
|
|
29
|
+
})();
|
|
30
|
+
|
|
31
|
+
function withBase(url) {
|
|
32
|
+
if (BASE === "/" || !url) return url;
|
|
33
|
+
// Absolute URLs (http:, mailto:, data:, …), protocol-relative and
|
|
34
|
+
// fragment-only links pass through.
|
|
35
|
+
if (/^([a-z][a-z0-9+.-]*:|\/\/|#)/i.test(url)) return url;
|
|
36
|
+
if (url.indexOf(BASE) === 0 || url === BASE.slice(0, -1)) return url;
|
|
37
|
+
return BASE + (url.charAt(0) === "/" ? url.slice(1) : url);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function fetchMenu() {
|
|
41
|
+
return fetch(withBase("/data/menu.json"), { credentials: "omit" })
|
|
42
|
+
.then(function (res) {
|
|
43
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
44
|
+
return res.json();
|
|
45
|
+
})
|
|
46
|
+
.catch(function (err) {
|
|
47
|
+
console.warn("[cms] menu.json load failed:", err);
|
|
48
|
+
return null;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function samePath(href) {
|
|
53
|
+
try {
|
|
54
|
+
var url = new URL(href, window.location.origin);
|
|
55
|
+
var here = window.location.pathname.replace(/\/index\.html$/, "/");
|
|
56
|
+
var there = url.pathname.replace(/\/index\.html$/, "/");
|
|
57
|
+
return here === there;
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Picks the per-language label from item.labels (set by the admin
|
|
64
|
+
// in the menu builder) based on <html lang>, with a region-stripped
|
|
65
|
+
// fallback so "en-US" matches "en". Mono-lingual sites just return
|
|
66
|
+
// item.label.
|
|
67
|
+
function pickLabel(item) {
|
|
68
|
+
var lang = (document.documentElement.getAttribute("lang") || "").trim();
|
|
69
|
+
if (lang && item.labels && typeof item.labels[lang] === "string" && item.labels[lang]) {
|
|
70
|
+
return item.labels[lang];
|
|
71
|
+
}
|
|
72
|
+
if (lang && lang.indexOf("-") > 0) {
|
|
73
|
+
var base = lang.split("-")[0];
|
|
74
|
+
if (item.labels && typeof item.labels[base] === "string" && item.labels[base]) {
|
|
75
|
+
return item.labels[base];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return item.label || "";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderBurgerItem(item) {
|
|
82
|
+
var li = document.createElement("li");
|
|
83
|
+
var a = document.createElement("a");
|
|
84
|
+
a.href = withBase(item.href) || "#";
|
|
85
|
+
a.textContent = pickLabel(item);
|
|
86
|
+
if (samePath(a.href)) a.setAttribute("aria-current", "page");
|
|
87
|
+
li.appendChild(a);
|
|
88
|
+
if (item.children && item.children.length) {
|
|
89
|
+
var sub = document.createElement("ul");
|
|
90
|
+
sub.className = "menu-children";
|
|
91
|
+
item.children.forEach(function (child) {
|
|
92
|
+
sub.appendChild(renderBurgerItem(child));
|
|
93
|
+
});
|
|
94
|
+
li.appendChild(sub);
|
|
95
|
+
}
|
|
96
|
+
return li;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function renderInlineItem(item) {
|
|
100
|
+
var a = document.createElement("a");
|
|
101
|
+
a.href = withBase(item.href) || "#";
|
|
102
|
+
a.textContent = pickLabel(item);
|
|
103
|
+
var active = samePath(a.href);
|
|
104
|
+
if (active) {
|
|
105
|
+
a.setAttribute("aria-current", "page");
|
|
106
|
+
a.className =
|
|
107
|
+
"font-label-caps text-label-caps text-primary border-b-2 border-primary uppercase tracking-widest transition-colors";
|
|
108
|
+
} else {
|
|
109
|
+
a.className =
|
|
110
|
+
"font-label-caps text-label-caps text-on-surface-variant uppercase tracking-widest hover:text-primary transition-colors";
|
|
111
|
+
}
|
|
112
|
+
return a;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function paint(menu) {
|
|
116
|
+
if (!menu) return;
|
|
117
|
+
document.querySelectorAll("[data-cms-menu]").forEach(function (host) {
|
|
118
|
+
var key = host.getAttribute("data-cms-menu");
|
|
119
|
+
var items = (menu && menu[key]) || [];
|
|
120
|
+
var inline = host.hasAttribute("data-cms-menu-inline");
|
|
121
|
+
|
|
122
|
+
if (inline) {
|
|
123
|
+
host.innerHTML = "";
|
|
124
|
+
items.forEach(function (item) {
|
|
125
|
+
host.appendChild(renderInlineItem(item));
|
|
126
|
+
});
|
|
127
|
+
} else {
|
|
128
|
+
var list = host.querySelector("ul");
|
|
129
|
+
if (!list) return;
|
|
130
|
+
list.innerHTML = "";
|
|
131
|
+
items.forEach(function (item) {
|
|
132
|
+
list.appendChild(renderBurgerItem(item));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Hide empty inline / footer hosts so they don't reserve
|
|
137
|
+
// whitespace. Skip #burger-menu: it's `position: fixed` (no
|
|
138
|
+
// layout impact) AND any `hidden` attribute would `display:
|
|
139
|
+
// none` the panel, which the `.burger-menu.is-open { transform:
|
|
140
|
+
// translate(0) }` rule can never override — the burger toggle
|
|
141
|
+
// would open an invisible panel. Visibility of the burger UI is
|
|
142
|
+
// gated by the burger TOGGLE button below instead.
|
|
143
|
+
var isBurgerMenu = host.id === "burger-menu";
|
|
144
|
+
if (items.length === 0 && !isBurgerMenu) host.setAttribute("hidden", "");
|
|
145
|
+
else host.removeAttribute("hidden");
|
|
146
|
+
});
|
|
147
|
+
// Hide the burger TOGGLE button when the header menu has no
|
|
148
|
+
// items — otherwise users see a button that opens an empty
|
|
149
|
+
// off-canvas panel and conclude "the burger is broken".
|
|
150
|
+
var headerItems = (menu && menu.header) || [];
|
|
151
|
+
document.querySelectorAll(".burger-toggle").forEach(function (toggle) {
|
|
152
|
+
if (headerItems.length === 0) toggle.setAttribute("hidden", "");
|
|
153
|
+
else toggle.removeAttribute("hidden");
|
|
154
|
+
});
|
|
155
|
+
paintBranding(menu);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Swap the wordmark for an <img> when the theme has a logo
|
|
159
|
+
// configured. Falls back to the static text in every other case.
|
|
160
|
+
function paintBranding(menu) {
|
|
161
|
+
var branding = menu && menu.branding;
|
|
162
|
+
var logoUrl = branding && branding.logoUrl;
|
|
163
|
+
if (!logoUrl) return;
|
|
164
|
+
document.querySelectorAll("[data-cms-brand]").forEach(function (host) {
|
|
165
|
+
var alt = host.textContent || "";
|
|
166
|
+
var img = document.createElement("img");
|
|
167
|
+
img.src = withBase(logoUrl);
|
|
168
|
+
img.alt = alt;
|
|
169
|
+
img.className = "h-8 w-auto";
|
|
170
|
+
img.loading = "lazy";
|
|
171
|
+
host.innerHTML = "";
|
|
172
|
+
host.appendChild(img);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function ensureCloseButton(menu) {
|
|
177
|
+
if (menu.querySelector(".burger-close")) return menu.querySelector(".burger-close");
|
|
178
|
+
var close = document.createElement("button");
|
|
179
|
+
close.type = "button";
|
|
180
|
+
close.className = "burger-close";
|
|
181
|
+
close.setAttribute("aria-label", "Close menu");
|
|
182
|
+
close.innerHTML =
|
|
183
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
|
|
184
|
+
menu.insertBefore(close, menu.firstChild);
|
|
185
|
+
return close;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function ensureBackdrop() {
|
|
189
|
+
var existing = document.querySelector(".burger-backdrop");
|
|
190
|
+
if (existing) return existing;
|
|
191
|
+
var backdrop = document.createElement("div");
|
|
192
|
+
backdrop.className = "burger-backdrop";
|
|
193
|
+
backdrop.setAttribute("aria-hidden", "true");
|
|
194
|
+
document.body.appendChild(backdrop);
|
|
195
|
+
return backdrop;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function wireBurger() {
|
|
199
|
+
var toggle = document.querySelector(".burger-toggle");
|
|
200
|
+
var menu = document.getElementById("burger-menu");
|
|
201
|
+
if (!toggle || !menu) return;
|
|
202
|
+
var close = ensureCloseButton(menu);
|
|
203
|
+
var backdrop = ensureBackdrop();
|
|
204
|
+
function setOpen(open) {
|
|
205
|
+
toggle.setAttribute("aria-expanded", open ? "true" : "false");
|
|
206
|
+
menu.classList.toggle("is-open", open);
|
|
207
|
+
backdrop.classList.toggle("is-open", open);
|
|
208
|
+
document.body.classList.toggle("burger-open", open);
|
|
209
|
+
}
|
|
210
|
+
toggle.addEventListener("click", function () {
|
|
211
|
+
var isOpen = toggle.getAttribute("aria-expanded") === "true";
|
|
212
|
+
setOpen(!isOpen);
|
|
213
|
+
});
|
|
214
|
+
close.addEventListener("click", function () { setOpen(false); });
|
|
215
|
+
backdrop.addEventListener("click", function () { setOpen(false); });
|
|
216
|
+
document.addEventListener("keydown", function (e) {
|
|
217
|
+
if (e.key === "Escape") setOpen(false);
|
|
218
|
+
});
|
|
219
|
+
menu.addEventListener("click", function (e) {
|
|
220
|
+
if (e.target.tagName === "A") setOpen(false);
|
|
221
|
+
});
|
|
222
|
+
document.addEventListener("pointerdown", function (e) {
|
|
223
|
+
if (toggle.getAttribute("aria-expanded") !== "true") return;
|
|
224
|
+
if (menu.contains(e.target)) return;
|
|
225
|
+
if (toggle.contains(e.target)) return;
|
|
226
|
+
setOpen(false);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function ready(fn) {
|
|
231
|
+
if (document.readyState !== "loading") fn();
|
|
232
|
+
else document.addEventListener("DOMContentLoaded", fn);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
ready(function () {
|
|
236
|
+
wireBurger();
|
|
237
|
+
fetchMenu().then(paint);
|
|
238
|
+
});
|
|
239
|
+
})();
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storefront theme — runtime sidebar loader.
|
|
3
|
+
*
|
|
4
|
+
* Populates `[data-cms-related]` and `[data-cms-author-bio]` hosts
|
|
5
|
+
* from `/data/posts.json` and `/data/authors.json`. Same lifecycle
|
|
6
|
+
* as the corporate / magazine / default themes.
|
|
7
|
+
*
|
|
8
|
+
* Differences vs corporate:
|
|
9
|
+
* - Related items render as a small product-card grid (image +
|
|
10
|
+
* name + price), not a text list. Drives the "Curated pairings"
|
|
11
|
+
* row at the bottom of the single-post template.
|
|
12
|
+
* - Newsletter / contact form wiring is the same convention
|
|
13
|
+
* (`[data-cms-form]`) — copied verbatim.
|
|
14
|
+
*/
|
|
15
|
+
(function () {
|
|
16
|
+
"use strict";
|
|
17
|
+
|
|
18
|
+
// ─── Base path ────────────────────────────────────────────────────
|
|
19
|
+
// Sites served from a sub-folder (e.g. GitHub Pages project sites)
|
|
20
|
+
// carry <meta name="x-cms-base" content="/repo/"> in <head>; no meta
|
|
21
|
+
// means the site lives at the domain root. URLs built at runtime from
|
|
22
|
+
// JSON data go through withBase() so they land under that folder.
|
|
23
|
+
var BASE = (function () {
|
|
24
|
+
var meta = document.querySelector('meta[name="x-cms-base"]');
|
|
25
|
+
var b = (meta && meta.getAttribute("content")) || "/";
|
|
26
|
+
if (b.charAt(0) !== "/") b = "/" + b;
|
|
27
|
+
if (b.charAt(b.length - 1) !== "/") b += "/";
|
|
28
|
+
return b;
|
|
29
|
+
})();
|
|
30
|
+
|
|
31
|
+
function withBase(url) {
|
|
32
|
+
if (BASE === "/" || !url) return url;
|
|
33
|
+
// Absolute URLs (http:, mailto:, data:, …), protocol-relative and
|
|
34
|
+
// fragment-only links pass through.
|
|
35
|
+
if (/^([a-z][a-z0-9+.-]*:|\/\/|#)/i.test(url)) return url;
|
|
36
|
+
if (url.indexOf(BASE) === 0 || url === BASE.slice(0, -1)) return url;
|
|
37
|
+
return BASE + (url.charAt(0) === "/" ? url.slice(1) : url);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// "/repo/fr/x.html" → "/fr/x.html" so path parsing ignores the base.
|
|
41
|
+
function stripBase(pathname) {
|
|
42
|
+
if (BASE === "/") return pathname;
|
|
43
|
+
if (pathname.indexOf(BASE) === 0) return "/" + pathname.slice(BASE.length);
|
|
44
|
+
if (pathname === BASE.slice(0, -1)) return "/";
|
|
45
|
+
return pathname;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var SOCIAL_ICON_PATHS = {
|
|
49
|
+
twitter: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z",
|
|
50
|
+
linkedin: "M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z",
|
|
51
|
+
instagram: "M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z",
|
|
52
|
+
website: "M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95a15.65 15.65 0 0 0-1.38-3.56A8.03 8.03 0 0 1 18.92 8zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56A7.987 7.987 0 0 1 5.08 16zm2.95-8H5.08a7.987 7.987 0 0 1 4.33-3.56A15.65 15.65 0 0 0 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"
|
|
53
|
+
};
|
|
54
|
+
function socialIconSvg(network) {
|
|
55
|
+
var path = SOCIAL_ICON_PATHS[network];
|
|
56
|
+
if (!path) return "";
|
|
57
|
+
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="' + path + '"/></svg>';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function fetchJson(path) {
|
|
61
|
+
return fetch(withBase(path), { credentials: "omit" })
|
|
62
|
+
.then(function (res) {
|
|
63
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
64
|
+
return res.json();
|
|
65
|
+
})
|
|
66
|
+
.catch(function (err) {
|
|
67
|
+
console.warn("[cms] " + path + " load failed:", err);
|
|
68
|
+
return null;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── Related products ─────────────────────────────────────────────
|
|
73
|
+
// DOM emitted matches the storefront single-post mockup — small grid
|
|
74
|
+
// of product cards with image + name + price.
|
|
75
|
+
function renderRelatedItem(entry) {
|
|
76
|
+
var a = document.createElement("a");
|
|
77
|
+
a.href = withBase("/" + entry.url);
|
|
78
|
+
a.className = "storefront-related-item";
|
|
79
|
+
|
|
80
|
+
var thumb = document.createElement("div");
|
|
81
|
+
thumb.className = "storefront-related-thumb";
|
|
82
|
+
if (entry.heroUrl) {
|
|
83
|
+
var img = document.createElement("img");
|
|
84
|
+
img.src = withBase(entry.heroUrl);
|
|
85
|
+
img.alt = entry.heroAlt || entry.title || "";
|
|
86
|
+
img.loading = "lazy";
|
|
87
|
+
thumb.appendChild(img);
|
|
88
|
+
}
|
|
89
|
+
a.appendChild(thumb);
|
|
90
|
+
|
|
91
|
+
var title = document.createElement("p");
|
|
92
|
+
title.className = "storefront-related-title";
|
|
93
|
+
title.textContent = entry.title || "";
|
|
94
|
+
a.appendChild(title);
|
|
95
|
+
|
|
96
|
+
if (entry.priceLabel) {
|
|
97
|
+
var price = document.createElement("p");
|
|
98
|
+
price.className = "storefront-related-price";
|
|
99
|
+
price.textContent = entry.priceLabel;
|
|
100
|
+
a.appendChild(price);
|
|
101
|
+
}
|
|
102
|
+
return a;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function renderRelated(host, posts) {
|
|
106
|
+
var termId = host.getAttribute("data-cms-term-id") || "";
|
|
107
|
+
var currentId = host.getAttribute("data-cms-current-id") || "";
|
|
108
|
+
var limit = parseInt(host.getAttribute("data-cms-limit") || "4", 10);
|
|
109
|
+
var label = host.getAttribute("data-cms-label") || "Curated pairings";
|
|
110
|
+
|
|
111
|
+
var matching = [];
|
|
112
|
+
if (termId) {
|
|
113
|
+
for (var i = 0; i < posts.length && matching.length < limit; i++) {
|
|
114
|
+
var p = posts[i];
|
|
115
|
+
if (p.id === currentId) continue;
|
|
116
|
+
if (p.type !== "post") continue;
|
|
117
|
+
if (p.primaryTermId !== termId) continue;
|
|
118
|
+
matching.push(p);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (matching.length === 0) {
|
|
122
|
+
for (var j = 0; j < posts.length && matching.length < limit; j++) {
|
|
123
|
+
var pp = posts[j];
|
|
124
|
+
if (pp.id === currentId) continue;
|
|
125
|
+
if (pp.type !== "post") continue;
|
|
126
|
+
matching.push(pp);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (matching.length === 0) {
|
|
130
|
+
host.setAttribute("hidden", "");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
host.removeAttribute("hidden");
|
|
135
|
+
host.innerHTML = "";
|
|
136
|
+
var headingEl = document.createElement("h3");
|
|
137
|
+
headingEl.className = "storefront-related-heading";
|
|
138
|
+
headingEl.textContent = label;
|
|
139
|
+
host.appendChild(headingEl);
|
|
140
|
+
var list = document.createElement("div");
|
|
141
|
+
list.className = "storefront-related-list";
|
|
142
|
+
matching.forEach(function (entry) {
|
|
143
|
+
list.appendChild(renderRelatedItem(entry));
|
|
144
|
+
});
|
|
145
|
+
host.appendChild(list);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── Author bio ──────────────────────────────────────────────────
|
|
149
|
+
function authorAvatar(name, url) {
|
|
150
|
+
if (!url) {
|
|
151
|
+
var span = document.createElement("span");
|
|
152
|
+
span.className = "storefront-bio-avatar";
|
|
153
|
+
span.style.display = "inline-flex";
|
|
154
|
+
span.style.alignItems = "center";
|
|
155
|
+
span.style.justifyContent = "center";
|
|
156
|
+
span.style.background = "rgb(var(--color-secondary-fixed))";
|
|
157
|
+
span.style.color = "rgb(var(--color-secondary))";
|
|
158
|
+
span.style.fontWeight = "700";
|
|
159
|
+
span.setAttribute("aria-hidden", "true");
|
|
160
|
+
span.textContent = (function () {
|
|
161
|
+
if (!name) return "?";
|
|
162
|
+
var parts = name.trim().split(/\s+/);
|
|
163
|
+
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
164
|
+
return ((parts[0][0] || "") + (parts[parts.length - 1][0] || "")).toUpperCase();
|
|
165
|
+
})();
|
|
166
|
+
return span;
|
|
167
|
+
}
|
|
168
|
+
var img = document.createElement("img");
|
|
169
|
+
img.className = "storefront-bio-avatar";
|
|
170
|
+
img.src = withBase(url);
|
|
171
|
+
img.alt = name || "";
|
|
172
|
+
img.loading = "lazy";
|
|
173
|
+
return img;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function renderAuthorBio(host, authors) {
|
|
177
|
+
var authorId = host.getAttribute("data-cms-author-id") || "";
|
|
178
|
+
if (!authorId) return;
|
|
179
|
+
var entry = authors[authorId];
|
|
180
|
+
if (!entry) return;
|
|
181
|
+
|
|
182
|
+
host.innerHTML = "";
|
|
183
|
+
var eyebrow = document.createElement("h4");
|
|
184
|
+
eyebrow.className = "storefront-bio-eyebrow";
|
|
185
|
+
eyebrow.textContent = host.getAttribute("data-cms-bio-label") || "Author";
|
|
186
|
+
host.appendChild(eyebrow);
|
|
187
|
+
|
|
188
|
+
var row = document.createElement("div");
|
|
189
|
+
row.className = "storefront-bio-row";
|
|
190
|
+
row.appendChild(authorAvatar(entry.displayName, entry.avatar));
|
|
191
|
+
|
|
192
|
+
var nameWrap = document.createElement("div");
|
|
193
|
+
var nameEl;
|
|
194
|
+
if (entry.url) {
|
|
195
|
+
nameEl = document.createElement("a");
|
|
196
|
+
nameEl.href = withBase("/" + entry.url);
|
|
197
|
+
} else {
|
|
198
|
+
nameEl = document.createElement("p");
|
|
199
|
+
}
|
|
200
|
+
nameEl.className = "storefront-bio-name";
|
|
201
|
+
nameEl.textContent = entry.displayName || "";
|
|
202
|
+
nameWrap.appendChild(nameEl);
|
|
203
|
+
|
|
204
|
+
if (entry.title) {
|
|
205
|
+
var role = document.createElement("p");
|
|
206
|
+
role.className = "storefront-bio-title";
|
|
207
|
+
role.textContent = entry.title;
|
|
208
|
+
nameWrap.appendChild(role);
|
|
209
|
+
}
|
|
210
|
+
row.appendChild(nameWrap);
|
|
211
|
+
host.appendChild(row);
|
|
212
|
+
|
|
213
|
+
if (entry.bio) {
|
|
214
|
+
var bio = document.createElement("p");
|
|
215
|
+
bio.className = "storefront-bio-bio";
|
|
216
|
+
bio.textContent = entry.bio;
|
|
217
|
+
host.appendChild(bio);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (entry.socials && entry.socials.length) {
|
|
221
|
+
var socials = document.createElement("div");
|
|
222
|
+
socials.className = "storefront-bio-socials";
|
|
223
|
+
for (var i = 0; i < entry.socials.length; i++) {
|
|
224
|
+
var s = entry.socials[i];
|
|
225
|
+
var a = document.createElement("a");
|
|
226
|
+
a.className = "storefront-bio-social";
|
|
227
|
+
a.href = s.url;
|
|
228
|
+
a.target = "_blank";
|
|
229
|
+
a.rel = "noopener noreferrer";
|
|
230
|
+
a.setAttribute("aria-label", s.network);
|
|
231
|
+
a.innerHTML = socialIconSvg(s.network);
|
|
232
|
+
socials.appendChild(a);
|
|
233
|
+
}
|
|
234
|
+
host.appendChild(socials);
|
|
235
|
+
}
|
|
236
|
+
host.removeAttribute("hidden");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ─── Newsletter / contact form wiring ────────────────────────────
|
|
240
|
+
// Same contract as corporate's contact-form: `[data-cms-form]` with
|
|
241
|
+
// mode + endpoint + mailto data attrs.
|
|
242
|
+
function asUrlEncoded(formData) {
|
|
243
|
+
var pairs = [];
|
|
244
|
+
formData.forEach(function (value, key) {
|
|
245
|
+
pairs.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
|
|
246
|
+
});
|
|
247
|
+
return pairs.join("&");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function showFormStatus(form, kind) {
|
|
251
|
+
["success", "error"].forEach(function (k) {
|
|
252
|
+
var el = form.querySelector("[data-cms-form-" + k + "]");
|
|
253
|
+
if (el) {
|
|
254
|
+
if (k === kind) el.removeAttribute("hidden");
|
|
255
|
+
else el.setAttribute("hidden", "");
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function buildMailto(form, address) {
|
|
261
|
+
var data = new FormData(form);
|
|
262
|
+
var subject = data.get("subject") || "Newsletter signup";
|
|
263
|
+
var lines = [];
|
|
264
|
+
data.forEach(function (value, key) {
|
|
265
|
+
if (key === "subject" || key === "consent") return;
|
|
266
|
+
lines.push(key + ": " + value);
|
|
267
|
+
});
|
|
268
|
+
return (
|
|
269
|
+
"mailto:" + encodeURIComponent(address) +
|
|
270
|
+
"?subject=" + encodeURIComponent(String(subject)) +
|
|
271
|
+
"&body=" + encodeURIComponent(lines.join("\n\n"))
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function handleFormSubmit(form, mode, endpoint, mailtoAddress, event) {
|
|
276
|
+
if (mode === "mailto") {
|
|
277
|
+
if (!mailtoAddress) return;
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
window.location.href = buildMailto(form, mailtoAddress);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (!endpoint) return;
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
var data = new FormData(form);
|
|
285
|
+
var submitButton = form.querySelector("button[type='submit']");
|
|
286
|
+
if (submitButton) submitButton.disabled = true;
|
|
287
|
+
fetch(withBase(endpoint), {
|
|
288
|
+
method: "POST",
|
|
289
|
+
headers: {
|
|
290
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
291
|
+
Accept: "application/json"
|
|
292
|
+
},
|
|
293
|
+
body: asUrlEncoded(data)
|
|
294
|
+
})
|
|
295
|
+
.then(function (res) {
|
|
296
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
297
|
+
showFormStatus(form, "success");
|
|
298
|
+
form.reset();
|
|
299
|
+
})
|
|
300
|
+
.catch(function (err) {
|
|
301
|
+
console.warn("[cms] form submit failed:", err);
|
|
302
|
+
showFormStatus(form, "error");
|
|
303
|
+
})
|
|
304
|
+
.finally(function () {
|
|
305
|
+
if (submitButton) submitButton.disabled = false;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function wireForms() {
|
|
310
|
+
document.querySelectorAll("[data-cms-form]").forEach(function (form) {
|
|
311
|
+
var mode = form.getAttribute("data-cms-form") || "endpoint";
|
|
312
|
+
var endpoint = form.getAttribute("data-cms-form-endpoint") || "";
|
|
313
|
+
var mailtoAddress = form.getAttribute("data-cms-form-mailto") || "";
|
|
314
|
+
form.addEventListener("submit", function (event) {
|
|
315
|
+
handleFormSubmit(form, mode, endpoint, mailtoAddress, event);
|
|
316
|
+
});
|
|
317
|
+
form.querySelectorAll("input, textarea, select").forEach(function (input) {
|
|
318
|
+
function update() {
|
|
319
|
+
var group = input.closest(".floating-label-group");
|
|
320
|
+
if (!group) return;
|
|
321
|
+
if (input.value && input.value.length > 0) group.classList.add("is-filled");
|
|
322
|
+
else group.classList.remove("is-filled");
|
|
323
|
+
}
|
|
324
|
+
input.addEventListener("input", update);
|
|
325
|
+
input.addEventListener("change", update);
|
|
326
|
+
update();
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function paintRelated(postsBlob) {
|
|
332
|
+
if (!postsBlob || !Array.isArray(postsBlob.posts)) return;
|
|
333
|
+
document.querySelectorAll("[data-cms-related]").forEach(function (host) {
|
|
334
|
+
renderRelated(host, postsBlob.posts);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ─── Per-category row sliders ─────────────────────────────────────
|
|
339
|
+
// Wires chevron buttons emitted by renderProductGrid in slider
|
|
340
|
+
// mode. Each track's [data-cms-row-track] sibling has [data-cms-
|
|
341
|
+
// row-prev] and [-next] chevrons; click → scroll one card width
|
|
342
|
+
// + gap. Same pattern as corporate's featured-posts chevrons.
|
|
343
|
+
function wireCategoryRowChevrons() {
|
|
344
|
+
document.querySelectorAll("[data-cms-row-track]").forEach(function (track) {
|
|
345
|
+
var wrap = track.parentElement;
|
|
346
|
+
if (!wrap) return;
|
|
347
|
+
var prev = wrap.querySelector("[data-cms-row-prev]");
|
|
348
|
+
var next = wrap.querySelector("[data-cms-row-next]");
|
|
349
|
+
function step(dir) {
|
|
350
|
+
var cards = track.children;
|
|
351
|
+
if (!cards || cards.length === 0) return;
|
|
352
|
+
var card = cards[0];
|
|
353
|
+
var width = card.getBoundingClientRect().width;
|
|
354
|
+
var styles = window.getComputedStyle(track);
|
|
355
|
+
var gap = parseFloat(styles.columnGap || styles.gap || "0") || 0;
|
|
356
|
+
track.scrollBy({ left: dir * (width + gap), behavior: "smooth" });
|
|
357
|
+
}
|
|
358
|
+
if (prev) prev.addEventListener("click", function () { step(-1); });
|
|
359
|
+
if (next) next.addEventListener("click", function () { step(1); });
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function paintAuthorBios(authorsBlob) {
|
|
364
|
+
if (!authorsBlob || !authorsBlob.authors) return;
|
|
365
|
+
document.querySelectorAll("[data-cms-author-bio]").forEach(function (host) {
|
|
366
|
+
renderAuthorBio(host, authorsBlob.authors);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function ready(fn) {
|
|
371
|
+
if (document.readyState !== "loading") fn();
|
|
372
|
+
else document.addEventListener("DOMContentLoaded", fn);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Multi-language helper — same locale prefix detection as the
|
|
376
|
+
// other themes (see default/posts-loader.js for the rationale).
|
|
377
|
+
function detectLocalePrefix() {
|
|
378
|
+
var path = stripBase(window.location.pathname).replace(/^\/+/, "");
|
|
379
|
+
var first = path.split("/")[0];
|
|
380
|
+
if (!first) return null;
|
|
381
|
+
if (/^[a-z]{2}(-[a-z]{2})?$/.test(first.toLowerCase())) return first.toLowerCase();
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
function fetchWithLocaleFallback(filename) {
|
|
385
|
+
var locale = detectLocalePrefix();
|
|
386
|
+
if (!locale) return fetchJson("/data/" + filename);
|
|
387
|
+
return fetchJson("/" + locale + "/data/" + filename).then(function (data) {
|
|
388
|
+
if (data) return data;
|
|
389
|
+
return fetchJson("/data/" + filename);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
ready(function () {
|
|
394
|
+
wireForms();
|
|
395
|
+
wireCategoryRowChevrons();
|
|
396
|
+
Promise.all([
|
|
397
|
+
fetchWithLocaleFallback("posts.json"),
|
|
398
|
+
fetchWithLocaleFallback("authors.json")
|
|
399
|
+
]).then(function (results) {
|
|
400
|
+
paintRelated(results[0]);
|
|
401
|
+
paintAuthorBios(results[1]);
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
})();
|