@sevenfold/setto-client 0.35.0 → 0.36.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.
@@ -21461,8 +21461,9 @@ class ThemeStore {
21461
21461
  }
21462
21462
  const PAGES_MANIFEST_VERSION = 1;
21463
21463
  const PAGES_PATH = "src/content/pages.json";
21464
- const BUILTIN_SECTION_TYPES = ["text"];
21464
+ const BUILTIN_SECTION_TYPES = ["text", "blog", "blog-latest"];
21465
21465
  const ID = /^[a-z0-9][a-z0-9-]{0,62}$/;
21466
+ const POST_SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
21466
21467
  const PATH = /^\/(?:[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*)?$/;
21467
21468
  function isRecord$1(value) {
21468
21469
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -21529,6 +21530,15 @@ function parseManifest(input) {
21529
21530
  sections
21530
21531
  });
21531
21532
  });
21533
+ const blogs = pages.flatMap(
21534
+ (p, i) => p.sections.flatMap((s, j) => s.type === "blog" ? [{ page: p, at: `pages[${i}].sections[${j}]` }] : [])
21535
+ );
21536
+ if (blogs.length > 1) {
21537
+ issues.push({ at: blogs[1].at, message: "a site has one blog; another blog section is already on a page" });
21538
+ }
21539
+ for (const blog of blogs) {
21540
+ if (blog.page.path === "/") issues.push({ at: blog.at, message: "the blog cannot be the front page" });
21541
+ }
21532
21542
  const menu = parseMenu(input.menu, "menu", issues);
21533
21543
  const footer2 = parseMenu(input.footer, "footer", issues);
21534
21544
  if (issues.length > 0) return { ok: false, issues };
@@ -21572,7 +21582,23 @@ function normalisePath(path2) {
21572
21582
  }
21573
21583
  function pageForPath(manifest, path2) {
21574
21584
  const wanted = normalisePath(path2);
21575
- return manifest.pages.find((p) => p.path === wanted) ?? null;
21585
+ return manifest.pages.find((p) => p.path === wanted) ?? (blogSlugForPath(manifest, path2) ? blogPage(manifest).page : null);
21586
+ }
21587
+ function blogPage(manifest) {
21588
+ for (const page of manifest.pages) {
21589
+ const section = page.sections.find((s) => s.type === "blog");
21590
+ if (section) return { page, section };
21591
+ }
21592
+ return null;
21593
+ }
21594
+ function blogSlugForPath(manifest, path2) {
21595
+ const blog = blogPage(manifest);
21596
+ if (!blog || blog.page.path === "/") return null;
21597
+ const wanted = normalisePath(path2);
21598
+ const prefix = `${blog.page.path}/`;
21599
+ if (!wanted.startsWith(prefix)) return null;
21600
+ const slug = wanted.slice(prefix.length);
21601
+ return slug.length <= 80 && POST_SLUG.test(slug) ? slug : null;
21576
21602
  }
21577
21603
  function pageById(manifest, id) {
21578
21604
  return manifest.pages.find((p) => p.id === id) ?? null;
@@ -22124,7 +22150,7 @@ function withoutCheckoutIntent() {
22124
22150
  }
22125
22151
  const FAB_SIZE = 52;
22126
22152
  const FAB_INSET = 16;
22127
- const STYLE_ID$2 = "setto-edit-layout";
22153
+ const STYLE_ID$3 = "setto-edit-layout";
22128
22154
  const HTML_CLASS = "setto-edit-mode";
22129
22155
  function useSettoDocumentLayout(active) {
22130
22156
  useEffect(() => {
@@ -22133,10 +22159,10 @@ function useSettoDocumentLayout(active) {
22133
22159
  html2.classList.add(HTML_CLASS);
22134
22160
  html2.style.setProperty("--setto-fab-size", `${FAB_SIZE}px`);
22135
22161
  html2.style.setProperty("--setto-fab-inset", `${FAB_INSET}px`);
22136
- let styleEl = document.getElementById(STYLE_ID$2);
22162
+ let styleEl = document.getElementById(STYLE_ID$3);
22137
22163
  if (!styleEl) {
22138
22164
  styleEl = document.createElement("style");
22139
- styleEl.id = STYLE_ID$2;
22165
+ styleEl.id = STYLE_ID$3;
22140
22166
  styleEl.textContent = `
22141
22167
  html.setto-edit-mode {
22142
22168
  width: 100%;
@@ -33040,7 +33066,7 @@ function formatHeadingAsSetext(node2, state) {
33040
33066
  );
33041
33067
  }
33042
33068
  function heading(node2, _, state, info) {
33043
- const rank = Math.max(Math.min(6, node2.depth || 1), 1);
33069
+ const rank2 = Math.max(Math.min(6, node2.depth || 1), 1);
33044
33070
  const tracker = state.createTracker(info);
33045
33071
  if (formatHeadingAsSetext(node2, state)) {
33046
33072
  const exit3 = state.enter("headingSetext");
@@ -33052,14 +33078,14 @@ function heading(node2, _, state, info) {
33052
33078
  });
33053
33079
  subexit2();
33054
33080
  exit3();
33055
- return value2 + "\n" + (rank === 1 ? "=" : "-").repeat(
33081
+ return value2 + "\n" + (rank2 === 1 ? "=" : "-").repeat(
33056
33082
  // The whole size…
33057
33083
  value2.length - // Minus the position of the character after the last EOL (or
33058
33084
  // 0 if there is none)…
33059
33085
  (Math.max(value2.lastIndexOf("\r"), value2.lastIndexOf("\n")) + 1)
33060
33086
  );
33061
33087
  }
33062
- const sequence = "#".repeat(rank);
33088
+ const sequence = "#".repeat(rank2);
33063
33089
  const exit2 = state.enter("headingAtx");
33064
33090
  const subexit = state.enter("phrasing");
33065
33091
  tracker.move(sequence + " ");
@@ -38003,12 +38029,12 @@ function SettoImage({ srcKey, alt, className, style, ...rest }) {
38003
38029
  )
38004
38030
  ] });
38005
38031
  }
38006
- const STYLE_ID$1 = "setto-repeater-styles";
38032
+ const STYLE_ID$2 = "setto-repeater-styles";
38007
38033
  function useRepeaterStyles() {
38008
38034
  useEffect(() => {
38009
- if (document.getElementById(STYLE_ID$1)) return;
38035
+ if (document.getElementById(STYLE_ID$2)) return;
38010
38036
  const el = document.createElement("style");
38011
- el.id = STYLE_ID$1;
38037
+ el.id = STYLE_ID$2;
38012
38038
  el.textContent = `
38013
38039
  .setto-repeater-remove {
38014
38040
  opacity: 0;
@@ -38134,6 +38160,695 @@ const removeBtnStyle = {
38134
38160
  boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
38135
38161
  fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif'
38136
38162
  };
38163
+ function usePageManifest() {
38164
+ const { pageStore, config } = useSetto();
38165
+ const snap = useSyncExternalStore(
38166
+ (cb) => pageStore?.subscribe(() => cb()) ?? (() => {
38167
+ }),
38168
+ () => pageStore?.snapshot() ?? EMPTY_PAGE_STORE_SNAPSHOT,
38169
+ () => pageStore?.snapshot() ?? EMPTY_PAGE_STORE_SNAPSHOT
38170
+ );
38171
+ return pageStore ? snap.manifest : bundledManifest(config.pages);
38172
+ }
38173
+ function useSettoMenu(list2 = "menu") {
38174
+ const manifest = usePageManifest();
38175
+ return manifest ? visibleMenu(manifest, list2) : null;
38176
+ }
38177
+ function SettoPage({ pageId, path: path2, sections = {}, builtins, fallback = null }) {
38178
+ const manifest = usePageManifest();
38179
+ const { editMode } = useSetto();
38180
+ if (!manifest) {
38181
+ if (!pageId && path2 !== void 0 && path2 !== "/" && path2 !== "") return /* @__PURE__ */ jsx(Fragment, { children: fallback });
38182
+ return /* @__PURE__ */ jsx(Fragment, { children: Object.entries(sections).map(([id, Component]) => /* @__PURE__ */ jsx(Component, {}, id)) });
38183
+ }
38184
+ const page = pageId ? pageById(manifest, pageId) : pageForPath(manifest, path2 ?? "/");
38185
+ if (!page) {
38186
+ if (pageId) {
38187
+ return /* @__PURE__ */ jsx(Fragment, { children: Object.entries(sections).map(([id, Component]) => /* @__PURE__ */ jsx(Component, {}, id)) });
38188
+ }
38189
+ return /* @__PURE__ */ jsx(Fragment, { children: fallback });
38190
+ }
38191
+ if (page.hidden && !editMode) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
38192
+ return /* @__PURE__ */ jsx(PageBody, { page, sections, builtins, editMode, manifest });
38193
+ }
38194
+ function PageBody({
38195
+ page,
38196
+ sections,
38197
+ builtins,
38198
+ editMode,
38199
+ manifest
38200
+ }) {
38201
+ usePageTitle(page);
38202
+ const HiddenPageNote = useSettoChromeSlot("hidden-page");
38203
+ const listed = new Set(manifest.pages.flatMap((p) => p.sections.map((s) => s.id)));
38204
+ const unlisted = Object.keys(sections).filter((id) => !listed.has(id));
38205
+ const firstVisible = page.sections.find((s) => !s.hidden && (s.type || sections[s.id]))?.id;
38206
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
38207
+ page.hidden && editMode ? HiddenPageNote ? /* @__PURE__ */ jsx(HiddenPageNote, { pageId: page.id }) : /* @__PURE__ */ jsx(HiddenNote, { text: "Denne siden er skjult for besøkende." }) : null,
38208
+ page.sections.map((section) => /* @__PURE__ */ jsx(Fragment$1, { children: renderSection(section, sections, builtins, editMode, section.id === firstVisible) }, section.id)),
38209
+ unlisted.map((id) => {
38210
+ const Component = sections[id];
38211
+ return /* @__PURE__ */ jsx(Component, {}, id);
38212
+ })
38213
+ ] });
38214
+ }
38215
+ function renderSection(section, sections, builtins, editMode, first) {
38216
+ if (section.hidden) {
38217
+ return editMode ? /* @__PURE__ */ jsx(HiddenSection, { section }) : null;
38218
+ }
38219
+ if (section.type) {
38220
+ const Builtin = builtins?.[section.type] ?? BUILTIN_RENDERERS[section.type];
38221
+ return /* @__PURE__ */ jsx(Builtin, { sectionId: section.id, first });
38222
+ }
38223
+ const Component = sections[section.id];
38224
+ if (!Component) {
38225
+ warnOnce(section.id);
38226
+ return null;
38227
+ }
38228
+ return /* @__PURE__ */ jsx(Component, {});
38229
+ }
38230
+ const warned = /* @__PURE__ */ new Set();
38231
+ function warnOnce(id) {
38232
+ if (warned.has(id)) return;
38233
+ warned.add(id);
38234
+ console.warn(`[setto] section "${id}" is in pages.json but the page does not provide it`);
38235
+ }
38236
+ function HiddenSection({ section }) {
38237
+ const { pageStore } = useSetto();
38238
+ const Offered = useSettoChromeSlot("hidden-section");
38239
+ const label = section.label ?? section.id;
38240
+ if (Offered) return /* @__PURE__ */ jsx(Offered, { sectionId: section.id, label });
38241
+ return /* @__PURE__ */ jsxs("div", { "data-setto-ui": true, style: hiddenBarStyle, children: [
38242
+ /* @__PURE__ */ jsxs("span", { children: [
38243
+ "Skjult: ",
38244
+ label
38245
+ ] }),
38246
+ pageStore ? /* @__PURE__ */ jsx(
38247
+ "button",
38248
+ {
38249
+ type: "button",
38250
+ "data-setto-ui": true,
38251
+ style: hiddenBtnStyle,
38252
+ onClick: () => pageStore.apply([{ op: "show_section", sectionId: section.id }]),
38253
+ children: "Vis"
38254
+ }
38255
+ ) : null
38256
+ ] });
38257
+ }
38258
+ function HiddenNote({ text: text2 }) {
38259
+ return /* @__PURE__ */ jsx("div", { "data-setto-ui": true, style: hiddenBarStyle, children: /* @__PURE__ */ jsx("span", { children: text2 }) });
38260
+ }
38261
+ function usePageTitle(page) {
38262
+ const { t } = useTranslation();
38263
+ const original = useRef(null);
38264
+ const title = page.path === "/" ? null : t(page.titleKey);
38265
+ useEffect(() => {
38266
+ if (!title || typeof document === "undefined") return;
38267
+ original.current ??= document.title;
38268
+ const base = original.current;
38269
+ document.title = base ? `${title} · ${base}` : title;
38270
+ return () => {
38271
+ document.title = base;
38272
+ };
38273
+ }, [title]);
38274
+ }
38275
+ function SettoTextSection({ sectionId, first }) {
38276
+ const keys2 = builtinKeys(sectionId);
38277
+ const { i18n } = useTranslation();
38278
+ const { store, editMode } = useSetto();
38279
+ const intro2 = store ? store.get(keys2.intro, i18n.language) : i18n.t(keys2.intro, { defaultValue: "" });
38280
+ const Heading = first ? "h1" : "h2";
38281
+ return /* @__PURE__ */ jsx(SettoSection, { sectionId, style: textSectionStyle, children: /* @__PURE__ */ jsxs("div", { style: textInnerStyle, children: [
38282
+ /* @__PURE__ */ jsx(Heading, { style: first ? textH1Style : textH2Style, children: /* @__PURE__ */ jsx(T, { k: keys2.heading }) }),
38283
+ intro2 || editMode ? /* @__PURE__ */ jsx("p", { style: textIntroStyle, children: /* @__PURE__ */ jsx(T, { k: keys2.intro }) }) : null,
38284
+ /* @__PURE__ */ jsx(
38285
+ SettoRepeater,
38286
+ {
38287
+ itemsKey: keys2.items,
38288
+ defaultItem: { heading: "Overskrift", text: "Tekst" },
38289
+ itemLabel: "avsnitt",
38290
+ children: (itemKey) => /* @__PURE__ */ jsx(TextItem, { sectionId, itemKey })
38291
+ }
38292
+ )
38293
+ ] }) });
38294
+ }
38295
+ function TextItem({ sectionId, itemKey }) {
38296
+ const keys2 = builtinKeys(sectionId);
38297
+ const { i18n } = useTranslation();
38298
+ const { store, editMode } = useSetto();
38299
+ const heading2 = store ? store.get(keys2.itemHeading(itemKey), i18n.language) : i18n.t(keys2.itemHeading(itemKey), { defaultValue: "" });
38300
+ return /* @__PURE__ */ jsxs("div", { style: textItemStyle, children: [
38301
+ heading2 || editMode ? /* @__PURE__ */ jsx("h3", { style: textH3Style, children: /* @__PURE__ */ jsx(T, { k: keys2.itemHeading(itemKey) }) }) : null,
38302
+ /* @__PURE__ */ jsx("p", { style: textParagraphStyle, children: /* @__PURE__ */ jsx(T, { k: keys2.itemText(itemKey) }) })
38303
+ ] });
38304
+ }
38305
+ const BUILTIN_RENDERERS = {
38306
+ text: SettoTextSection,
38307
+ blog: SettoBlogSection,
38308
+ "blog-latest": SettoBlogLatest
38309
+ };
38310
+ const textSectionStyle = { padding: "clamp(48px, 8vw, 96px) 16px" };
38311
+ const textInnerStyle = { maxWidth: "68ch", margin: "0 auto" };
38312
+ const textH1Style = {
38313
+ fontFamily: "var(--setto-display-font, inherit)",
38314
+ fontSize: "clamp(2rem, 4vw, 2.75rem)",
38315
+ lineHeight: 1.15,
38316
+ fontWeight: 600,
38317
+ margin: "0 0 0.75em"
38318
+ };
38319
+ const textH2Style = { ...textH1Style, fontSize: "clamp(1.5rem, 3vw, 2rem)" };
38320
+ const textH3Style = {
38321
+ fontFamily: "var(--setto-display-font, inherit)",
38322
+ fontSize: "1.125rem",
38323
+ lineHeight: 1.3,
38324
+ fontWeight: 600,
38325
+ margin: "0 0 0.4em"
38326
+ };
38327
+ const textIntroStyle = { fontSize: "1.125rem", lineHeight: 1.6, opacity: 0.85, margin: "0 0 2em" };
38328
+ const textItemStyle = { margin: "0 0 1.75em" };
38329
+ const textParagraphStyle = { lineHeight: 1.7, margin: 0, whiteSpace: "pre-line" };
38330
+ const hiddenBarStyle = {
38331
+ display: "flex",
38332
+ alignItems: "center",
38333
+ justifyContent: "center",
38334
+ gap: 12,
38335
+ padding: "10px 16px",
38336
+ margin: "4px 0",
38337
+ border: "1px dashed rgba(100, 10, 255, 0.45)",
38338
+ borderRadius: 8,
38339
+ background: "rgba(100, 10, 255, 0.05)",
38340
+ color: "#3b2a66",
38341
+ font: '500 13px system-ui, -apple-system, "Segoe UI", sans-serif'
38342
+ };
38343
+ const hiddenBtnStyle = {
38344
+ border: "none",
38345
+ borderRadius: 999,
38346
+ padding: "4px 12px",
38347
+ background: "#640AFF",
38348
+ color: "#fff",
38349
+ font: '600 12px system-ui, -apple-system, "Segoe UI", sans-serif',
38350
+ cursor: "pointer"
38351
+ };
38352
+ const BLOG_ROUTE = "/blogg";
38353
+ const BLOG_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
38354
+ const BLOG_SLUG_MAX = 80;
38355
+ const YOUTUBE_ID_PATTERN = /^[A-Za-z0-9_-]{11}$/;
38356
+ function isValidBlogSlug(slug) {
38357
+ return slug.length <= BLOG_SLUG_MAX && BLOG_SLUG_PATTERN.test(slug);
38358
+ }
38359
+ function byDateDesc(a, b) {
38360
+ return a.date === b.date ? a.slug.localeCompare(b.slug) : a.date < b.date ? 1 : -1;
38361
+ }
38362
+ function postsForLanguage(posts, language, fallbackLanguage) {
38363
+ const bySlug = /* @__PURE__ */ new Map();
38364
+ for (const post2 of posts) {
38365
+ const current = bySlug.get(post2.slug);
38366
+ if (!current || rank(post2, language, fallbackLanguage) < rank(current, language, fallbackLanguage)) {
38367
+ bySlug.set(post2.slug, post2);
38368
+ }
38369
+ }
38370
+ return [...bySlug.values()].sort(byDateDesc);
38371
+ }
38372
+ function postForSlug(posts, slug, language, fallbackLanguage) {
38373
+ return postsForLanguage(posts.filter((p) => p.slug === slug), language, fallbackLanguage)[0] ?? null;
38374
+ }
38375
+ function rank(post2, language, fallbackLanguage) {
38376
+ if (post2.locale === language) return 0;
38377
+ if (post2.locale === fallbackLanguage) return 1;
38378
+ return 2;
38379
+ }
38380
+ function formatPostDate(date, language) {
38381
+ const parsed = /* @__PURE__ */ new Date(`${date}T12:00:00Z`);
38382
+ if (Number.isNaN(parsed.getTime())) return date;
38383
+ const locale = language === "no" || language === "nb" || language === "nn" ? "nb-NO" : language;
38384
+ try {
38385
+ return new Intl.DateTimeFormat(locale, { day: "numeric", month: "long", year: "numeric", timeZone: "UTC" }).format(parsed);
38386
+ } catch {
38387
+ return date;
38388
+ }
38389
+ }
38390
+ const NO = {
38391
+ blog: "Blogg",
38392
+ latest: "Fra bloggen",
38393
+ allPosts: "Alle innlegg",
38394
+ readMore: "Les mer",
38395
+ back: "Alle innlegg",
38396
+ empty: "Ingen innlegg ennå.",
38397
+ notFound: "Fant ikke innlegget.",
38398
+ loading: "Henter innlegget …",
38399
+ share: "Del",
38400
+ moreShare: "Flere valg …",
38401
+ copyLink: "Kopier lenke",
38402
+ copied: "Lenken er kopiert",
38403
+ email: "E-post",
38404
+ video: "Video",
38405
+ play: "Spill av"
38406
+ };
38407
+ const EN = {
38408
+ blog: "Blog",
38409
+ latest: "From the blog",
38410
+ allPosts: "All posts",
38411
+ readMore: "Read more",
38412
+ back: "All posts",
38413
+ empty: "No posts yet.",
38414
+ notFound: "That post could not be found.",
38415
+ loading: "Loading the post …",
38416
+ share: "Share",
38417
+ moreShare: "More …",
38418
+ copyLink: "Copy link",
38419
+ copied: "Link copied",
38420
+ email: "Email",
38421
+ video: "Video",
38422
+ play: "Play"
38423
+ };
38424
+ function blogStrings(language) {
38425
+ return language === "no" || language === "nb" || language === "nn" ? NO : EN;
38426
+ }
38427
+ function BlogBody({ nodes, language }) {
38428
+ return /* @__PURE__ */ jsx(Fragment, { children: nodes.map((node2, i) => renderFlow(node2, i, language)) });
38429
+ }
38430
+ function renderFlow(node2, key, language) {
38431
+ switch (node2.type) {
38432
+ case "paragraph":
38433
+ return /* @__PURE__ */ jsx("p", { children: renderPhrasing(node2.children) }, key);
38434
+ case "heading": {
38435
+ const Tag = node2.depth === 2 ? "h2" : node2.depth === 3 ? "h3" : "h4";
38436
+ return /* @__PURE__ */ jsx(Tag, { children: renderPhrasing(node2.children) }, key);
38437
+ }
38438
+ case "list": {
38439
+ const items = node2.children.map((item, i) => /* @__PURE__ */ jsx("li", { children: item.children.map((child, j) => renderListChild(child, j, item.spread === true, language)) }, i));
38440
+ return node2.ordered ? /* @__PURE__ */ jsx("ol", { start: node2.start ?? void 0, children: items }, key) : /* @__PURE__ */ jsx("ul", { children: items }, key);
38441
+ }
38442
+ case "blockquote":
38443
+ return /* @__PURE__ */ jsx("blockquote", { children: node2.children.map((child, i) => renderFlow(child, i, language)) }, key);
38444
+ case "thematicBreak":
38445
+ return /* @__PURE__ */ jsx("hr", {}, key);
38446
+ case "code":
38447
+ return /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: node2.value }) }, key);
38448
+ case "table":
38449
+ return /* @__PURE__ */ jsx(BlogTable, { table: node2 }, key);
38450
+ case "mdxJsxFlowElement":
38451
+ return /* @__PURE__ */ jsx(BlogComponent, { element: node2, language }, key);
38452
+ default:
38453
+ return null;
38454
+ }
38455
+ }
38456
+ function renderListChild(child, key, spread, language) {
38457
+ if (child.type === "paragraph" && !spread) return /* @__PURE__ */ jsx("span", { children: renderPhrasing(child.children) }, key);
38458
+ return renderFlow(child, key, language);
38459
+ }
38460
+ function renderPhrasing(children) {
38461
+ return children.map((child, i) => {
38462
+ switch (child.type) {
38463
+ case "text":
38464
+ return child.value;
38465
+ case "emphasis":
38466
+ return /* @__PURE__ */ jsx("em", { children: renderPhrasing(child.children) }, i);
38467
+ case "strong":
38468
+ return /* @__PURE__ */ jsx("strong", { children: renderPhrasing(child.children) }, i);
38469
+ case "delete":
38470
+ return /* @__PURE__ */ jsx("del", { children: renderPhrasing(child.children) }, i);
38471
+ case "inlineCode":
38472
+ return /* @__PURE__ */ jsx("code", { children: child.value }, i);
38473
+ case "break":
38474
+ return /* @__PURE__ */ jsx("br", {}, i);
38475
+ case "link": {
38476
+ const safe = isSafeHref(child.url);
38477
+ if (!safe) return /* @__PURE__ */ jsx("span", { children: renderPhrasing(child.children) }, i);
38478
+ const external = /^https?:\/\//i.test(child.url);
38479
+ return /* @__PURE__ */ jsx("a", { href: child.url, ...external ? { rel: "noopener noreferrer" } : {}, children: renderPhrasing(child.children) }, i);
38480
+ }
38481
+ default:
38482
+ return null;
38483
+ }
38484
+ });
38485
+ }
38486
+ function BlogTable({ table: table2 }) {
38487
+ const [head2, ...rows] = table2.children;
38488
+ const align = (i) => table2.align?.[i] ?? void 0;
38489
+ return /* @__PURE__ */ jsx("div", { className: "setto-blog__table", children: /* @__PURE__ */ jsxs("table", { children: [
38490
+ head2 ? /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: head2.children.map((cell, i) => /* @__PURE__ */ jsx("th", { style: { textAlign: align(i) }, children: renderPhrasing(cell.children) }, i)) }) }) : null,
38491
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row, r) => /* @__PURE__ */ jsx("tr", { children: row.children.map((cell, i) => /* @__PURE__ */ jsx("td", { style: { textAlign: align(i) }, children: renderPhrasing(cell.children) }, i)) }, r)) })
38492
+ ] }) });
38493
+ }
38494
+ function attrs(element2) {
38495
+ const out = {};
38496
+ for (const attribute of element2.attributes) {
38497
+ const a = attribute;
38498
+ if (a.type === "mdxJsxAttribute" && typeof a.value === "string") out[a.name] = a.value;
38499
+ }
38500
+ return out;
38501
+ }
38502
+ function BlogComponent({ element: element2, language }) {
38503
+ const props = attrs(element2);
38504
+ switch (element2.name) {
38505
+ case "Image":
38506
+ return props.src && props.alt !== void 0 ? /* @__PURE__ */ jsxs("figure", { className: "setto-blog__figure", children: [
38507
+ /* @__PURE__ */ jsx(BlogImg, { src: props.src, alt: props.alt, sizes: "(min-width: 760px) 720px, 100vw" }),
38508
+ props.caption ? /* @__PURE__ */ jsx("figcaption", { children: props.caption }) : null
38509
+ ] }) : null;
38510
+ case "Gallery": {
38511
+ const images = element2.children.filter(
38512
+ (child) => child.type === "mdxJsxFlowElement" && child.name === "Image"
38513
+ );
38514
+ return /* @__PURE__ */ jsxs("figure", { className: "setto-blog__gallery", children: [
38515
+ /* @__PURE__ */ jsx("div", { className: "setto-blog__gallery-grid", children: images.map((image2, i) => {
38516
+ const a = attrs(image2);
38517
+ return a.src ? /* @__PURE__ */ jsxs("figure", { className: "setto-blog__gallery-item", children: [
38518
+ /* @__PURE__ */ jsx(BlogImg, { src: a.src, alt: a.alt ?? "", sizes: "(min-width: 760px) 360px, 50vw" }),
38519
+ a.caption ? /* @__PURE__ */ jsx("figcaption", { children: a.caption }) : null
38520
+ ] }, i) : null;
38521
+ }) }),
38522
+ props.caption ? /* @__PURE__ */ jsx("figcaption", { children: props.caption }) : null
38523
+ ] });
38524
+ }
38525
+ case "YouTube":
38526
+ return props.id && YOUTUBE_ID_PATTERN.test(props.id) ? /* @__PURE__ */ jsx(YouTubeEmbed, { id: props.id, title: props.title, language }) : null;
38527
+ case "Callout":
38528
+ return /* @__PURE__ */ jsx("aside", { className: "setto-blog__callout", "data-tone": props.tone ?? "info", children: element2.children.map((child, i) => renderFlow(child, i, language)) });
38529
+ default:
38530
+ return null;
38531
+ }
38532
+ }
38533
+ function BlogImg({ src, alt, sizes, className }) {
38534
+ const { config } = useSetto();
38535
+ const [failed, setFailed] = useState(false);
38536
+ const optimize = config.images?.optimize === true && !failed && isOptimizable(src) && !(typeof window !== "undefined" && hasNoOptimizer(window.location.hostname));
38537
+ if (!optimize) return /* @__PURE__ */ jsx("img", { src, alt, loading: "lazy", decoding: "async", className });
38538
+ return /* @__PURE__ */ jsx(
38539
+ "img",
38540
+ {
38541
+ src: optimizedUrl(src, 1080),
38542
+ srcSet: optimizedSrcSet(src),
38543
+ sizes,
38544
+ alt,
38545
+ loading: "lazy",
38546
+ decoding: "async",
38547
+ className,
38548
+ onError: () => setFailed(true)
38549
+ }
38550
+ );
38551
+ }
38552
+ function YouTubeEmbed({ id, title, language }) {
38553
+ const [playing, setPlaying] = useState(false);
38554
+ const strings = blogStrings(language);
38555
+ const label = title ?? strings.video;
38556
+ return /* @__PURE__ */ jsxs("figure", { className: "setto-blog__video", children: [
38557
+ /* @__PURE__ */ jsx("div", { className: "setto-blog__video-frame", children: playing ? /* @__PURE__ */ jsx(
38558
+ "iframe",
38559
+ {
38560
+ src: `https://www.youtube-nocookie.com/embed/${id}?autoplay=1&rel=0`,
38561
+ title: label,
38562
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
38563
+ allowFullScreen: true,
38564
+ referrerPolicy: "strict-origin-when-cross-origin"
38565
+ }
38566
+ ) : /* @__PURE__ */ jsxs("button", { type: "button", className: "setto-blog__video-play", onClick: () => setPlaying(true), "aria-label": `${strings.play}: ${label}`, children: [
38567
+ /* @__PURE__ */ jsx("img", { src: `https://i.ytimg.com/vi/${id}/hqdefault.jpg`, alt: "", loading: "lazy", decoding: "async" }),
38568
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "setto-blog__video-icon", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 68 48", width: "68", height: "48", children: [
38569
+ /* @__PURE__ */ jsx("path", { d: "M66.5 7.7A8.5 8.5 0 0 0 60.5 1.7C55.2.3 34 .3 34 .3s-21.2 0-26.5 1.4A8.5 8.5 0 0 0 1.5 7.7C.1 13 .1 24 .1 24s0 11 1.4 16.3a8.5 8.5 0 0 0 6 6c5.3 1.4 26.5 1.4 26.5 1.4s21.2 0 26.5-1.4a8.5 8.5 0 0 0 6-6C67.9 35 67.9 24 67.9 24s0-11-1.4-16.3z", fill: "#f00" }),
38570
+ /* @__PURE__ */ jsx("path", { d: "M45 24 27 14v20", fill: "#fff" })
38571
+ ] }) })
38572
+ ] }) }),
38573
+ title ? /* @__PURE__ */ jsx("figcaption", { children: title }) : null
38574
+ ] });
38575
+ }
38576
+ const LATEST_COUNT = 3;
38577
+ function useBlogContext() {
38578
+ const { config } = useSetto();
38579
+ const { i18n, t } = useTranslation();
38580
+ const manifest = usePageManifest();
38581
+ const language = i18n.language || config.languages?.[0] || "no";
38582
+ const fallbackLanguage = config.languages?.[0] ?? "no";
38583
+ const route = typeof window === "undefined" ? { basename: "", rest: "/" } : languageFromPath(window.location.pathname, config.languages ?? [fallbackLanguage]);
38584
+ const href = (path2) => `${route.basename}${path2}`;
38585
+ return { config, t, manifest, language, fallbackLanguage, strings: blogStrings(language), path: route.rest, href };
38586
+ }
38587
+ function blogPathOf(manifest) {
38588
+ return manifest && blogPage(manifest)?.page.path || BLOG_ROUTE;
38589
+ }
38590
+ function SettoBlogSection({ sectionId, first }) {
38591
+ useBlogStyles();
38592
+ const ctx = useBlogContext();
38593
+ const slug = ctx.manifest ? blogSlugForPath(ctx.manifest, ctx.path) : null;
38594
+ const page = ctx.manifest ? findSection(ctx.manifest, sectionId)?.page : void 0;
38595
+ const title = page ? ctx.t(page.titleKey, { defaultValue: ctx.strings.blog }) : ctx.strings.blog;
38596
+ const blogPath = blogPathOf(ctx.manifest);
38597
+ const inner = useRef(null);
38598
+ const clear = useClearFixedHeader(inner, first);
38599
+ return /* @__PURE__ */ jsx(SettoSection, { sectionId, className: "setto-blog", children: /* @__PURE__ */ jsx("div", { className: "setto-blog__inner", ref: inner, style: clear ? { paddingTop: clear } : void 0, children: slug ? /* @__PURE__ */ jsx(BlogPostView, { slug, blogPath, ctx }) : /* @__PURE__ */ jsx(BlogIndexView, { title, first, blogPath, ctx }) }) });
38600
+ }
38601
+ function SettoBlog({ basePath = BLOG_ROUTE, title }) {
38602
+ useBlogStyles();
38603
+ const ctx = useBlogContext();
38604
+ const base = normalisePath(basePath);
38605
+ const here = normalisePath(ctx.path);
38606
+ const rest = here.startsWith(`${base}/`) ? here.slice(base.length + 1) : "";
38607
+ const slug = rest && isValidBlogSlug(rest) ? rest : null;
38608
+ const inner = useRef(null);
38609
+ const clear = useClearFixedHeader(inner, true);
38610
+ return /* @__PURE__ */ jsx("section", { className: "setto-blog", children: /* @__PURE__ */ jsx("div", { className: "setto-blog__inner", ref: inner, style: clear ? { paddingTop: clear } : void 0, children: slug ? /* @__PURE__ */ jsx(BlogPostView, { slug, blogPath: base, ctx }) : /* @__PURE__ */ jsx(BlogIndexView, { title: title ?? ctx.strings.blog, first: true, blogPath: base, ctx }) }) });
38611
+ }
38612
+ function useClearFixedHeader(ref, enabled) {
38613
+ const [clear, setClear] = useState(0);
38614
+ useLayoutEffect(() => {
38615
+ if (!enabled || typeof window === "undefined") return;
38616
+ const measure = () => {
38617
+ const el = ref.current;
38618
+ if (!el) return;
38619
+ const covering = document.elementsFromPoint(window.innerWidth / 2, 1).map((node2) => fixedAncestor(node2)).find((node2) => node2 !== null && !node2.contains(el));
38620
+ const headerBottom = covering ? covering.getBoundingClientRect().bottom : 0;
38621
+ const start = el.getBoundingClientRect().top + window.scrollY;
38622
+ const overlap = Math.ceil(headerBottom + 16 - start);
38623
+ setClear(overlap > 0 ? overlap : 0);
38624
+ };
38625
+ measure();
38626
+ window.addEventListener("resize", measure);
38627
+ return () => window.removeEventListener("resize", measure);
38628
+ }, [ref, enabled]);
38629
+ return clear;
38630
+ }
38631
+ function fixedAncestor(node2) {
38632
+ for (let at = node2; at && at !== document.body; at = at.parentElement) {
38633
+ if (getComputedStyle(at).position === "fixed") return at;
38634
+ }
38635
+ return null;
38636
+ }
38637
+ function BlogIndexView({ title, first, blogPath, ctx }) {
38638
+ const posts = ctx.config.blog ? postsForLanguage(ctx.config.blog.posts, ctx.language, ctx.fallbackLanguage) : [];
38639
+ const Heading = first ? "h1" : "h2";
38640
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
38641
+ /* @__PURE__ */ jsx(Heading, { className: "setto-blog__title", children: title }),
38642
+ posts.length === 0 ? /* @__PURE__ */ jsx("p", { className: "setto-blog__empty", children: ctx.strings.empty }) : /* @__PURE__ */ jsx("div", { className: "setto-blog__list", children: posts.map((post2) => /* @__PURE__ */ jsx(PostCard, { post: post2, blogPath, ctx, headingLevel: first ? "h2" : "h3" }, post2.slug)) })
38643
+ ] });
38644
+ }
38645
+ function PostCard({
38646
+ post: post2,
38647
+ blogPath,
38648
+ ctx,
38649
+ headingLevel
38650
+ }) {
38651
+ const Heading = headingLevel;
38652
+ const href = ctx.href(`${blogPath}/${post2.slug}`);
38653
+ return /* @__PURE__ */ jsxs("article", { className: "setto-blog__card", children: [
38654
+ post2.cover ? /* @__PURE__ */ jsx("a", { href, className: "setto-blog__card-cover", tabIndex: -1, "aria-hidden": "true", children: /* @__PURE__ */ jsx(BlogImg, { src: post2.cover, alt: "", sizes: "(min-width: 960px) 360px, (min-width: 640px) 50vw, 100vw" }) }) : null,
38655
+ /* @__PURE__ */ jsxs("div", { className: "setto-blog__card-body", children: [
38656
+ /* @__PURE__ */ jsx("time", { className: "setto-blog__date", dateTime: post2.date, children: formatPostDate(post2.date, ctx.language) }),
38657
+ /* @__PURE__ */ jsx(Heading, { className: "setto-blog__card-title", children: /* @__PURE__ */ jsx("a", { href, children: post2.title }) }),
38658
+ /* @__PURE__ */ jsx("p", { className: "setto-blog__card-lead", children: post2.description }),
38659
+ /* @__PURE__ */ jsxs("a", { href, className: "setto-blog__more", "aria-label": `${ctx.strings.readMore}: ${post2.title}`, children: [
38660
+ ctx.strings.readMore,
38661
+ " ",
38662
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "→" })
38663
+ ] })
38664
+ ] })
38665
+ ] });
38666
+ }
38667
+ function BlogPostView({ slug, blogPath, ctx }) {
38668
+ const blog = ctx.config.blog;
38669
+ const summary = blog ? postForSlug(blog.posts, slug, ctx.language, ctx.fallbackLanguage) : null;
38670
+ const [post2, setPost] = useState(null);
38671
+ const [state, setState] = useState(summary ? "loading" : "missing");
38672
+ useEffect(() => {
38673
+ if (!blog || !summary) {
38674
+ setState("missing");
38675
+ return;
38676
+ }
38677
+ let live = true;
38678
+ setState("loading");
38679
+ blog.load(summary.slug, summary.locale).then((loaded) => {
38680
+ if (!live) return;
38681
+ setPost(loaded);
38682
+ setState(loaded ? "ready" : "missing");
38683
+ }).catch(() => live && setState("missing"));
38684
+ return () => {
38685
+ live = false;
38686
+ };
38687
+ }, [blog, summary?.slug, summary?.locale]);
38688
+ usePostTitle(summary?.title ?? null, ctx.config.displayName);
38689
+ const back = /* @__PURE__ */ jsxs("a", { href: ctx.href(blogPath), className: "setto-blog__back", children: [
38690
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "←" }),
38691
+ " ",
38692
+ ctx.strings.back
38693
+ ] });
38694
+ if (!summary || state === "missing") {
38695
+ return /* @__PURE__ */ jsxs("div", { className: "setto-blog__post", children: [
38696
+ back,
38697
+ /* @__PURE__ */ jsx("p", { className: "setto-blog__empty", children: ctx.strings.notFound })
38698
+ ] });
38699
+ }
38700
+ const fm = post2?.frontmatter ?? summary;
38701
+ return /* @__PURE__ */ jsxs("article", { className: "setto-blog__post", lang: fm.locale, children: [
38702
+ back,
38703
+ /* @__PURE__ */ jsxs("header", { className: "setto-blog__post-header", children: [
38704
+ /* @__PURE__ */ jsx("time", { className: "setto-blog__date", dateTime: fm.date, children: formatPostDate(fm.date, ctx.language) }),
38705
+ /* @__PURE__ */ jsx("h1", { className: "setto-blog__post-title", children: fm.title }),
38706
+ /* @__PURE__ */ jsx("p", { className: "setto-blog__lead", children: fm.description })
38707
+ ] }),
38708
+ fm.cover ? /* @__PURE__ */ jsx("figure", { className: "setto-blog__cover", children: /* @__PURE__ */ jsx(BlogImg, { src: fm.cover, alt: "", sizes: "(min-width: 1040px) 960px, 100vw" }) }) : null,
38709
+ state === "ready" && post2 ? /* @__PURE__ */ jsx("div", { className: "setto-blog__prose", children: /* @__PURE__ */ jsx(BlogBody, { nodes: post2.body.children, language: ctx.language }) }) : /* @__PURE__ */ jsx("p", { className: "setto-blog__loading", "aria-live": "polite", children: ctx.strings.loading }),
38710
+ /* @__PURE__ */ jsx(ShareBar, { title: fm.title, strings: ctx.strings })
38711
+ ] });
38712
+ }
38713
+ function usePostTitle(title, siteName) {
38714
+ useEffect(() => {
38715
+ if (!title || typeof document === "undefined") return;
38716
+ const previous2 = document.title;
38717
+ const timer = window.setTimeout(() => {
38718
+ document.title = siteName ? `${title} · ${siteName}` : title;
38719
+ }, 0);
38720
+ return () => {
38721
+ window.clearTimeout(timer);
38722
+ document.title = previous2;
38723
+ };
38724
+ }, [title, siteName]);
38725
+ }
38726
+ function ShareBar({ title, strings }) {
38727
+ const [copied, setCopied] = useState(false);
38728
+ const [native, setNative] = useState(false);
38729
+ useEffect(() => setNative(typeof navigator !== "undefined" && typeof navigator.share === "function"), []);
38730
+ const url = typeof window === "undefined" ? "" : `${window.location.origin}${window.location.pathname}`;
38731
+ const u = encodeURIComponent(url);
38732
+ const t = encodeURIComponent(title);
38733
+ return /* @__PURE__ */ jsxs("div", { className: "setto-blog__share", "aria-label": strings.share, children: [
38734
+ /* @__PURE__ */ jsx("span", { className: "setto-blog__share-label", children: strings.share }),
38735
+ native ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void navigator.share({ title, url }).catch(() => void 0), children: strings.moreShare }) : null,
38736
+ /* @__PURE__ */ jsx("a", { href: `https://www.facebook.com/sharer/sharer.php?u=${u}`, target: "_blank", rel: "noopener noreferrer", children: "Facebook" }),
38737
+ /* @__PURE__ */ jsx("a", { href: `https://www.linkedin.com/sharing/share-offsite/?url=${u}`, target: "_blank", rel: "noopener noreferrer", children: "LinkedIn" }),
38738
+ /* @__PURE__ */ jsx("a", { href: `mailto:?subject=${t}&body=${u}`, children: strings.email }),
38739
+ /* @__PURE__ */ jsx(
38740
+ "button",
38741
+ {
38742
+ type: "button",
38743
+ onClick: () => {
38744
+ void navigator.clipboard?.writeText(url).then(() => {
38745
+ setCopied(true);
38746
+ window.setTimeout(() => setCopied(false), 2e3);
38747
+ });
38748
+ },
38749
+ children: copied ? strings.copied : strings.copyLink
38750
+ }
38751
+ )
38752
+ ] });
38753
+ }
38754
+ function SettoBlogLatest({ sectionId, first }) {
38755
+ useBlogStyles();
38756
+ const ctx = useBlogContext();
38757
+ if (!ctx.config.blog) return null;
38758
+ const posts = postsForLanguage(ctx.config.blog.posts, ctx.language, ctx.fallbackLanguage).slice(0, LATEST_COUNT);
38759
+ if (posts.length === 0) return null;
38760
+ const blogPath = blogPathOf(ctx.manifest);
38761
+ const Heading = first ? "h1" : "h2";
38762
+ return /* @__PURE__ */ jsx(SettoSection, { sectionId, className: "setto-blog setto-blog--latest", children: /* @__PURE__ */ jsxs("div", { className: "setto-blog__inner setto-blog__inner--wide", children: [
38763
+ /* @__PURE__ */ jsxs("div", { className: "setto-blog__latest-head", children: [
38764
+ /* @__PURE__ */ jsx(Heading, { className: "setto-blog__title", children: ctx.strings.latest }),
38765
+ /* @__PURE__ */ jsxs("a", { href: ctx.href(blogPath), className: "setto-blog__more", children: [
38766
+ ctx.strings.allPosts,
38767
+ " ",
38768
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "→" })
38769
+ ] })
38770
+ ] }),
38771
+ /* @__PURE__ */ jsx("div", { className: "setto-blog__list", children: posts.map((post2) => /* @__PURE__ */ jsx(PostCard, { post: post2, blogPath, ctx, headingLevel: "h3" }, post2.slug)) })
38772
+ ] }) });
38773
+ }
38774
+ const STYLE_ID$1 = "setto-blog-styles";
38775
+ const BLOG_CSS = `
38776
+ .setto-blog{padding:clamp(48px,8vw,96px) 16px}
38777
+ .setto-blog :where(.setto-blog__inner){max-width:68ch;margin:0 auto}
38778
+ .setto-blog :where(.setto-blog__inner--wide){max-width:1120px}
38779
+ .setto-blog :where(.setto-blog__title){font-family:var(--setto-display-font,inherit);font-size:clamp(2rem,4vw,2.75rem);line-height:1.15;font-weight:600;margin:0 0 .75em}
38780
+ .setto-blog :where(.setto-blog__list){display:grid;gap:clamp(1.5rem,3vw,2.5rem);grid-template-columns:repeat(auto-fill,minmax(min(100%,300px),1fr))}
38781
+ .setto-blog :where(.setto-blog__card){display:grid;gap:.9rem;align-content:start}
38782
+ .setto-blog :where(.setto-blog__card-cover){display:block;overflow:hidden;border-radius:.9rem;aspect-ratio:16/10}
38783
+ .setto-blog :where(.setto-blog__card-cover img,.setto-blog__cover img,.setto-blog__figure img,.setto-blog__gallery-item img){display:block;width:100%;height:100%;object-fit:cover}
38784
+ .setto-blog :where(.setto-blog__card-body){display:grid;gap:.45rem}
38785
+ .setto-blog :where(.setto-blog__card-title){font-family:var(--setto-display-font,inherit);font-size:1.3rem;line-height:1.25;font-weight:600;margin:0}
38786
+ .setto-blog :where(.setto-blog__card-lead){margin:0;line-height:1.55}
38787
+ .setto-blog :where(.setto-blog__date){font-size:.875rem;letter-spacing:.01em}
38788
+ .setto-blog :where(.setto-blog__more,.setto-blog__back){display:inline-flex;gap:.35em;align-items:center;font-weight:500;width:fit-content}
38789
+ .setto-blog :where(.setto-blog__back){margin-bottom:2rem}
38790
+ .setto-blog :where(.setto-blog__latest-head){display:flex;flex-wrap:wrap;justify-content:space-between;align-items:baseline;gap:.5rem 1.5rem;margin-bottom:1.75rem}
38791
+ .setto-blog :where(.setto-blog__latest-head .setto-blog__title){margin:0}
38792
+ .setto-blog :where(.setto-blog__post-header){display:grid;gap:.6rem;margin-bottom:2rem}
38793
+ .setto-blog :where(.setto-blog__post-title){font-family:var(--setto-display-font,inherit);font-size:clamp(2rem,5vw,3rem);line-height:1.1;font-weight:600;margin:0}
38794
+ .setto-blog :where(.setto-blog__lead){font-size:1.2rem;line-height:1.5;margin:0}
38795
+ .setto-blog :where(.setto-blog__cover){margin:0 0 2.5rem;overflow:hidden;border-radius:1rem;aspect-ratio:16/9}
38796
+ .setto-blog :where(.setto-blog__prose){font-size:1.075rem;line-height:1.7}
38797
+ .setto-blog :where(.setto-blog__prose > * + *){margin-top:1.15em}
38798
+ .setto-blog :where(.setto-blog__prose h2,.setto-blog__prose h3,.setto-blog__prose h4){font-family:var(--setto-display-font,inherit);line-height:1.25;font-weight:600;margin:1.8em 0 0}
38799
+ .setto-blog :where(.setto-blog__prose h2){font-size:1.6rem}
38800
+ .setto-blog :where(.setto-blog__prose h3){font-size:1.3rem}
38801
+ .setto-blog :where(.setto-blog__prose h4){font-size:1.1rem}
38802
+ .setto-blog :where(.setto-blog__prose ul,.setto-blog__prose ol){padding-left:1.4em}
38803
+ .setto-blog :where(.setto-blog__prose ul){list-style:disc}
38804
+ .setto-blog :where(.setto-blog__prose ol){list-style:decimal}
38805
+ .setto-blog :where(.setto-blog__prose li + li){margin-top:.35em}
38806
+ .setto-blog :where(.setto-blog__prose blockquote){margin-left:0;padding-left:1.1em;border-left-width:3px;border-left-style:solid;font-style:italic}
38807
+ .setto-blog :where(.setto-blog__prose pre){overflow-x:auto;padding:1em;border-radius:.6rem;font-size:.9em}
38808
+ .setto-blog :where(.setto-blog__prose code){font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.92em}
38809
+ .setto-blog :where(.setto-blog__prose hr){border:0;border-top-width:1px;border-top-style:solid;margin:2.5em 0}
38810
+ .setto-blog :where(.setto-blog__prose a){text-decoration:underline;text-underline-offset:.18em}
38811
+ .setto-blog :where(.setto-blog__table){overflow-x:auto}
38812
+ .setto-blog :where(.setto-blog__table table){border-collapse:collapse;width:100%;font-size:.95em}
38813
+ .setto-blog :where(.setto-blog__table th,.setto-blog__table td){padding:.55em .8em;border-bottom-width:1px;border-bottom-style:solid;text-align:left}
38814
+ .setto-blog :where(.setto-blog__figure,.setto-blog__gallery,.setto-blog__video){margin:2em 0}
38815
+ .setto-blog :where(.setto-blog__figure img){border-radius:.8rem;height:auto}
38816
+ .setto-blog :where(figcaption){font-size:.9rem;margin-top:.6em;opacity:.75}
38817
+ .setto-blog :where(.setto-blog__gallery-grid){display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(min(100%,150px),1fr))}
38818
+ .setto-blog :where(.setto-blog__gallery-item){margin:0}
38819
+ .setto-blog :where(.setto-blog__gallery-item img){border-radius:.6rem;aspect-ratio:4/3}
38820
+ .setto-blog :where(.setto-blog__video-frame){position:relative;aspect-ratio:16/9;border-radius:.8rem;overflow:hidden}
38821
+ .setto-blog :where(.setto-blog__video-frame iframe){position:absolute;inset:0;width:100%;height:100%;border:0}
38822
+ .setto-blog :where(.setto-blog__video-play){position:absolute;inset:0;width:100%;height:100%;padding:0;border:0;cursor:pointer;display:grid;place-items:center}
38823
+ .setto-blog :where(.setto-blog__video-play img){position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
38824
+ .setto-blog :where(.setto-blog__video-icon){position:relative;display:block;line-height:0}
38825
+ .setto-blog :where(.setto-blog__callout){padding:1.1em 1.3em;border-radius:.8rem;border-left-width:4px;border-left-style:solid}
38826
+ .setto-blog :where(.setto-blog__callout > * + *){margin-top:.7em}
38827
+ .setto-blog :where(.setto-blog__callout ul,.setto-blog__callout ol){padding-left:1.3em;list-style:disc}
38828
+ .setto-blog :where(.setto-blog__share){display:flex;flex-wrap:wrap;gap:.5rem .9rem;align-items:center;margin-top:3rem;padding-top:1.5rem;border-top-width:1px;border-top-style:solid}
38829
+ .setto-blog :where(.setto-blog__share-label){font-weight:600}
38830
+ .setto-blog :where(.setto-blog__share a,.setto-blog__share button){font:inherit;padding:.35rem .85rem;border-radius:999px;border-width:1px;border-style:solid;cursor:pointer;text-decoration:none;line-height:1.4}
38831
+ .setto-blog :where(.setto-blog__empty,.setto-blog__loading){opacity:.75}
38832
+ :where(.setto-blog){color:inherit}
38833
+ :where(.setto-blog__date){opacity:.7}
38834
+ :where(.setto-blog a){color:inherit}
38835
+ :where(.setto-blog__prose blockquote,.setto-blog__prose hr,.setto-blog__table th,.setto-blog__table td,.setto-blog__share){border-color:color-mix(in srgb,currentColor 18%,transparent)}
38836
+ :where(.setto-blog__prose pre,.setto-blog__card-cover){background:color-mix(in srgb,currentColor 6%,transparent)}
38837
+ :where(.setto-blog__share a,.setto-blog__share button){border-color:color-mix(in srgb,currentColor 30%,transparent);background:transparent;color:inherit}
38838
+ :where(.setto-blog__callout){background:color-mix(in srgb,currentColor 5%,transparent);border-left-color:color-mix(in srgb,currentColor 40%,transparent)}
38839
+ :where(.setto-blog__callout[data-tone="tip"]){border-left-color:#0f7b4f}
38840
+ :where(.setto-blog__callout[data-tone="warning"]){border-left-color:#b54708}
38841
+ :where(.setto-blog__video-frame){background:#000}
38842
+ `;
38843
+ function useBlogStyles() {
38844
+ useInsertionEffect(() => {
38845
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID$1)) return;
38846
+ const el = document.createElement("style");
38847
+ el.id = STYLE_ID$1;
38848
+ el.textContent = BLOG_CSS;
38849
+ document.head.appendChild(el);
38850
+ }, []);
38851
+ }
38137
38852
  const FORM_FIELD_TYPES = [
38138
38853
  "text",
38139
38854
  "email",
@@ -39136,193 +39851,6 @@ const iframeStyle = {
39136
39851
  height: "100%",
39137
39852
  border: 0
39138
39853
  };
39139
- function usePageManifest() {
39140
- const { pageStore, config } = useSetto();
39141
- const snap = useSyncExternalStore(
39142
- (cb) => pageStore?.subscribe(() => cb()) ?? (() => {
39143
- }),
39144
- () => pageStore?.snapshot() ?? EMPTY_PAGE_STORE_SNAPSHOT,
39145
- () => pageStore?.snapshot() ?? EMPTY_PAGE_STORE_SNAPSHOT
39146
- );
39147
- return pageStore ? snap.manifest : bundledManifest(config.pages);
39148
- }
39149
- function useSettoMenu(list2 = "menu") {
39150
- const manifest = usePageManifest();
39151
- return manifest ? visibleMenu(manifest, list2) : null;
39152
- }
39153
- function SettoPage({ pageId, path: path2, sections = {}, builtins, fallback = null }) {
39154
- const manifest = usePageManifest();
39155
- const { editMode } = useSetto();
39156
- if (!manifest) {
39157
- if (!pageId && path2 !== void 0 && path2 !== "/" && path2 !== "") return /* @__PURE__ */ jsx(Fragment, { children: fallback });
39158
- return /* @__PURE__ */ jsx(Fragment, { children: Object.entries(sections).map(([id, Component]) => /* @__PURE__ */ jsx(Component, {}, id)) });
39159
- }
39160
- const page = pageId ? pageById(manifest, pageId) : pageForPath(manifest, path2 ?? "/");
39161
- if (!page) {
39162
- if (pageId) {
39163
- return /* @__PURE__ */ jsx(Fragment, { children: Object.entries(sections).map(([id, Component]) => /* @__PURE__ */ jsx(Component, {}, id)) });
39164
- }
39165
- return /* @__PURE__ */ jsx(Fragment, { children: fallback });
39166
- }
39167
- if (page.hidden && !editMode) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
39168
- return /* @__PURE__ */ jsx(PageBody, { page, sections, builtins, editMode, manifest });
39169
- }
39170
- function PageBody({
39171
- page,
39172
- sections,
39173
- builtins,
39174
- editMode,
39175
- manifest
39176
- }) {
39177
- usePageTitle(page);
39178
- const HiddenPageNote = useSettoChromeSlot("hidden-page");
39179
- const listed = new Set(manifest.pages.flatMap((p) => p.sections.map((s) => s.id)));
39180
- const unlisted = Object.keys(sections).filter((id) => !listed.has(id));
39181
- const firstVisible = page.sections.find((s) => !s.hidden && (s.type || sections[s.id]))?.id;
39182
- return /* @__PURE__ */ jsxs(Fragment, { children: [
39183
- page.hidden && editMode ? HiddenPageNote ? /* @__PURE__ */ jsx(HiddenPageNote, { pageId: page.id }) : /* @__PURE__ */ jsx(HiddenNote, { text: "Denne siden er skjult for besøkende." }) : null,
39184
- page.sections.map((section) => /* @__PURE__ */ jsx(Fragment$1, { children: renderSection(section, sections, builtins, editMode, section.id === firstVisible) }, section.id)),
39185
- unlisted.map((id) => {
39186
- const Component = sections[id];
39187
- return /* @__PURE__ */ jsx(Component, {}, id);
39188
- })
39189
- ] });
39190
- }
39191
- function renderSection(section, sections, builtins, editMode, first) {
39192
- if (section.hidden) {
39193
- return editMode ? /* @__PURE__ */ jsx(HiddenSection, { section }) : null;
39194
- }
39195
- if (section.type) {
39196
- const Builtin = builtins?.[section.type] ?? BUILTIN_RENDERERS[section.type];
39197
- return /* @__PURE__ */ jsx(Builtin, { sectionId: section.id, first });
39198
- }
39199
- const Component = sections[section.id];
39200
- if (!Component) {
39201
- warnOnce(section.id);
39202
- return null;
39203
- }
39204
- return /* @__PURE__ */ jsx(Component, {});
39205
- }
39206
- const warned = /* @__PURE__ */ new Set();
39207
- function warnOnce(id) {
39208
- if (warned.has(id)) return;
39209
- warned.add(id);
39210
- console.warn(`[setto] section "${id}" is in pages.json but the page does not provide it`);
39211
- }
39212
- function HiddenSection({ section }) {
39213
- const { pageStore } = useSetto();
39214
- const Offered = useSettoChromeSlot("hidden-section");
39215
- const label = section.label ?? section.id;
39216
- if (Offered) return /* @__PURE__ */ jsx(Offered, { sectionId: section.id, label });
39217
- return /* @__PURE__ */ jsxs("div", { "data-setto-ui": true, style: hiddenBarStyle, children: [
39218
- /* @__PURE__ */ jsxs("span", { children: [
39219
- "Skjult: ",
39220
- label
39221
- ] }),
39222
- pageStore ? /* @__PURE__ */ jsx(
39223
- "button",
39224
- {
39225
- type: "button",
39226
- "data-setto-ui": true,
39227
- style: hiddenBtnStyle,
39228
- onClick: () => pageStore.apply([{ op: "show_section", sectionId: section.id }]),
39229
- children: "Vis"
39230
- }
39231
- ) : null
39232
- ] });
39233
- }
39234
- function HiddenNote({ text: text2 }) {
39235
- return /* @__PURE__ */ jsx("div", { "data-setto-ui": true, style: hiddenBarStyle, children: /* @__PURE__ */ jsx("span", { children: text2 }) });
39236
- }
39237
- function usePageTitle(page) {
39238
- const { t } = useTranslation();
39239
- const original = useRef(null);
39240
- const title = page.path === "/" ? null : t(page.titleKey);
39241
- useEffect(() => {
39242
- if (!title || typeof document === "undefined") return;
39243
- original.current ??= document.title;
39244
- const base = original.current;
39245
- document.title = base ? `${title} · ${base}` : title;
39246
- return () => {
39247
- document.title = base;
39248
- };
39249
- }, [title]);
39250
- }
39251
- function SettoTextSection({ sectionId, first }) {
39252
- const keys2 = builtinKeys(sectionId);
39253
- const { i18n } = useTranslation();
39254
- const { store, editMode } = useSetto();
39255
- const intro2 = store ? store.get(keys2.intro, i18n.language) : i18n.t(keys2.intro, { defaultValue: "" });
39256
- const Heading = first ? "h1" : "h2";
39257
- return /* @__PURE__ */ jsx(SettoSection, { sectionId, style: textSectionStyle, children: /* @__PURE__ */ jsxs("div", { style: textInnerStyle, children: [
39258
- /* @__PURE__ */ jsx(Heading, { style: first ? textH1Style : textH2Style, children: /* @__PURE__ */ jsx(T, { k: keys2.heading }) }),
39259
- intro2 || editMode ? /* @__PURE__ */ jsx("p", { style: textIntroStyle, children: /* @__PURE__ */ jsx(T, { k: keys2.intro }) }) : null,
39260
- /* @__PURE__ */ jsx(
39261
- SettoRepeater,
39262
- {
39263
- itemsKey: keys2.items,
39264
- defaultItem: { heading: "Overskrift", text: "Tekst" },
39265
- itemLabel: "avsnitt",
39266
- children: (itemKey) => /* @__PURE__ */ jsx(TextItem, { sectionId, itemKey })
39267
- }
39268
- )
39269
- ] }) });
39270
- }
39271
- function TextItem({ sectionId, itemKey }) {
39272
- const keys2 = builtinKeys(sectionId);
39273
- const { i18n } = useTranslation();
39274
- const { store, editMode } = useSetto();
39275
- const heading2 = store ? store.get(keys2.itemHeading(itemKey), i18n.language) : i18n.t(keys2.itemHeading(itemKey), { defaultValue: "" });
39276
- return /* @__PURE__ */ jsxs("div", { style: textItemStyle, children: [
39277
- heading2 || editMode ? /* @__PURE__ */ jsx("h3", { style: textH3Style, children: /* @__PURE__ */ jsx(T, { k: keys2.itemHeading(itemKey) }) }) : null,
39278
- /* @__PURE__ */ jsx("p", { style: textParagraphStyle, children: /* @__PURE__ */ jsx(T, { k: keys2.itemText(itemKey) }) })
39279
- ] });
39280
- }
39281
- const BUILTIN_RENDERERS = {
39282
- text: SettoTextSection
39283
- };
39284
- const textSectionStyle = { padding: "clamp(48px, 8vw, 96px) 16px" };
39285
- const textInnerStyle = { maxWidth: "68ch", margin: "0 auto" };
39286
- const textH1Style = {
39287
- fontFamily: "var(--setto-display-font, inherit)",
39288
- fontSize: "clamp(2rem, 4vw, 2.75rem)",
39289
- lineHeight: 1.15,
39290
- fontWeight: 600,
39291
- margin: "0 0 0.75em"
39292
- };
39293
- const textH2Style = { ...textH1Style, fontSize: "clamp(1.5rem, 3vw, 2rem)" };
39294
- const textH3Style = {
39295
- fontFamily: "var(--setto-display-font, inherit)",
39296
- fontSize: "1.125rem",
39297
- lineHeight: 1.3,
39298
- fontWeight: 600,
39299
- margin: "0 0 0.4em"
39300
- };
39301
- const textIntroStyle = { fontSize: "1.125rem", lineHeight: 1.6, opacity: 0.85, margin: "0 0 2em" };
39302
- const textItemStyle = { margin: "0 0 1.75em" };
39303
- const textParagraphStyle = { lineHeight: 1.7, margin: 0, whiteSpace: "pre-line" };
39304
- const hiddenBarStyle = {
39305
- display: "flex",
39306
- alignItems: "center",
39307
- justifyContent: "center",
39308
- gap: 12,
39309
- padding: "10px 16px",
39310
- margin: "4px 0",
39311
- border: "1px dashed rgba(100, 10, 255, 0.45)",
39312
- borderRadius: 8,
39313
- background: "rgba(100, 10, 255, 0.05)",
39314
- color: "#3b2a66",
39315
- font: '500 13px system-ui, -apple-system, "Segoe UI", sans-serif'
39316
- };
39317
- const hiddenBtnStyle = {
39318
- border: "none",
39319
- borderRadius: 999,
39320
- padding: "4px 12px",
39321
- background: "#640AFF",
39322
- color: "#fff",
39323
- font: '600 12px system-ui, -apple-system, "Segoe UI", sans-serif',
39324
- cursor: "pointer"
39325
- };
39326
39854
  const DEFAULT_DURATION = {
39327
39855
  fadeIn: 600,
39328
39856
  slideUp: 700,
@@ -40285,6 +40813,7 @@ export {
40285
40813
  AuthGate,
40286
40814
  BLOCK_REGISTRY,
40287
40815
  BLOCK_TYPES,
40816
+ BLOG_CSS,
40288
40817
  BUILTIN_SECTION_TYPES,
40289
40818
  CHECKOUT_INTENT,
40290
40819
  DEFAULT_LANGUAGE,
@@ -40309,6 +40838,9 @@ export {
40309
40838
  SettoAnimation,
40310
40839
  SettoBlock,
40311
40840
  SettoBlockTree,
40841
+ SettoBlog,
40842
+ SettoBlogLatest,
40843
+ SettoBlogSection,
40312
40844
  SettoChat,
40313
40845
  SettoChrome,
40314
40846
  SettoContext,
@@ -40330,6 +40862,8 @@ export {
40330
40862
  alternateLinks,
40331
40863
  applyOp,
40332
40864
  applyOps,
40865
+ blogPage,
40866
+ blogSlugForPath,
40333
40867
  builtinKeys,
40334
40868
  checkoutIntentUrl,
40335
40869
  countChanges,
@@ -40339,6 +40873,7 @@ export {
40339
40873
  fieldTypeHasPlaceholder,
40340
40874
  findSection,
40341
40875
  formFieldsPrefix,
40876
+ formatPostDate,
40342
40877
  isAdminDeploymentView,
40343
40878
  isAdminPanelView,
40344
40879
  isAdminRoute,
@@ -40360,6 +40895,8 @@ export {
40360
40895
  parseManifest,
40361
40896
  pathForLanguage,
40362
40897
  pickInitialLanguage,
40898
+ postForSlug,
40899
+ postsForLanguage,
40363
40900
  readLocaleFields,
40364
40901
  redirectForVisitor,
40365
40902
  serialiseManifest,