@jant/core 0.3.43 → 0.3.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/{app-GbfwoeDJ.js → app-C-L7wL6o.js} +485 -452
  2. package/dist/app-Hvqe7Ks_.js +5 -0
  3. package/dist/client/.vite/manifest.json +3 -3
  4. package/dist/client/_assets/client-DDs6NzB3.css +2 -0
  5. package/dist/client/_assets/{client-auth-CXILhW1b.js → client-auth-Dcon89Av.js} +30 -11
  6. package/dist/client/_assets/{client-D95FNDg5.js → client-dSfWfMe9.js} +7 -7
  7. package/dist/{github-sync-7y_nTXx1.js → github-sync-CQ1x271f.js} +3 -0
  8. package/dist/index.js +4 -87
  9. package/dist/node.js +3 -3
  10. package/package.json +1 -1
  11. package/src/client/components/jant-compose-dialog.ts +87 -9
  12. package/src/client/components/jant-compose-editor.ts +5 -1
  13. package/src/client/components/jant-post-menu.ts +23 -5
  14. package/src/client/compose-bridge.ts +2 -1
  15. package/src/client/toast.ts +29 -2
  16. package/src/client/upload-session.ts +1 -1
  17. package/src/db/migrations/0019_bored_magus.sql +2 -0
  18. package/src/db/migrations/0020_free_zaladane.sql +1 -0
  19. package/src/db/migrations/meta/0019_snapshot.json +2238 -0
  20. package/src/db/migrations/meta/0020_snapshot.json +2129 -0
  21. package/src/db/migrations/meta/_journal.json +14 -0
  22. package/src/db/migrations/pg/0017_bright_beyonder.sql +2 -0
  23. package/src/db/migrations/pg/0018_red_warlock.sql +1 -0
  24. package/src/db/migrations/pg/meta/0017_snapshot.json +2862 -0
  25. package/src/db/migrations/pg/meta/0018_snapshot.json +2739 -0
  26. package/src/db/migrations/pg/meta/_journal.json +14 -0
  27. package/src/db/pg/schema.ts +4 -30
  28. package/src/db/schema.ts +4 -39
  29. package/src/i18n/locales/public/en.po +10 -5
  30. package/src/i18n/locales/public/en.ts +1 -1
  31. package/src/i18n/locales/public/zh-Hans.po +10 -5
  32. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  33. package/src/i18n/locales/public/zh-Hant.po +10 -5
  34. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  35. package/src/index.ts +0 -3
  36. package/src/lib/__tests__/resolve-config.test.ts +4 -4
  37. package/src/lib/__tests__/startup-config.test.ts +27 -2
  38. package/src/lib/constants.ts +1 -0
  39. package/src/lib/github-sync-trigger.ts +7 -51
  40. package/src/lib/icons.ts +37 -0
  41. package/src/lib/startup-config.ts +53 -6
  42. package/src/routes/api/github-sync.tsx +36 -14
  43. package/src/routes/api/internal/sites.ts +1 -0
  44. package/src/routes/pages/home.tsx +2 -0
  45. package/src/routes/pages/latest.tsx +2 -0
  46. package/src/runtime/__tests__/readiness.test.ts +34 -0
  47. package/src/runtime/readiness.ts +8 -4
  48. package/src/services/__tests__/collection.test.ts +13 -11
  49. package/src/services/__tests__/site-admin.test.ts +85 -0
  50. package/src/services/github-sync.ts +6 -0
  51. package/src/services/site-admin.ts +66 -1
  52. package/src/styles/components.css +14 -0
  53. package/src/styles/ui.css +109 -0
  54. package/src/types/bindings.ts +0 -2
  55. package/src/types/config.ts +1 -1
  56. package/src/types/props.ts +2 -0
  57. package/src/ui/__tests__/font-themes.test.ts +2 -2
  58. package/src/ui/dash/settings/SettingsRootContent.tsx +17 -17
  59. package/src/ui/feed/LinkCard.tsx +3 -20
  60. package/src/ui/feed/LinkPreview.tsx +5 -19
  61. package/src/ui/feed/PostStatusBadges.tsx +4 -38
  62. package/src/ui/font-themes.ts +17 -17
  63. package/src/ui/layouts/BaseLayout.tsx +14 -29
  64. package/src/ui/pages/HomePage.tsx +21 -5
  65. package/src/ui/shared/DecorativeQuoteMark.tsx +2 -13
  66. package/src/ui/shared/Icon.tsx +60 -0
  67. package/src/ui/shared/IconSprite.tsx +57 -0
  68. package/src/ui/shared/PostFooter.tsx +6 -62
  69. package/src/ui/shared/custom-icons.ts +132 -0
  70. package/src/ui/shared/icon-collector.ts +37 -0
  71. package/dist/app-Ctl0T0zO.js +0 -5
  72. package/dist/client/_assets/client-C_kImWZj.css +0 -2
  73. package/src/lib/github-sync-queue-handler.ts +0 -69
  74. package/src/lib/github-sync-worker.ts +0 -72
  75. package/src/lib/job-queue-cf.ts +0 -18
  76. package/src/lib/job-queue-db.ts +0 -149
  77. package/src/lib/job-queue.ts +0 -35
@@ -1,9 +1,10 @@
1
1
  import { a as getSitePathPrefix, c as normalizePath, d as sanitizeUrl, f as slugify, g as toPublicPath, h as toPublicHref, i as getSiteOrigin, m as toAbsoluteSiteUrl, n as extractDisplayDomain, o as isFullUrl, p as stripSitePathPrefix, r as extractDomain, s as isSafeInternalRedirect, t as buildSiteUrl, u as normalizeSiteUrl, v as __exportAll } from "./url-umUptr5z.js";
2
- import { A as JANT_BRAND_PACK_FILENAME, B as getJantLogoFills, C as formatTime, D as toISOString, F as getJantBrandPackHref, G as arrayBufferToBase64, H as getJantPositiveLogoPngHref, I as getJantBundledAsset, K as base64ToUint8Array, L as getJantIconFilename, M as JANT_REPO_URL, N as getDefaultJantAppleTouchIconBytes, O as HOME_BRANDING_LINK_LABEL, P as getDefaultJantFaviconIcoBytes, R as getJantIconHref, S as formatRelativeTime, T as now, U as JANT_LOGO_PATH_DATA, V as getJantLogoHref, W as JANT_LOGO_VIEW_BOX, _ as getImageUrl, a as tiptapJsonToMarkdown, b as formatDate, c as render, d as extractSummary, f as extractSummaryHtml, g as escapeHtml, h as trimTiptapBody, i as createExportService, j as JANT_POSITIVE_LOGO_PNG_FILENAME, k as HOME_BRANDING_PREFIX, l as toPlainText, m as renderTiptapJson, n as createGitHubSyncService, o as markdownToTiptapJson, p as renderTiptapDocument, u as extractBodyText, v as getMediaUrl, w as formatYearMonth, x as formatRelativeAge, y as getPublicUrlForProvider, z as getJantLogoFilename } from "./github-sync-7y_nTXx1.js";
2
+ import { A as JANT_BRAND_PACK_FILENAME, B as getJantLogoFills, C as formatTime, D as toISOString, F as getJantBrandPackHref, G as arrayBufferToBase64, H as getJantPositiveLogoPngHref, I as getJantBundledAsset, K as base64ToUint8Array, L as getJantIconFilename, M as JANT_REPO_URL, N as getDefaultJantAppleTouchIconBytes, O as HOME_BRANDING_LINK_LABEL, P as getDefaultJantFaviconIcoBytes, R as getJantIconHref, S as formatRelativeTime, T as now, U as JANT_LOGO_PATH_DATA, V as getJantLogoHref, W as JANT_LOGO_VIEW_BOX, _ as getImageUrl, a as tiptapJsonToMarkdown, b as formatDate, c as render, d as extractSummary, f as extractSummaryHtml, g as escapeHtml, h as trimTiptapBody, i as createExportService, j as JANT_POSITIVE_LOGO_PNG_FILENAME, k as HOME_BRANDING_PREFIX, l as toPlainText, m as renderTiptapJson, n as createGitHubSyncService, o as markdownToTiptapJson, p as renderTiptapDocument, u as extractBodyText, v as getMediaUrl, w as formatYearMonth, x as formatRelativeAge, y as getPublicUrlForProvider, z as getJantLogoFilename } from "./github-sync-CQ1x271f.js";
3
3
  import { C as coalesceDisplayText, S as shouldUseSecureCookies, _ as getInternalAdminToken, a as getConfiguredSingleSiteUrl, b as getSiteResolutionMode, c as getDevApiToken, d as getHostedControlPlaneBaseUrl, f as getHostedControlPlaneDomainCheckSecret, g as getHostedControlPlaneSsoSecret, h as getHostedControlPlaneProviderLabel$1, i as getConfiguredSingleSitePathPrefix, l as getEnvString, m as getHostedControlPlaneInternalToken, n as getAuthSecret, o as getConfiguredStorageDriver, p as getHostedControlPlaneInternalBaseUrl, r as getConfiguredSingleSiteOrigin, s as getCorsOrigins, u as getGitHubAppConfig, v as getLocalStoragePath } from "./env-CgaH9Mut.js";
4
4
  import { a as listInstallationReposPage, n as getInstallation, o as searchInstallationRepos, t as buildInstallUrl } from "./github-app-WeadXMb8.js";
5
5
  import { r as parseRepoSlug, t as createGitHubClient } from "./github-api-BkRWnqMx.js";
6
6
  import { I18n } from "@lingui/core";
7
+ import * as lucideIcons from "lucide-static";
7
8
  import { z } from "zod";
8
9
  import { fromString, typeidUnboxed } from "typeid-js";
9
10
  import { decode } from "blurhash";
@@ -13,7 +14,6 @@ import { drizzle } from "drizzle-orm/better-sqlite3";
13
14
  import { drizzle as drizzle$1 } from "drizzle-orm/d1";
14
15
  import { check, foreignKey, index, integer, primaryKey, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
15
16
  import { boolean, check as check$1, customType, foreignKey as foreignKey$1, index as index$1, integer as integer$1, pgTable, primaryKey as primaryKey$1, text as text$1, timestamp, unique, uniqueIndex as uniqueIndex$1 } from "drizzle-orm/pg-core";
16
- import * as lucideIcons from "lucide-static";
17
17
  import { APIError, betterAuth } from "better-auth";
18
18
  import { drizzleAdapter } from "better-auth/adapters/drizzle";
19
19
  import { verifyPassword } from "better-auth/crypto";
@@ -1848,7 +1848,7 @@ var Hono = class extends Hono$1 {
1848
1848
  }
1849
1849
  //#endregion
1850
1850
  //#region src/i18n/locales/public/en.ts
1851
- var messages$3 = JSON.parse("{\"+4u2g6\":[\"A ready-made 1:1 PNG for decks, mockups, directories, and other square placements.\"],\"+DPYOZ\":[\"Add a link to your main RSS feed. Change what /feed returns in General.\"],\"+G8qqW\":[\"Collection saved.\"],\"+IJm1Z\":[\"Muted\"],\"+Irvp3\":[\"Everything on this page is ready to use for articles, launch posts, directories, and product coverage.\"],\"+Qaboy\":[\"Favicon\"],\"+fWu2O\":[\"A calmer, warmer accent makes the default theme feel quieter and more intentional.\"],\"+nHhRH\":[\"Use \",[\"brandColorName\"]],\"+siMqD\":[\"Journal\"],\"/DFKdU\":[\"Type the quote...\"],\"/PfPLc\":[\"Label (optional)\"],\"/PoNoq\":[\"Edit link\"],\"/Ui2OV\":[\"Use the reverse logo on dark backgrounds.\"],\"/Ybds4\":[\"Primary Jant logo for websites, docs, press coverage, and editorial layouts.\"],\"/rTz0M\":[\"Audio\"],\"0EcUWz\":[\"Discard changes?\"],\"0Lj7or\":[\"Save text attachment?\"],\"0XDp7X\":[\"Links should read clearly without glowing against the page.\"],\"0ieXE7\":[\"Highest rated\"],\"11h9eK\":[\"Includes\"],\"15++NM\":[\"Inline emphasis\"],\"1DBGsz\":[\"Notes\"],\"1NeeWI\":[\"Square assets for avatars, apps, browsers, and shared links\"],\"1THMr2\":[\"Brand pack\"],\"1njn7W\":[\"Light\"],\"2B7HLH\":[\"New post\"],\"2C7mSG\":[\"Collection link\"],\"2ETv7R\":[\"Tune color in a real reading context\"],\"2HbvFp\":[\"Real post components\"],\"2MXb5X\":[\"Field notes on quiet design\"],\"2koDOQ\":[\"Thread accents\"],\"2q/Q7x\":[\"Visibility\"],\"2sCqzD\":[\"Use this for websites, docs, articles, and other light or neutral surfaces.\"],\"33DClx\":[\"Link to your latest posts. If it comes before Featured, the homepage opens here.\"],\"3Cw1AI\":[\"Add Collection\"],\"3lJk5u\":[\"Keep the artwork unchanged.\"],\"3mdteM\":[\"before deciding whether the accent is carrying too much product energy.\"],\"3neqtf\":[\"Thread accent\"],\"3qkggm\":[\"Fullscreen\"],\"3vMdv3\":[\"This link is reserved. Choose something else.\"],\"3wKq0C\":[\"Couldn't save. Try again in a moment.\"],\"3xi01/\":[\"Look at the footer metadata last, to make sure the accent is not fighting the typography.\"],\"47iMgt\":[\"Editorial interfaces worth borrowing from\"],\"4D09NB\":[\"Link to your collections page\"],\"4HLTdq\":[\"without media\"],\"4J/OYU\":[\"Collection created.\"],\"4eiXo+\":[\"Leave blank to generate one automatically.\"],\"4pV0kE\":[\"Avatar-ready\"],\"51EYZX\":[\"without title\"],\"5dcjwM\":[\"Choose today or an earlier date, or leave it blank to publish now.\"],\"5pAjd8\":[\"Accent should feel present, not loud.\"],\"5sEkBi\":[\"Open raw asset\"],\"6UTABI\":[\"Collection order updated.\"],\"6WAK+2\":[\"Use current date\"],\"6Y4BBO\":[\"An abstract editorial layout in warm paper colors\"],\"6cjUDB\":[\"Brand assets\"],\"6lGV3K\":[\"Show less\"],\"6p0JeQ\":[\"to make sure both still feel like they belong to the same product.\"],\"6sVyMq\":[\"Add a URL before posting this link.\"],\"6yCv8j\":[\"Save these changes to the text attachment, discard them, or keep editing.\"],\"74kJNs\":[\"View earlier notes in this thread\"],\"7DvUqV\":[\"Read the page from top to bottom without looking at the swatches.\"],\"7aris6\":[\"March 15\"],\"7d1a0d\":[\"Public\"],\"7hYXO0\":[\"Use this on dark backgrounds, image-backed surfaces, and any placement where the green logo would lose contrast.\"],\"7kMW54\":[\"Open raw SVG\"],\"7nGhhM\":[\"What's on your mind?\"],\"7vhWI8\":[\"New Password\"],\"87a/t/\":[\"Label\"],\"8Btgys\":[\"Draft deleted.\"],\"8WX0J+\":[\"Your thoughts (optional)\"],\"8ZsakT\":[\"Password\"],\"8bpHix\":[\"Couldn't create your account. Check the details and try again.\"],\"8eC78s\":[\"A calmer accent makes\"],\"8tM8+a\":[\"Save as draft\"],\"90IRF2\":[\"This article is here to answer a specific question: does the default accent still feel calm once it has to carry a full reading experience?\"],\"9SHZas\":[\"Shows 'Settings' when logged in, 'Sign in' when logged out\"],\"9aloPG\":[\"References\"],\"9dr9Nh\":[\"Open external link\"],\"9qWoxS\":[\"Feed\"],\"A1D8Yt\":[\"What the accent should do\"],\"A1taO8\":[\"Search\"],\"A2Vg/u\":[\"Navigation and reading states\"],\"AjHkcv\":[\"Default preview image for social shares and link unfurls.\"],\"AyHO4m\":[\"What's this collection about?\"],\"B1FFMj\":[\"Download Brand Pack\"],\"B495Gs\":[\"Archive\"],\"BdjLtf\":[\"thread\"],\"Bmaby2\":[\"All formats\"],\"C+9df9\":[\"Quoted or highlighted passages should feel like annotations, not warnings.\"],\"C0/57J\":[\"This is the last part of the collection link.\"],\"CAh1km\":[\"collections\"],\"CH3bgf\":[\"RSS feed for this view\"],\"CT7H2e\":[\"Link to your featured posts. If it comes before Latest, the homepage opens here.\"],\"CmBCXY\":[\"Link updated.\"],\"D4em/+\":[\"Logos\"],\"DHhJ7s\":[\"Previous\"],\"DJLY+/\":[\" and try again.\"],\"DOx286\":[\"Draft restored.\"],\"DPfwMq\":[\"Done\"],\"DSJXZM\":[\"Enter a valid date.\"],\"DYlMYF\":[\"Built-in background\"],\"DoJzLz\":[\"Collections\"],\"Du2B9f\":[\"The default accent should support reading first. Start by comparing it against the\"],\"DxwUcG\":[\"Read the palette as content first\"],\"E3NcGH\":[\"Square logo PNG\"],\"EEYbdt\":[\"Publish\"],\"EGwzOK\":[\"Complete Setup\"],\"EHWwm1\":[\"The default accent should feel written, not branded.\"],\"EO3I6h\":[\"Upload didn't go through. Try again in a moment.\"],\"EQNPYo\":[\"Featured on \",[\"date\"],\" at \",[\"time\"]],\"EQtz4D\":[\"Open a few links and check whether they still feel native to the page.\"],\"EU3tBD\":[\"Link removed.\"],\"EetoJL\":[\"Guide the eye without taking over the layout.\"],\"Eiv3bO\":[\"Buttons can stay steady, but links, thread markers, and subtle emphasis should feel closer to ink on paper than dashboard chrome.\"],\"ElTnWL\":[\"Published on\"],\"EmQw8O\":[\"If this article still feels like a page you want to keep reading, the palette is probably close.\"],\"EsJdRp\":[\"Save theme\"],\"FESYvt\":[\"Describe this for people with visual impairments...\"],\"FEr96N\":[\"Theme\"],\"FGySZL\":[\"The default accent works best when it reads like a fountain-pen underline. Compare it against the\"],\"FM+KeU\":[\"No drafts yet. Save a draft to find it here.\"],\"Fdv5k7\":[\"What to look for while tuning it\"],\"FkMol5\":[\"Featured\"],\"Fxf4jq\":[\"Description (optional)\"],\"G2u/aQ\":[\"Download official Jant logos, icons, and preview assets.\"],\"GBJzTZ\":[\"Archive\"],\"GX2VMa\":[\"Create your admin account.\"],\"GY/1J4\":[\"Jant fallback canary string\"],\"GbIOhd\":[\"Reply quietly\"],\"GiRWtR\":[\"Why the default accent should feel written, not branded\"],\"GkpIs2\":[\"Remove this link from Collections? The destination won't change.\"],\"GorKul\":[\"Welcome to Jant\"],\"GxkJXS\":[\"Uploading...\"],\"H29JXm\":[\"+ ALT\"],\"H4lgRd\":[\"Authentication isn't set up. Check your server config.\"],\"HFPGej\":[\"No threads match these filters. Try adjusting your selection or clear all filters.\"],\"HG79RB\":[\"Post as Private\"],\"HNEHJP\":[\"Demo credentials are pre-filled — hit Sign In to continue.\"],\"HbAIQc\":[\"A reference link for checking whether the accent feels editorial instead of promotional.\"],\"Ht1V3q\":[\"For the same reason, inline code should stay neutral. Something like theme.siteAccent = soften(green, 12%) should not suddenly become the loudest thing on the page.\"],\"I22eN0\":[\"Shared links\"],\"I6zLrz\":[\"Use these when you need a transparent square logo, a shaped tile with a built-in background, a browser icon, or a default preview image.\"],\"ICsA6P\":[\"You have unsaved changes\"],\"IUX7p+\":[\"White logo on the Jant green rounded tile for app icon mockups, touch icons, directory listings, and other square placements that should feel softer.\"],\"IagCbF\":[\"URL\"],\"IjnQHI\":[\"with title\"],\"ImOQa9\":[\"Reply\"],\"IsI3kE\":[\"Nothing here yet. Add posts to one of these collections to fill this view.\"],\"J+2Rls\":[\"Leave blank to publish now. Use an earlier date when importing older posts.\"],\"J4tAHl\":[\"Headings should keep their hierarchy even when the accent gets softer.\"],\"JYj5R2\":[\"Browse files\"],\"JcD7qf\":[\"More actions\"],\"JqJ5Xv\":[\"Latest\"],\"JuN5GC\":[\"No file selected. Choose a file to upload.\"],\"JwLPQ/\":[\"This sign-in link has expired. Return to \"],\"KOqvXP\":[\"Do not recolor, stretch, rotate, outline, or add effects to the logo.\"],\"KbS2K9\":[\"Reset Password\"],\"KdSsVl\":[\"Author (optional)\"],\"Khu3PV\":[\"Publish settings\"],\"KiJn9B\":[\"Note\"],\"KlZ+t+\":[\"%name% + %count% more\"],\"KsvRin\":[\"Hide from Latest\"],\"KzmC5L\":[\"Controls\"],\"L7svJg\":[\"Reading\"],\"Lbkbwy\":[\"A quote card for judging accent color against softer, citation-heavy content.\"],\"LcvzvX\":[\"Tap to retry\"],\"LkA8jz\":[\"Add alt text\"],\"LxRg6f\":[\"live theme controls\"],\"M4tzVU\":[\"Latest posts\"],\"M8kJqa\":[\"Drafts\"],\"MHrjPM\":[\"Title\"],\"MILa7n\":[\"Square tile\"],\"MSc/Yq\":[\"Do you want to publish your changes or discard them?\"],\"Mc7+6G\":[\"Enter a valid URL starting with http://, https://, or mailto:.\"],\"MdMyne\":[\"Source link (optional)\"],\"MiMY3Q\":[\"Apple touch icon\"],\"MiyoI7\":[\"default note sample\"],\"MqghUt\":[\"Search posts...\"],\"Myqkib\":[\"Create a collection to get started.\"],\"N8UzTV\":[\"Replies\"],\"NAFbuE\":[\"Search snippet\"],\"NH9Z1R\":[\"Start here\"],\"NqsRbb\":[\"Jant logo\"],\"NvXuWk\":[\"Won't move the thread to the top of latest.\"],\"O1367B\":[\"All collections\"],\"O3oNi5\":[\"Email\"],\"OEdMhi\":[\"The best default color is the one you notice only after reading for a while.\"],\"OEt/to\":[\"Guidelines\"],\"OJxdgi\":[\"Keep this link under 200 characters.\"],\"OaoJcz\":[\"Social preview\"],\"OmfDbR\":[\"Site accent\"],\"Ovks1h\":[\"A softer blue feels more like ink than product chrome.\"],\"P/sHNL\":[\"Use this page to judge buttons, links, cards, forms, thread accents, and quiet surfaces before changing a theme globally.\"],\"Q2mGA7\":[\"Clear filter\"],\"QBqVyM\":[\"Home screen icon for iPhone and iPad shortcuts.\"],\"QebAts\":[\"Link added.\"],\"Qgbxdw\":[\"Designing a calmer default accent for Jant\"],\"Qn9Ao8\":[\"Circle tile\"],\"QyDt3L\":[\"File uploaded.\"],\"R5CMuK\":[\"Jant looks best when the accent feels editorial. Buttons can stay sturdy, but inline emphasis should feel like a pen mark, not a dashboard highlight.\"],\"R8AthW\":[\"Divider\"],\"R9Khdg\":[\"Auto\"],\"RAv3u7\":[\"Compare it against the theme controls\"],\"ROa4Ti\":[\"Interfaces for reading should guide the eye, not keep asking for attention.\"],\"RZOWDv\":[\"Add a custom shortcut to any page or site.\"],\"RdmNnl\":[\"Browser tab\"],\"RfGczC\":[\"Square logo\"],\"Rj01Fz\":[\"Links\"],\"S37om9\":[\"Included assets\"],\"S8NCfs\":[\"Save to drafts to edit and post at a later time.\"],\"SJGVAw\":[\"Feel editorial and slightly quieter.\"],\"SJmfuf\":[\"Site Name\"],\"SaNhJE\":[\"feel deliberate instead of washed out.\"],\"SpTWH3\":[\"Download SVG\"],\"SvRuJt\":[\"Field Notes on Interface Tone\"],\"T/R+Qz\":[\"Primary\"],\"TNZKpI\":[\"Danger\"],\"TvaTxw\":[\"Doesn't appear in Latest. Still appears in collections you add it to.\"],\"UIMXHD\":[\"Remove Divider\"],\"UaZwcz\":[\"More options are available after you create it.\"],\"Uc5y7o\":[\"Choose the standard logo for websites, docs, directories, and editorial layouts.\"],\"V18SVO\":[\"Use the logo on light backgrounds.\"],\"V4WsyL\":[\"Add Link\"],\"VCA6B2\":[\"These are actual feed components with real footers, summaries, and inline links. Use this section to judge whether the theme still feels calm once it is applied to realistic content.\"],\"VNqFYa\":[\"Loading post...\"],\"WCOanD\":[\"This reference is useful because it treats links and citations as part of the reading rhythm. Keep that in mind while tuning the\"],\"WbIbzR\":[\"Checking link...\"],\"WcWS//\":[\"Download file\"],\"WhsN3P\":[\"A good default accent in Jant should feel like editorial structure, not product branding. That means links, emphasis, and thread cues can be visible without turning the page into UI chrome.\"],\"Wn+/rH\":[\"Transparent square\"],\"WpXcBJ\":[\"Nothing here yet.\"],\"XU7b+L\":[\"Primary logo files\"],\"XV1mAn\":[\"Only visible when signed in.\"],\"XrnWzN\":[\"Published!\"],\"YIix5Y\":[\"Search...\"],\"YUglt2\":[\"Generating a link...\"],\"YXiA6e\":[\"Primary button\"],\"Ygx3Yl\":[\"Small browser icon used in tabs and bookmarks.\"],\"Z6NwTi\":[\"Save as Draft\"],\"ZGs2so\":[\"Delete this collection permanently? Posts inside won't be removed.\"],\"ZV5ykW\":[\"Download PNG\"],\"ZhhOwV\":[\"Quote\"],\"ZmSeP+\":[\"Save to drafts?\"],\"ZxFuun\":[[\"count\",\"plural\",{\"one\":[\"Found \",\"#\",\" result\"],\"other\":[\"Found \",\"#\",\" results\"]}]],\"a5j82I\":[\"No collections match that search. Try a different name.\"],\"aHTB7P\":[\"Supplementary content attached to your post\"],\"aMEyv0\":[\"Stay sturdy and readable.\"],\"aN6wx0\":[\"Nothing in Featured yet. Mark a post as featured to show it here.\"],\"aYpXKS\":[\"and checking whether the accent is guiding attention or pulling too hard.\"],\"aaGV/9\":[\"New Link\"],\"af+9p6\":[\"Quiet metadata\"],\"an5hVd\":[\"Images\"],\"ao77hr\":[[\"count\",\"plural\",{\"one\":[\"#\",\" hidden post\"],\"other\":[\"#\",\" hidden posts\"]}]],\"auFlOr\":[\"Icons and previews\"],\"avuFKG\":[\"threads\"],\"bFpC86\":[\"Everything in one download\"],\"bGtMpA\":[\"Add a label and URL.\"],\"bHOiy1\":[\"Password changes are off in demo mode. Sign in with the shared demo credentials.\"],\"bbdNeX\":[\"Sign in\"],\"bfCbdi\":[\"Current post\"],\"bkBJmZ\":[\"This is useful as a color check because it puts the accent next to quotation styling, metadata, and a quieter explanatory paragraph. Compare it back to the\"],\"bzSI52\":[\"Discard\"],\"c2JRUS\":[\"Generate automatically\"],\"cIoW7X\":[\"Inline link\"],\"cTUByn\":[\"Newest first\"],\"cb7FR8\":[\"White logo on the Jant green square tile for platforms and layouts that expect a true edge-to-edge square.\"],\"cgmi4V\":[\"Delete Draft\"],\"cnGeoo\":[\"Delete\"],\"d+F4pf\":[\"The image should sit quietly inside the article instead of feeling like a card preview.\"],\"d/o/BH\":[\"Couldn't publish. Saved as draft.\"],\"dD7NPy\":[\"Outline\"],\"dEgA5A\":[\"Cancel\"],\"dUsGbd\":[\"The right accent should disappear into the writing until you need it.\"],\"dXoieq\":[\"Summary\"],\"dYKrp3\":[\"Hidden from Latest\"],\"dbUuAj\":[\"Appears in Latest.\"],\"df4a/r\":[\"Couldn't load this post. Try again.\"],\"ePK91l\":[\"Edit\"],\"eWLklq\":[\"Quotes\"],\"f4MAoA\":[\"Some uploads failed. Saved as draft.\"],\"f5s9EI\":[\"Press N to write\"],\"f6Hub0\":[\"Sort\"],\"f8fH8W\":[\"Design\"],\"fD+f7T\":[\"RSS feed\"],\"fKrDxS\":[\"Brand tile\"],\"fMPkxb\":[\"Show more\"],\"fqDzSu\":[\"Rate\"],\"fttd2R\":[\"My Collection\"],\"gCcxP/\":[\"Threads can include up to \",[\"count\"],\" posts.\"],\"gFdWl+\":[\"A long-form article sample for checking the default palette in a true reading context.\"],\"gNKz6Z\":[\"Collection deleted.\"],\"gXH9r/\":[\"Open raw PNG\"],\"gj52YE\":[\"This collection is empty. Add posts from the editor.\"],\"gpaPhA\":[\"Helps screen readers describe the image\"],\"h5RcXU\":[\"Post hidden\"],\"hLlWo5\":[\"A few simple rules.\"],\"hWpUeY\":[\"Auto link\"],\"hXzOVo\":[\"Next\"],\"heSQoS\":[\"Paste a URL...\"],\"hrkGms\":[\"Search\"],\"i0vDGK\":[\"Sort Order\"],\"i5+Y7d\":[\"Download the official Jant logo, icons, and preview files.\"],\"i6kro6\":[\"Edit custom link\"],\"i6nDCI\":[\"Choose a new password.\"],\"iG7KNr\":[\"Logo\"],\"iH8pgl\":[\"Back\"],\"ilSmIt\":[\"Hard edge\"],\"iu7tUI\":[\"Breadcrumb\"],\"jAXE5p\":[\"Reverse logo\"],\"jAqB/k\":[\"Post privately\"],\"jQflRT\":[\"This uses the real single-post detail rendering with a longer article, inline image, tables, lists, quotes, and code. The content column stays at the same width as the live site.\"],\"jd+8Mm\":[\"Social preview image\"],\"jdJOV1\":[\"Settings\"],\"ji7oVU\":[\"Edit post\"],\"jpctdh\":[\"View\"],\"jvyYZG\":[\"What's on your mind...\"],\"k3Iw35\":[\"Switch to the white logo when the standard green version would lose contrast.\"],\"kPMIr+\":[\"Give it a title...\"],\"kj6ppi\":[\"entry\"],\"kr39oD\":[\"No collections yet. Start one to organize posts by topic.\"],\"kzvWob\":[\"Link to the post archive\"],\"laT1IJ\":[\"iOS home screen\"],\"lb+Xwx\":[\"Custom link\"],\"m16xKo\":[\"Add\"],\"mKT7g0\":[\"Text attachment\"],\"mc/vLq\":[\"This link is already in use. Choose something else.\"],\"muKqfV\":[\"Featured\"],\"n1ekoW\":[\"Sign In\"],\"n3ReIn\":[\"Collections\"],\"n6QD94\":[\"Oldest first\"],\"nFukaP\":[\"Wrong email or password. Check your credentials and try again.\"],\"nV6twc\":[\"Organize\"],\"nd8Puv\":[\"White logo on the Jant green circle for profile images, badges, and other round placements where you want a ready-made asset.\"],\"ndrEYW\":[\"When the accent is slightly warmer and less literal, the whole page feels more like a writing space and less like product UI.\"],\"o21Y+P\":[\"entries\"],\"oO0hKx\":[[\"count\",\"plural\",{\"one\":[\"#\",\" more post\"],\"other\":[\"#\",\" more posts\"]}]],\"oTu7Wt\":[\"Combined Collections\"],\"ode0+L\":[\"Theme sample\"],\"ogssnn\":[\"with media\"],\"ovBPCi\":[\"Default\"],\"p1Z67P\":[\"When primary is too rigid, the whole page starts reading like product UI instead of writing space.\"],\"p2/GCq\":[\"Confirm Password\"],\"pB0OKE\":[\"New Divider\"],\"pBHx39\":[\"Dark backgrounds\"],\"pVrU5x\":[\"If this page feels too branded, the first place to soften is the default theme’s site accent, not the border or body text.\"],\"pvnfJD\":[\"Dark\"],\"q+hNag\":[\"Collection\"],\"q5YRzz\":[\"Color check\"],\"q8RviX\":[\"Titled\"],\"qcawwg\":[\"Publish now\"],\"qiN9NB\":[\"Surface\"],\"qt89I8\":[\"Draft saved.\"],\"quvfGs\":[\"instead of judging it as an isolated swatch.\"],\"r7kcaA\":[\"Drag collections, links, and dividers into the order you want.\"],\"rA2TFI\":[\"Switch the palette and mode without opening settings or changing the active site theme.\"],\"rV8ZnP\":[\"Edit publish date\"],\"rdUucN\":[\"Preview\"],\"s8G5Or\":[\"This upload would exceed your shared hosted media limit. Remove files or upgrade storage to continue.\"],\"s9gHf5\":[\"your-post-link\"],\"sER+bs\":[\"Files\"],\"sQpDn6\":[\"Exit fullscreen\"],\"sgr2wQ\":[\"collection\"],\"slujBW\":[\"Use lowercase letters, numbers, and hyphens only.\"],\"syiAKf\":[\"note treatment\"],\"t42hIC\":[\"Everything most people need is in one ZIP.\"],\"tCctex\":[\"The brand pack includes SVG logos, a transparent square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and the default social preview image.\"],\"tKlWWY\":[\"Emoji\"],\"tSWVu5\":[\"Published on \",[\"date\"],\" at \",[\"time\"]],\"tfDRzk\":[\"Save\"],\"tgSBSE\":[\"Remove Link\"],\"uowbPn\":[\"Remove attachment\"],\"v3E8iS\":[\"A practical checklist\"],\"vSJd18\":[\"Video\"],\"vSYKYI\":[\"Main feed\"],\"vXCC6J\":[\"Something doesn't look right. Check the form and try again.\"],\"vcpc5o\":[\"Close menu\"],\"vdFnYM\":[\"Reset link\"],\"vdvpU5\":[\"/archive?format=quote or https://example.com\"],\"vgpfCi\":[\"Save draft\"],\"vpSPA1\":[\"Auth secret is missing. Check your environment variables.\"],\"vzU4k9\":[\"New Collection\"],\"w0Emel\":[\"Suggested link\"],\"w6mlns\":[\"Article detail page\"],\"wJ+GRy\":[\"All visibility\"],\"wL3cK8\":[\"Latest\"],\"wja8aL\":[\"Untitled\"],\"wlnK1t\":[\"A single ZIP with the main logo, reverse logo, square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and social preview image.\"],\"wm3Zlr\":[\"All years\"],\"xCWek4\":[\"File storage isn't set up. Check your server config.\"],\"xVrkxi\":[\"quiet design\"],\"xVvw1i\":[\"This reset link is no longer valid. Request a new one to continue.\"],\"xYilR2\":[\"Media\"],\"xeiujy\":[\"Text\"],\"xhTx3y\":[\"Choose the standard logo for most placements and the reverse logo when you need more contrast.\"],\"y28hnO\":[\"Post\"],\"y2o/Y0\":[\"This Link Has Expired\"],\"yGZVl1\":[\"More\"],\"yQ2kGp\":[\"Load more\"],\"yUtAh2\":[\"New Thread\"],\"ycM1Xg\":[\"No results. Try different keywords.\"],\"ynMAhG\":[\"Default logo\"],\"yzF66j\":[\"Link\"],\"zBFr9G\":[\"Paste a long article, AI response, or any text...\\n\\nMarkdown formatting will be preserved.\"],\"zJDAbh\":[\"Don't save\"],\"zcDmsG\":[\"Featured posts\"],\"zoK+eO\":[\"Add a title before posting this link.\"],\"zucql+\":[\"Menu\"],\"zwBp5t\":[\"Private\"]}");
1851
+ var messages$3 = JSON.parse("{\"+4u2g6\":[\"A ready-made 1:1 PNG for decks, mockups, directories, and other square placements.\"],\"+DPYOZ\":[\"Add a link to your main RSS feed. Change what /feed returns in General.\"],\"+G8qqW\":[\"Collection saved.\"],\"+IJm1Z\":[\"Muted\"],\"+Irvp3\":[\"Everything on this page is ready to use for articles, launch posts, directories, and product coverage.\"],\"+Qaboy\":[\"Favicon\"],\"+fWu2O\":[\"A calmer, warmer accent makes the default theme feel quieter and more intentional.\"],\"+nHhRH\":[\"Use \",[\"brandColorName\"]],\"+siMqD\":[\"Journal\"],\"/DFKdU\":[\"Type the quote...\"],\"/PfPLc\":[\"Label (optional)\"],\"/PoNoq\":[\"Edit link\"],\"/Ui2OV\":[\"Use the reverse logo on dark backgrounds.\"],\"/Ybds4\":[\"Primary Jant logo for websites, docs, press coverage, and editorial layouts.\"],\"/rTz0M\":[\"Audio\"],\"0EcUWz\":[\"Discard changes?\"],\"0Lj7or\":[\"Save text attachment?\"],\"0XDp7X\":[\"Links should read clearly without glowing against the page.\"],\"0ieXE7\":[\"Highest rated\"],\"11h9eK\":[\"Includes\"],\"15++NM\":[\"Inline emphasis\"],\"1DBGsz\":[\"Notes\"],\"1NeeWI\":[\"Square assets for avatars, apps, browsers, and shared links\"],\"1THMr2\":[\"Brand pack\"],\"1njn7W\":[\"Light\"],\"2B7HLH\":[\"New post\"],\"2C7mSG\":[\"Collection link\"],\"2ETv7R\":[\"Tune color in a real reading context\"],\"2HbvFp\":[\"Real post components\"],\"2MXb5X\":[\"Field notes on quiet design\"],\"2koDOQ\":[\"Thread accents\"],\"2q/Q7x\":[\"Visibility\"],\"2sCqzD\":[\"Use this for websites, docs, articles, and other light or neutral surfaces.\"],\"33DClx\":[\"Link to your latest posts. If it comes before Featured, the homepage opens here.\"],\"3Cw1AI\":[\"Add Collection\"],\"3lJk5u\":[\"Keep the artwork unchanged.\"],\"3mdteM\":[\"before deciding whether the accent is carrying too much product energy.\"],\"3neqtf\":[\"Thread accent\"],\"3qkggm\":[\"Fullscreen\"],\"3vMdv3\":[\"This link is reserved. Choose something else.\"],\"3wKq0C\":[\"Couldn't save. Try again in a moment.\"],\"3xi01/\":[\"Look at the footer metadata last, to make sure the accent is not fighting the typography.\"],\"47iMgt\":[\"Editorial interfaces worth borrowing from\"],\"4D09NB\":[\"Link to your collections page\"],\"4HLTdq\":[\"without media\"],\"4J/OYU\":[\"Collection created.\"],\"4eiXo+\":[\"Leave blank to generate one automatically.\"],\"4pV0kE\":[\"Avatar-ready\"],\"51EYZX\":[\"without title\"],\"5dcjwM\":[\"Choose today or an earlier date, or leave it blank to publish now.\"],\"5pAjd8\":[\"Accent should feel present, not loud.\"],\"5sEkBi\":[\"Open raw asset\"],\"6UTABI\":[\"Collection order updated.\"],\"6WAK+2\":[\"Use current date\"],\"6Y4BBO\":[\"An abstract editorial layout in warm paper colors\"],\"6cjUDB\":[\"Brand assets\"],\"6lGV3K\":[\"Show less\"],\"6p0JeQ\":[\"to make sure both still feel like they belong to the same product.\"],\"6sVyMq\":[\"Add a URL before posting this link.\"],\"6yCv8j\":[\"Save these changes to the text attachment, discard them, or keep editing.\"],\"74kJNs\":[\"View earlier notes in this thread\"],\"7DvUqV\":[\"Read the page from top to bottom without looking at the swatches.\"],\"7aris6\":[\"March 15\"],\"7d1a0d\":[\"Public\"],\"7hYXO0\":[\"Use this on dark backgrounds, image-backed surfaces, and any placement where the green logo would lose contrast.\"],\"7kMW54\":[\"Open raw SVG\"],\"7nGhhM\":[\"What's on your mind?\"],\"7vhWI8\":[\"New Password\"],\"87a/t/\":[\"Label\"],\"8Btgys\":[\"Draft deleted.\"],\"8WX0J+\":[\"Your thoughts (optional)\"],\"8ZsakT\":[\"Password\"],\"8bpHix\":[\"Couldn't create your account. Check the details and try again.\"],\"8eC78s\":[\"A calmer accent makes\"],\"8tM8+a\":[\"Save as draft\"],\"90IRF2\":[\"This article is here to answer a specific question: does the default accent still feel calm once it has to carry a full reading experience?\"],\"9SHZas\":[\"Shows 'Settings' when logged in, 'Sign in' when logged out\"],\"9aloPG\":[\"References\"],\"9dr9Nh\":[\"Open external link\"],\"9qWoxS\":[\"Feed\"],\"A1D8Yt\":[\"What the accent should do\"],\"A1taO8\":[\"Search\"],\"A2Vg/u\":[\"Navigation and reading states\"],\"AjHkcv\":[\"Default preview image for social shares and link unfurls.\"],\"AyHO4m\":[\"What's this collection about?\"],\"B1FFMj\":[\"Download Brand Pack\"],\"B495Gs\":[\"Archive\"],\"BdjLtf\":[\"thread\"],\"Bmaby2\":[\"All formats\"],\"C+9df9\":[\"Quoted or highlighted passages should feel like annotations, not warnings.\"],\"C0/57J\":[\"This is the last part of the collection link.\"],\"CAh1km\":[\"collections\"],\"CH3bgf\":[\"RSS feed for this view\"],\"CT7H2e\":[\"Link to your featured posts. If it comes before Latest, the homepage opens here.\"],\"CmBCXY\":[\"Link updated.\"],\"D4em/+\":[\"Logos\"],\"DHhJ7s\":[\"Previous\"],\"DJLY+/\":[\" and try again.\"],\"DOx286\":[\"Draft restored.\"],\"DPfwMq\":[\"Done\"],\"DSJXZM\":[\"Enter a valid date.\"],\"DYlMYF\":[\"Built-in background\"],\"DoJzLz\":[\"Collections\"],\"Du2B9f\":[\"The default accent should support reading first. Start by comparing it against the\"],\"DxwUcG\":[\"Read the palette as content first\"],\"E3NcGH\":[\"Square logo PNG\"],\"EEYbdt\":[\"Publish\"],\"EGwzOK\":[\"Complete Setup\"],\"EHWwm1\":[\"The default accent should feel written, not branded.\"],\"EO3I6h\":[\"Upload didn't go through. Try again in a moment.\"],\"EQNPYo\":[\"Featured on \",[\"date\"],\" at \",[\"time\"]],\"EQtz4D\":[\"Open a few links and check whether they still feel native to the page.\"],\"EU3tBD\":[\"Link removed.\"],\"EetoJL\":[\"Guide the eye without taking over the layout.\"],\"Eiv3bO\":[\"Buttons can stay steady, but links, thread markers, and subtle emphasis should feel closer to ink on paper than dashboard chrome.\"],\"ElTnWL\":[\"Published on\"],\"EmQw8O\":[\"If this article still feels like a page you want to keep reading, the palette is probably close.\"],\"EsJdRp\":[\"Save theme\"],\"FESYvt\":[\"Describe this for people with visual impairments...\"],\"FEr96N\":[\"Theme\"],\"FGySZL\":[\"The default accent works best when it reads like a fountain-pen underline. Compare it against the\"],\"FM+KeU\":[\"No drafts yet. Save a draft to find it here.\"],\"Fdv5k7\":[\"What to look for while tuning it\"],\"FkMol5\":[\"Featured\"],\"Fxf4jq\":[\"Description (optional)\"],\"G2u/aQ\":[\"Download official Jant logos, icons, and preview assets.\"],\"GBJzTZ\":[\"Archive\"],\"GX2VMa\":[\"Create your admin account.\"],\"GY/1J4\":[\"Jant fallback canary string\"],\"GbIOhd\":[\"Reply quietly\"],\"GiRWtR\":[\"Why the default accent should feel written, not branded\"],\"GkpIs2\":[\"Remove this link from Collections? The destination won't change.\"],\"GorKul\":[\"Welcome to Jant\"],\"GxkJXS\":[\"Uploading...\"],\"H29JXm\":[\"+ ALT\"],\"H4lgRd\":[\"Authentication isn't set up. Check your server config.\"],\"HFPGej\":[\"No threads match these filters. Try adjusting your selection or clear all filters.\"],\"HG79RB\":[\"Post as Private\"],\"HNEHJP\":[\"Demo credentials are pre-filled — hit Sign In to continue.\"],\"HbAIQc\":[\"A reference link for checking whether the accent feels editorial instead of promotional.\"],\"Ht1V3q\":[\"For the same reason, inline code should stay neutral. Something like theme.siteAccent = soften(green, 12%) should not suddenly become the loudest thing on the page.\"],\"I22eN0\":[\"Shared links\"],\"I6zLrz\":[\"Use these when you need a transparent square logo, a shaped tile with a built-in background, a browser icon, or a default preview image.\"],\"ICsA6P\":[\"You have unsaved changes\"],\"IUX7p+\":[\"White logo on the Jant green rounded tile for app icon mockups, touch icons, directory listings, and other square placements that should feel softer.\"],\"IagCbF\":[\"URL\"],\"IjnQHI\":[\"with title\"],\"ImOQa9\":[\"Reply\"],\"IsI3kE\":[\"Nothing here yet. Add posts to one of these collections to fill this view.\"],\"J+2Rls\":[\"Leave blank to publish now. Use an earlier date when importing older posts.\"],\"J4tAHl\":[\"Headings should keep their hierarchy even when the accent gets softer.\"],\"JYj5R2\":[\"Browse files\"],\"JcD7qf\":[\"More actions\"],\"JqJ5Xv\":[\"Latest\"],\"JuN5GC\":[\"No file selected. Choose a file to upload.\"],\"JwLPQ/\":[\"This sign-in link has expired. Return to \"],\"KOqvXP\":[\"Do not recolor, stretch, rotate, outline, or add effects to the logo.\"],\"KbS2K9\":[\"Reset Password\"],\"KdSsVl\":[\"Author (optional)\"],\"Khu3PV\":[\"Publish settings\"],\"KiJn9B\":[\"Note\"],\"KlZ+t+\":[\"%name% + %count% more\"],\"KsvRin\":[\"Hide from Latest\"],\"KzmC5L\":[\"Controls\"],\"L7svJg\":[\"Reading\"],\"Lbkbwy\":[\"A quote card for judging accent color against softer, citation-heavy content.\"],\"LcvzvX\":[\"Tap to retry\"],\"LkA8jz\":[\"Add alt text\"],\"LxRg6f\":[\"live theme controls\"],\"M4tzVU\":[\"Latest posts\"],\"M8kJqa\":[\"Drafts\"],\"MHrjPM\":[\"Title\"],\"MILa7n\":[\"Square tile\"],\"MSc/Yq\":[\"Do you want to publish your changes or discard them?\"],\"Mc7+6G\":[\"Enter a valid URL starting with http://, https://, or mailto:.\"],\"MdMyne\":[\"Source link (optional)\"],\"MiMY3Q\":[\"Apple touch icon\"],\"MiyoI7\":[\"default note sample\"],\"MqghUt\":[\"Search posts...\"],\"Myqkib\":[\"Create a collection to get started.\"],\"N8UzTV\":[\"Replies\"],\"NAFbuE\":[\"Search snippet\"],\"NH9Z1R\":[\"Start here\"],\"NqsRbb\":[\"Jant logo\"],\"NvXuWk\":[\"Won't move the thread to the top of latest.\"],\"O1367B\":[\"All collections\"],\"O3oNi5\":[\"Email\"],\"OEdMhi\":[\"The best default color is the one you notice only after reading for a while.\"],\"OEt/to\":[\"Guidelines\"],\"OJxdgi\":[\"Keep this link under 200 characters.\"],\"OaoJcz\":[\"Social preview\"],\"OmfDbR\":[\"Site accent\"],\"Ovks1h\":[\"A softer blue feels more like ink than product chrome.\"],\"P/sHNL\":[\"Use this page to judge buttons, links, cards, forms, thread accents, and quiet surfaces before changing a theme globally.\"],\"Q/uoSA\":[\"Quiet here for now.\"],\"Q2mGA7\":[\"Clear filter\"],\"QBqVyM\":[\"Home screen icon for iPhone and iPad shortcuts.\"],\"QebAts\":[\"Link added.\"],\"Qgbxdw\":[\"Designing a calmer default accent for Jant\"],\"Qn9Ao8\":[\"Circle tile\"],\"QyDt3L\":[\"File uploaded.\"],\"R5CMuK\":[\"Jant looks best when the accent feels editorial. Buttons can stay sturdy, but inline emphasis should feel like a pen mark, not a dashboard highlight.\"],\"R8AthW\":[\"Divider\"],\"R9Khdg\":[\"Auto\"],\"RAv3u7\":[\"Compare it against the theme controls\"],\"ROa4Ti\":[\"Interfaces for reading should guide the eye, not keep asking for attention.\"],\"RZOWDv\":[\"Add a custom shortcut to any page or site.\"],\"RdmNnl\":[\"Browser tab\"],\"RfGczC\":[\"Square logo\"],\"Rj01Fz\":[\"Links\"],\"S37om9\":[\"Included assets\"],\"S8NCfs\":[\"Save to drafts to edit and post at a later time.\"],\"SJGVAw\":[\"Feel editorial and slightly quieter.\"],\"SJmfuf\":[\"Site Name\"],\"SaNhJE\":[\"feel deliberate instead of washed out.\"],\"SpTWH3\":[\"Download SVG\"],\"SvRuJt\":[\"Field Notes on Interface Tone\"],\"T/R+Qz\":[\"Primary\"],\"TNZKpI\":[\"Danger\"],\"TvaTxw\":[\"Doesn't appear in Latest. Still appears in collections you add it to.\"],\"UIMXHD\":[\"Remove Divider\"],\"UaZwcz\":[\"More options are available after you create it.\"],\"Uc5y7o\":[\"Choose the standard logo for websites, docs, directories, and editorial layouts.\"],\"V18SVO\":[\"Use the logo on light backgrounds.\"],\"V4WsyL\":[\"Add Link\"],\"VCA6B2\":[\"These are actual feed components with real footers, summaries, and inline links. Use this section to judge whether the theme still feels calm once it is applied to realistic content.\"],\"VNqFYa\":[\"Loading post...\"],\"WCOanD\":[\"This reference is useful because it treats links and citations as part of the reading rhythm. Keep that in mind while tuning the\"],\"WbIbzR\":[\"Checking link...\"],\"WcWS//\":[\"Download file\"],\"WhsN3P\":[\"A good default accent in Jant should feel like editorial structure, not product branding. That means links, emphasis, and thread cues can be visible without turning the page into UI chrome.\"],\"Wn+/rH\":[\"Transparent square\"],\"XU7b+L\":[\"Primary logo files\"],\"XV1mAn\":[\"Only visible when signed in.\"],\"XrnWzN\":[\"Published!\"],\"YIix5Y\":[\"Search...\"],\"YUglt2\":[\"Generating a link...\"],\"YXiA6e\":[\"Primary button\"],\"Ygx3Yl\":[\"Small browser icon used in tabs and bookmarks.\"],\"Z6NwTi\":[\"Save as Draft\"],\"ZGs2so\":[\"Delete this collection permanently? Posts inside won't be removed.\"],\"ZV5ykW\":[\"Download PNG\"],\"ZhhOwV\":[\"Quote\"],\"ZmSeP+\":[\"Save to drafts?\"],\"ZxFuun\":[[\"count\",\"plural\",{\"one\":[\"Found \",\"#\",\" result\"],\"other\":[\"Found \",\"#\",\" results\"]}]],\"a5j82I\":[\"No collections match that search. Try a different name.\"],\"aHTB7P\":[\"Supplementary content attached to your post\"],\"aMEyv0\":[\"Stay sturdy and readable.\"],\"aN6wx0\":[\"Nothing in Featured yet. Mark a post as featured to show it here.\"],\"aYpXKS\":[\"and checking whether the accent is guiding attention or pulling too hard.\"],\"aaGV/9\":[\"New Link\"],\"af+9p6\":[\"Quiet metadata\"],\"an5hVd\":[\"Images\"],\"ao77hr\":[[\"count\",\"plural\",{\"one\":[\"#\",\" hidden post\"],\"other\":[\"#\",\" hidden posts\"]}]],\"auFlOr\":[\"Icons and previews\"],\"avuFKG\":[\"threads\"],\"bFpC86\":[\"Everything in one download\"],\"bGtMpA\":[\"Add a label and URL.\"],\"bHOiy1\":[\"Password changes are off in demo mode. Sign in with the shared demo credentials.\"],\"bbdNeX\":[\"Sign in\"],\"bfCbdi\":[\"Current post\"],\"bkBJmZ\":[\"This is useful as a color check because it puts the accent next to quotation styling, metadata, and a quieter explanatory paragraph. Compare it back to the\"],\"bzSI52\":[\"Discard\"],\"c2JRUS\":[\"Generate automatically\"],\"cIoW7X\":[\"Inline link\"],\"cTUByn\":[\"Newest first\"],\"cb7FR8\":[\"White logo on the Jant green square tile for platforms and layouts that expect a true edge-to-edge square.\"],\"cgmi4V\":[\"Delete Draft\"],\"cnGeoo\":[\"Delete\"],\"d+F4pf\":[\"The image should sit quietly inside the article instead of feeling like a card preview.\"],\"d/o/BH\":[\"Couldn't publish. Saved as draft.\"],\"dD7NPy\":[\"Outline\"],\"dEgA5A\":[\"Cancel\"],\"dUsGbd\":[\"The right accent should disappear into the writing until you need it.\"],\"dXoieq\":[\"Summary\"],\"dYKrp3\":[\"Hidden from Latest\"],\"dbUuAj\":[\"Appears in Latest.\"],\"df4a/r\":[\"Couldn't load this post. Try again.\"],\"ePK91l\":[\"Edit\"],\"eWLklq\":[\"Quotes\"],\"f4MAoA\":[\"Some uploads failed. Saved as draft.\"],\"f5s9EI\":[\"Press N to write\"],\"f6Hub0\":[\"Sort\"],\"f8fH8W\":[\"Design\"],\"fD+f7T\":[\"RSS feed\"],\"fKrDxS\":[\"Brand tile\"],\"fMPkxb\":[\"Show more\"],\"fqDzSu\":[\"Rate\"],\"fttd2R\":[\"My Collection\"],\"gCcxP/\":[\"Threads can include up to \",[\"count\"],\" posts.\"],\"gFdWl+\":[\"A long-form article sample for checking the default palette in a true reading context.\"],\"gNKz6Z\":[\"Collection deleted.\"],\"gXH9r/\":[\"Open raw PNG\"],\"gj52YE\":[\"This collection is empty. Add posts from the editor.\"],\"gpaPhA\":[\"Helps screen readers describe the image\"],\"h5RcXU\":[\"Post hidden\"],\"hLlWo5\":[\"A few simple rules.\"],\"hWpUeY\":[\"Auto link\"],\"hXzOVo\":[\"Next\"],\"heSQoS\":[\"Paste a URL...\"],\"hrkGms\":[\"Search\"],\"i0vDGK\":[\"Sort Order\"],\"i5+Y7d\":[\"Download the official Jant logo, icons, and preview files.\"],\"i6kro6\":[\"Edit custom link\"],\"i6nDCI\":[\"Choose a new password.\"],\"iG7KNr\":[\"Logo\"],\"iH8pgl\":[\"Back\"],\"ilSmIt\":[\"Hard edge\"],\"iu7tUI\":[\"Breadcrumb\"],\"jAXE5p\":[\"Reverse logo\"],\"jAqB/k\":[\"Post privately\"],\"jQflRT\":[\"This uses the real single-post detail rendering with a longer article, inline image, tables, lists, quotes, and code. The content column stays at the same width as the live site.\"],\"jd+8Mm\":[\"Social preview image\"],\"jdJOV1\":[\"Settings\"],\"ji7oVU\":[\"Edit post\"],\"jpctdh\":[\"View\"],\"jvyYZG\":[\"What's on your mind...\"],\"k3Iw35\":[\"Switch to the white logo when the standard green version would lose contrast.\"],\"kPMIr+\":[\"Give it a title...\"],\"kj6ppi\":[\"entry\"],\"kr39oD\":[\"No collections yet. Start one to organize posts by topic.\"],\"kzvWob\":[\"Link to the post archive\"],\"laT1IJ\":[\"iOS home screen\"],\"lb+Xwx\":[\"Custom link\"],\"m16xKo\":[\"Add\"],\"mKT7g0\":[\"Text attachment\"],\"mc/vLq\":[\"This link is already in use. Choose something else.\"],\"muKqfV\":[\"Featured\"],\"n1ekoW\":[\"Sign In\"],\"n3ReIn\":[\"Collections\"],\"n6QD94\":[\"Oldest first\"],\"nFukaP\":[\"Wrong email or password. Check your credentials and try again.\"],\"nV6twc\":[\"Organize\"],\"nd8Puv\":[\"White logo on the Jant green circle for profile images, badges, and other round placements where you want a ready-made asset.\"],\"ndrEYW\":[\"When the accent is slightly warmer and less literal, the whole page feels more like a writing space and less like product UI.\"],\"nfU386\":[\"Sign in if this is your space.\"],\"o21Y+P\":[\"entries\"],\"oO0hKx\":[[\"count\",\"plural\",{\"one\":[\"#\",\" more post\"],\"other\":[\"#\",\" more posts\"]}]],\"oTu7Wt\":[\"Combined Collections\"],\"ode0+L\":[\"Theme sample\"],\"ogssnn\":[\"with media\"],\"ovBPCi\":[\"Default\"],\"p1Z67P\":[\"When primary is too rigid, the whole page starts reading like product UI instead of writing space.\"],\"p2/GCq\":[\"Confirm Password\"],\"pB0OKE\":[\"New Divider\"],\"pBHx39\":[\"Dark backgrounds\"],\"pVrU5x\":[\"If this page feels too branded, the first place to soften is the default theme’s site accent, not the border or body text.\"],\"pvnfJD\":[\"Dark\"],\"q+hNag\":[\"Collection\"],\"q5YRzz\":[\"Color check\"],\"q8RviX\":[\"Titled\"],\"qcawwg\":[\"Publish now\"],\"qiN9NB\":[\"Surface\"],\"qt89I8\":[\"Draft saved.\"],\"quvfGs\":[\"instead of judging it as an isolated swatch.\"],\"r7kcaA\":[\"Drag collections, links, and dividers into the order you want.\"],\"rA2TFI\":[\"Switch the palette and mode without opening settings or changing the active site theme.\"],\"rV8ZnP\":[\"Edit publish date\"],\"rdUucN\":[\"Preview\"],\"s8G5Or\":[\"This upload would exceed your shared hosted media limit. Remove files or upgrade storage to continue.\"],\"s9gHf5\":[\"your-post-link\"],\"sER+bs\":[\"Files\"],\"sQpDn6\":[\"Exit fullscreen\"],\"sgr2wQ\":[\"collection\"],\"slujBW\":[\"Use lowercase letters, numbers, and hyphens only.\"],\"syiAKf\":[\"note treatment\"],\"t42hIC\":[\"Everything most people need is in one ZIP.\"],\"tCctex\":[\"The brand pack includes SVG logos, a transparent square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and the default social preview image.\"],\"tKlWWY\":[\"Emoji\"],\"tSWVu5\":[\"Published on \",[\"date\"],\" at \",[\"time\"]],\"tfDRzk\":[\"Save\"],\"tgSBSE\":[\"Remove Link\"],\"uowbPn\":[\"Remove attachment\"],\"v3E8iS\":[\"A practical checklist\"],\"vSJd18\":[\"Video\"],\"vSYKYI\":[\"Main feed\"],\"vXCC6J\":[\"Something doesn't look right. Check the form and try again.\"],\"vcpc5o\":[\"Close menu\"],\"vdFnYM\":[\"Reset link\"],\"vdvpU5\":[\"/archive?format=quote or https://example.com\"],\"vgpfCi\":[\"Save draft\"],\"vpSPA1\":[\"Auth secret is missing. Check your environment variables.\"],\"vzU4k9\":[\"New Collection\"],\"w0Emel\":[\"Suggested link\"],\"w6mlns\":[\"Article detail page\"],\"wJ+GRy\":[\"All visibility\"],\"wL3cK8\":[\"Latest\"],\"wja8aL\":[\"Untitled\"],\"wlnK1t\":[\"A single ZIP with the main logo, reverse logo, square PNG, rounded, square, and circle tiles, plus favicon, Apple touch icon, and social preview image.\"],\"wm3Zlr\":[\"All years\"],\"xCWek4\":[\"File storage isn't set up. Check your server config.\"],\"xVrkxi\":[\"quiet design\"],\"xVvw1i\":[\"This reset link is no longer valid. Request a new one to continue.\"],\"xYilR2\":[\"Media\"],\"xeiujy\":[\"Text\"],\"xhTx3y\":[\"Choose the standard logo for most placements and the reverse logo when you need more contrast.\"],\"y28hnO\":[\"Post\"],\"y2o/Y0\":[\"This Link Has Expired\"],\"yGZVl1\":[\"More\"],\"yQ2kGp\":[\"Load more\"],\"yUtAh2\":[\"New Thread\"],\"ycM1Xg\":[\"No results. Try different keywords.\"],\"ynMAhG\":[\"Default logo\"],\"yzF66j\":[\"Link\"],\"zBFr9G\":[\"Paste a long article, AI response, or any text...\\n\\nMarkdown formatting will be preserved.\"],\"zJDAbh\":[\"Don't save\"],\"zcDmsG\":[\"Featured posts\"],\"zoK+eO\":[\"Add a title before posting this link.\"],\"zucql+\":[\"Menu\"],\"zwBp5t\":[\"Private\"]}");
1852
1852
  //#endregion
1853
1853
  //#region src/i18n/locales/settings/en.ts
1854
1854
  var messages$2 = JSON.parse("{\"+9JI/F\":[\"Connecting will sync your site onto \",[\"repo\"],\"'s default branch on top of its existing history. Existing files outside Jant's managed paths are kept. This can't be undone.\"],\"+AXdXp\":[\"Label and URL are required\"],\"+K0AvT\":[\"Disconnect\"],\"+zy2Nq\":[\"Type\"],\"/3H2/s\":[\"This hosted site signs in through \",[\"providerLabel\"],\". Manage password and hosted access there.\"],\"/JnyjR\":[\"Toggle built-in navigation items. Their order controls what shows in the header and which feed the homepage opens first.\"],\"0OGSSc\":[\"Avatar display updated.\"],\"0UzCUX\":[\"Update the password you use to sign in\"],\"10UtuM\":[\"CJK Font\"],\"14BEca\":[\"Read why\"],\"1F6Mzc\":[\"No navigation items yet. Add links or enable system items below.\"],\"1njn7W\":[\"Light\"],\"2B7t+s\":[\"Sessions and password\"],\"2DoBvq\":[\"Feeds\"],\"2FYpfJ\":[\"More\"],\"2MXb5X\":[\"Field notes on quiet design\"],\"2PTjMB\":[\"I want to delete \",[\"siteName\"]],\"2cFU6q\":[\"Site Footer\"],\"2oWZo7\":[\"Last commit\"],\"2uuy4H\":[\"Connected via Personal Access Token\"],\"35x8eZ\":[\"Showing \",[\"shown\"],\" of \",[\"total\"]],\"3Cw1AI\":[\"Add Collection\"],\"3VrybB\":[\"Redirect\"],\"3Yvsaz\":[\"302 (Temporary)\"],\"3n0zbB\":[\"Session management is off in demo mode. Use the shared demo session instead.\"],\"3sYJi5\":[\"Download a Hugo-compatible archive — host it statically or move to another Jant.\"],\"3wKq0C\":[\"Couldn't save. Try again in a moment.\"],\"49Bsal\":[\"Feed settings updated.\"],\"4Jge8E\":[\"Active Sessions\"],\"4KIa+q\":[\"Export downloaded.\"],\"4cEClj\":[\"Sessions\"],\"4zGJ5E\":[\"Delete Account Permanently\"],\"5QlUIt\":[\"Empty repository. Ready to connect.\"],\"5VQnR3\":[\"Use these when you want a feed URL that never changes.\"],\"5dpcN1\":[\"type to search all\"],\"69OXZB\":[\"Delete Hosted Site\"],\"6ArdBh\":[\"Uses featured posts for /feed.\"],\"6DjeBT\":[\"Demo sites always stay hidden from search engines.\"],\"6FFB7q\":[\"Uses the latest public posts for /feed.\"],\"6K1Vef\":[\"Delete this blog permanently? This cannot be undone.\"],\"6NpNLc\":[\"This repository has existing content.\"],\"6V3Ea3\":[\"Copied\"],\"746NHh\":[\"this blog\"],\"7811AW\":[\"This repository is already backing up this site.\"],\"7FaY4u\":[\"Usage\"],\"7G9YLi\":[\"Allow search engines to index my site\"],\"7MZxzw\":[\"Password changed.\"],\"7vhWI8\":[\"New Password\"],\"7z05Pf\":[\"Open the hosted site controls in \",[\"providerLabel\"],\" to cancel billing or permanently delete this site.\"],\"81nFIS\":[\"Passwords don't match. Make sure both fields are identical.\"],\"87a/t/\":[\"Label\"],\"89Upyo\":[\"That theme isn't available. Pick another one.\"],\"8BfEpW\":[\"Hosted account\"],\"8N/Mcp\":[\"Archive filter parameters (e.g. format=note&view=list)\"],\"8U2Z7f\":[\"New Custom URL\"],\"8ZsakT\":[\"Password\"],\"9+vGLh\":[\"Custom CSS\"],\"9As8Nu\":[\"Create one on GitHub\"],\"9Lsvt5\":[\"Signed in \",[\"date\"]],\"9T7Cwm\":[\"Redirects, vanity paths, and URL control\"],\"9aUyym\":[\"See where you're signed in and revoke old sessions\"],\"A1taO8\":[\"Search\"],\"AeXO77\":[\"Account\"],\"AnY+O9\":[\"Show \\\"Build with Jant\\\" at the bottom of the home page\"],\"ApZDMk\":[\"This is used for your favicon and apple-touch-icon. For best results, upload a square PNG with a solid background at least 512x512 pixels.\"],\"B495Gs\":[\"Archive\"],\"B4ESok\":[\"API reference\"],\"CTAEes\":[\"Select a repository\"],\"CjZZgz\":[\"This repository already has commits\"],\"DCKkhU\":[\"Current Password\"],\"DKKKeF\":[\"Manage password and hosted access in \",[\"providerLabel\"]],\"ECIBO2\":[\"Controls the language of the dashboard and settings. Public pages stay in English.\"],\"EO3I6h\":[\"Upload didn't go through. Try again in a moment.\"],\"Enslfm\":[\"Destination\"],\"F7FKwe\":[\"Anything you paste here has full access to your visitors' browsers. Only use code from sources you trust.\"],\"FkMol5\":[\"Featured\"],\"G/1oP+\":[\"Remove the webhook and stop syncing. Your repository content will not be deleted.\"],\"G0qJsQ\":[\"Security token missing. Refresh the page and try again.\"],\"G39wnK\":[\"Back up and sync content with a GitHub repository\"],\"GMMWcy\":[\"Name, metadata, language, and search defaults\"],\"GXsAby\":[\"Revoke\"],\"GxkJXS\":[\"Uploading...\"],\"GzKzUa\":[\"Demo limits\"],\"HKH+W+\":[\"Data\"],\"Hp1l6f\":[\"Current\"],\"HxlY7t\":[\"Changing this updates what subscribers get from /feed.\"],\"HxuOlm\":[\"Site Header\"],\"I6gXOa\":[\"Path\"],\"ID38tA\":[\"Account deletion is off in demo mode. The shared demo resets separately.\"],\"IF9tPu\":[\"When to use site export, database backups, and recovery drills.\"],\"IW5PBo\":[\"Copy Token\"],\"IagCbF\":[\"URL\"],\"IreQBq\":[\"Repository\"],\"J6bLeg\":[\"Add a custom link to any URL\"],\"JL7LF5\":[\"available CSS variables, data attributes, and examples.\"],\"JTviaO\":[\"Manage sign-in security, exports, and irreversible actions.\"],\"JcD7qf\":[\"More actions\"],\"JjX0OO\":[\"Copy your token now — it won't be shown again.\"],\"JrFTcr\":[\"Connecting…\"],\"JuN5GC\":[\"No file selected. Choose a file to upload.\"],\"KDw4GX\":[\"Try again\"],\"KSgo21\":[\"Pick a repository\"],\"KVVYBh\":[\"Add collection to navigation\"],\"KiJn9B\":[\"Note\"],\"L3DEwT\":[\"Remove this avatar? Your favicon and header icon will go back to the default.\"],\"L4t4/q\":[\"March 14\"],\"LdyooL\":[\"link\"],\"M/D8PK\":[\"+ Install on another account\"],\"M/haSd\":[\"Always show the light version of the theme.\"],\"M2kIWU\":[\"Font theme\"],\"M6CbAU\":[\"Toggle edit panel\"],\"Me5t5H\":[\"Connect a GitHub repository to automatically back up your posts as Markdown files. Edits on GitHub sync back to your site.\"],\"MtENL9\":[\"Tune how your site looks, reads, and runs.\"],\"N/8NPV\":[\"Before deleting, download a site export. You won't be able to recover this account after deletion.\"],\"N7UNHY\":[\"Featured feed\"],\"NHnUHF\":[\"Favicon and the profile mark in your header\"],\"NU2Fqi\":[\"Save CSS\"],\"Nldjdr\":[\"No custom URLs yet. Create one to add redirects or custom paths for posts.\"],\"O7rgs6\":[\"Header RSS points to your \",[\"feed\"],\" feed (/feed). Change what /feed returns in General.\"],\"OSJXFg\":[\"Applies to your entire site, including admin pages. Pick a palette, then choose whether it follows the system or stays fixed.\"],\"PEUV5I\":[\"Code injection updated.\"],\"PZ7HJ8\":[\"Blog Avatar\"],\"Pwqkdw\":[\"Loading…\"],\"PxJ9W6\":[\"Generate Token\"],\"Q/6Y+2\":[\"Needs Contents (read/write) and Webhooks (read/write) on the target repository.\"],\"Q30z/l\":[\"Remove this collection from navigation? The collection itself won't be deleted.\"],\"Q99OtV\":[\"Pin a collection to your navigation bar. An asterisk (*) appears next to collections updated in the last 48 hours.\"],\"QZmz0H\":[\"Built-in links\"],\"Qnrzvb\":[\"Active Tokens\"],\"R6Z4LE\":[\"Download failed. Please try again.\"],\"R9Khdg\":[\"Auto\"],\"RxsRD6\":[\"Time Zone\"],\"SJmfuf\":[\"Site Name\"],\"SKZhW9\":[\"Token name\"],\"SVQQPe\":[\"Couldn't connect. Check the error and try again.\"],\"TpF3v+\":[\"Injected before </head>. Use for analytics, custom meta tags, and styles that must load early.\"],\"Tz0i8g\":[\"Settings\"],\"UFK415\":[\"Site-wide HTML for analytics and widgets\"],\"Uj/btJ\":[\"Display avatar in my site header\"],\"UsODUn\":[\"Select an account\"],\"UxKoFf\":[\"Navigation\"],\"V+bhUy\":[\"Install GitHub App\"],\"V4WsyL\":[\"Add Link\"],\"V5pZwT\":[\"Search settings updated.\"],\"VXUPla\":[\"Connect with GitHub App\"],\"VhMDMg\":[\"Change Password\"],\"Vn3jYy\":[\"Navigation items\"],\"VoZYGU\":[\"This will permanently delete all your data — posts, media, collections, settings, and your account. Your blog will be reset to its initial setup state. This cannot be undone.\"],\"Weq9zb\":[\"General\"],\"Wi9i06\":[\"Follow each visitor's system preference.\"],\"Wx1M8N\":[\"Install the GitHub App to grant access without managing personal tokens. Permissions are scoped per repository and revocable from GitHub.\"],\"X+8FMk\":[\"Current password doesn't match. Try again.\"],\"X1G9eY\":[\"Navigation Preview\"],\"X9Hujr\":[\"Manual Push\"],\"XtBJV8\":[\"Checking repository…\"],\"Xtc16w\":[\"Refresh repository list\"],\"Y/F35r\":[\"Create a post with curl:\"],\"YF6zHf\":[\"Site settings updated.\"],\"YdG2RF\":[\"Export Site\"],\"YwhjRx\":[\"Manage Account\"],\"ZDY7Fy\":[\"Syncing…\"],\"ZQKLI1\":[\"Danger Zone\"],\"ZS/CBL\":[\"Delete this navigation link? Visitors won't see it in your site header anymore.\"],\"ZhhOwV\":[\"Quote\"],\"ZiooJI\":[\"API Tokens\"],\"Zm7Qb0\":[\"Backup & Restore Guide\"],\"ZmUkwN\":[\"Add custom link to navigation\"],\"a14mj8\":[\"Unknown device\"],\"a3LDKx\":[\"Security\"],\"aAIQg2\":[\"Appearance\"],\"aFkzVF\":[\"The slug of the target post or collection\"],\"alKG0+\":[\"Font Theme\"],\"anibOb\":[\"About this blog\"],\"any7NR\":[\"Theming guide\"],\"b+/jO6\":[\"301 (Permanent)\"],\"bHOiy1\":[\"Password changes are off in demo mode. Sign in with the shared demo credentials.\"],\"bHYIks\":[\"Sign Out\"],\"bmrL08\":[\"Demo mode hides sessions, password changes, and account deletion. Export still works.\"],\"c3MN2z\":[\"all available endpoints and request formats.\"],\"cSDy01\":[\"Custom CSS updated.\"],\"clzoNp\":[\"Always show the dark version of the theme.\"],\"cnGeoo\":[\"Delete\"],\"d3FRkY\":[\"Could not copy. Try again.\"],\"d5oGUo\":[\"Create a new repository on GitHub\"],\"dEgA5A\":[\"Cancel\"],\"dTXUY+\":[\"Confirm account deletion\"],\"dYKrp3\":[\"Hidden from Latest\"],\"dk7TCH\":[\"Permanently delete all data and reset the blog\"],\"drodVV\":[\"No collections yet. Create one first, then add it to your navigation.\"],\"dsWkIw\":[\"Disconnect from GitHub? The webhook will be removed. Your repository content will not be deleted.\"],\"e/tSI5\":[\"Navigation order updated.\"],\"ePK91l\":[\"Edit\"],\"ebQKK7\":[\"Site\"],\"egK+Yy\":[\"Bearer tokens for scripts and automation\"],\"ehj/zN\":[\"Redirect Type\"],\"eneWvv\":[\"Draft\"],\"erTMh7\":[\"Last synced\"],\"f+m8jj\":[\"Feed URL copied.\"],\"f8fH8W\":[\"Design\"],\"fWYqkz\":[\"Code Injection\"],\"gOWiTY\":[\"Load a serif font optimized for Chinese, Japanese, or Korean content.\"],\"gZ5owP\":[\"Search repositories\"],\"gbqbh6\":[\"Safe to leave this page — syncing continues in the background.\"],\"gkFvVN\":[\"Injected before </body>. Use for chat widgets and scripts that should not block page load.\"],\"gtQsRO\":[\"Create Custom URL\"],\"hBO/y4\":[\"Security token expired. Refresh the page and try again.\"],\"hGmyDl\":[\"Tokens let you access the API from scripts, shortcuts, and other tools without signing in.\"],\"hIHkRy\":[\"Connected via GitHub App\"],\"hdSi1b\":[\"Type \",[\"repo\"],\" to confirm\"],\"he3ygx\":[\"Copy\"],\"i0qMbr\":[\"Home\"],\"iEUzMn\":[\"system\"],\"iSLIjg\":[\"Connect\"],\"iVOMRi\":[\"Home settings updated.\"],\"icB4Cv\":[\"Drag links here to show them under the More menu\"],\"iiDXZc\":[\"Displayed at the bottom of all posts and pages.\"],\"j4VrG6\":[\"Download Export ZIP\"],\"j5nQL2\":[\"e.g. iOS Shortcuts\"],\"jUV7CU\":[\"Upload Avatar\"],\"jVUmOK\":[\"Markdown supported\"],\"jgBjXJ\":[\"Revoke this token? Any scripts using it will stop working.\"],\"jpctdh\":[\"View\"],\"k1ifdL\":[\"Processing...\"],\"kMXclu\":[\"Download a site export\"],\"kNiQp6\":[\"Pinned\"],\"kRhzWq\":[\"GitHub Sync\"],\"kVQs7s\":[\"Fine-grained styling overrides\"],\"ke1gWS\":[\"Custom URLs\"],\"kfcRb0\":[\"Avatar\"],\"kxDZ2i\":[\"This code runs on every page of your site.\"],\"l2Op2p\":[\"Query Parameters\"],\"lLW3vJ\":[\"Target Slug\"],\"lYHJih\":[\"Revoke this session? That device will need to sign in again.\"],\"mLOk1i\":[\"Push all posts to GitHub right now instead of waiting for the next automatic sync.\"],\"mSNmrX\":[\"List posts:\"],\"nK07ni\":[\"Choose a typographic direction for your site. Each theme changes both the font pairing and the reading rhythm.\"],\"nbfdhU\":[\"Integrations\"],\"o/vNDE\":[\"lets you override any theme variable.\"],\"oGC9uP\":[\"owner/repo\"],\"oH2JHg\":[\"We'll prefill the name \",[\"name\"],\". The list refreshes on return.\"],\"oKOOsY\":[\"Color Theme\"],\"oL535e\":[\"Not synced yet\"],\"oNA4If\":[\"All collections are already in your navigation.\"],\"pZq3aX\":[\"Upload failed. Please try again.\"],\"pgTIrt\":[\"Choose the GitHub account and repository to sync with this site.\"],\"psoxDF\":[\"That font theme isn't available. Pick another one.\"],\"pvnfJD\":[\"Dark\"],\"q+hNag\":[\"Collection\"],\"r5EW6f\":[\"This repository is already backing up another Jant site (\",[\"host\"],\"). Pick a different repository.\"],\"rEspiY\":[\"Navigation placement updated.\"],\"rFmBG3\":[\"Color theme\"],\"rlonmB\":[\"Couldn't delete. Try again in a moment.\"],\"satWc6\":[\"Main RSS feed\"],\"sgr2wQ\":[\"collection\"],\"sqxcaY\":[\"Created \",[\"date\"]],\"sxkWRg\":[\"Advanced\"],\"t/YqKh\":[\"Remove\"],\"t3hvHq\":[\"Sync Now\"],\"tfDRzk\":[\"Save\"],\"tvgAq5\":[\"No accounts authorized yet\"],\"u1VTd3\":[\"Palette, surface tone, and overall mood\"],\"u3wRF+\":[\"Published\"],\"u6KOjV\":[\"Want more control?\"],\"udPwLB\":[\"Header\"],\"ui6aMF\":[\"These devices are currently signed in to your account. Revoke any session you don't recognize.\"],\"vBEKwo\":[\"Manage this site's active sessions here. Password and hosted access are managed through \",[\"providerLabel\"],\".\"],\"vRldcl\":[\"Typography choices and reading texture\"],\"vSYKYI\":[\"Main feed\"],\"vTuib7\":[\"This controls what /feed returns.\"],\"vXIe7J\":[\"Language\"],\"vmQmHx\":[\"Add custom CSS to override any styles. Use data attributes like [data-page], [data-post], [data-format] to target specific elements.\"],\"vzX5FB\":[\"Delete Account\"],\"w8Rv8T\":[\"Label is required\"],\"wL3cK8\":[\"Latest\"],\"wPmHHc\":[\"Quiet surfaces let writing lead.\"],\"wW6NCp\":[\"Last error\"],\"wc+17X\":[\"/* Your custom CSS here */\"],\"wuLtXn\":[\"No active sessions right now. Signed-in devices show up here.\"],\"xCWek4\":[\"File storage isn't set up. Check your server config.\"],\"xHt036\":[\"Personal Access Token\"],\"xbN8dp\":[\"Soft color should still carry a clear reading rhythm.\"],\"y28hnO\":[\"Post\"],\"y8Md/V\":[\"Language and time updated.\"],\"yNCqOt\":[\"Latest feed\"],\"yQ3kNF\":[\"Type the following phrase to confirm:\"],\"ydq1k2\":[\"Pick an account first\"],\"yjjCV8\":[\"Fixed feed URLs\"],\"yjkELF\":[\"Confirm New Password\"],\"yzF66j\":[\"Link\"],\"z6wakA\":[\"A short intro shown on your home page.\"],\"zEizrk\":[\"Last used \",[\"date\"]],\"zSURJW\":[\"No repositories match.\"],\"zXH2jX\":[\"Language & Time\"],\"zlcDd2\":[\"Delete this custom URL? Visitors using it won't be redirected anymore.\"],\"zwBp5t\":[\"Private\"],\"zxRN6H\":[\"Header links, home feed, and overflow menu\"]}");
@@ -3352,15 +3352,289 @@ function normalizeThemeColorForMeta(color) {
3352
3352
  * internal paths (e.g. `/_assets/client-HASH.js`) embedded by the Worker build
3353
3353
  * from the Vite client manifest. Used only in production (IS_VITE_DEV=false).
3354
3354
  */ var IS_VITE_DEV = typeof __JANT_DEV__ !== "undefined" && __JANT_DEV__ === true;
3355
- var CORE_VERSION = "0.3.43-8e1ad0b8fb20998c";
3356
- var CLIENT_JS_FILE = "/_assets/client-D95FNDg5.js";
3357
- var CLIENT_AUTH_JS_FILE = "/_assets/client-auth-CXILhW1b.js";
3358
- var CLIENT_CSS_FILE = "/_assets/client-C_kImWZj.css";
3355
+ var CORE_VERSION = "0.3.45-63de2a28e1b65691";
3356
+ var CLIENT_JS_FILE = "/_assets/client-dSfWfMe9.js";
3357
+ var CLIENT_AUTH_JS_FILE = "/_assets/client-auth-Dcon89Av.js";
3358
+ var CLIENT_CSS_FILE = "/_assets/client-DDs6NzB3.css";
3359
3359
  var CLIENT_CJK_CSS_FILE = "/_assets/client-cjk-B7Z0snDu.css";
3360
3360
  var CLIENT_CJK_TC_CSS_FILE = "/_assets/client-cjk-tc-BesJYrb2.css";
3361
3361
  var CLIENT_CJK_JP_CSS_FILE = "/_assets/client-cjk-jp-DZwrTzQC.css";
3362
3362
  var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
3363
3363
  //#endregion
3364
+ //#region src/ui/shared/icon-collector.ts
3365
+ /**
3366
+ * Request-scoped icon collector for SSR SVG sprite pattern.
3367
+ *
3368
+ * The <Icon> component registers each used icon name here during render.
3369
+ * At the end of <body>, <IconSprite> reads the collected set and emits a
3370
+ * single <svg><symbol>...</symbol></svg> block so every <use href="#icon-x">
3371
+ * reference in the page resolves to a definition.
3372
+ *
3373
+ * Mirrors the I18nProvider pattern in i18n/context.tsx: Hono JSX renders
3374
+ * synchronously per request, so a module-level singleton is safe.
3375
+ */ var currentCollector = null;
3376
+ /**
3377
+ * Start a new collection scope for the current render pass.
3378
+ * Call at the top of the root layout before children render.
3379
+ */ function resetIconCollector() {
3380
+ currentCollector = /* @__PURE__ */ new Set();
3381
+ }
3382
+ /**
3383
+ * Register an icon as used during this render.
3384
+ * Safe to call even when no collector is active (no-op).
3385
+ */ function collectIcon(name) {
3386
+ currentCollector?.add(name);
3387
+ }
3388
+ /**
3389
+ * Get the icon names collected so far during this render.
3390
+ * Returns an empty set if no collection scope is active.
3391
+ */ function getCollectedIcons() {
3392
+ return currentCollector ?? /* @__PURE__ */ new Set();
3393
+ }
3394
+ //#endregion
3395
+ //#region src/lib/featured-icons.ts
3396
+ /**
3397
+ * Shared icon definitions for featured post affordances.
3398
+ *
3399
+ * These paths are reused across Hono JSX, Lit, and exported static markup so
3400
+ * "Featured" keeps one visual language everywhere.
3401
+ */ var FEATURED_SPARKLE_PATH = "M12 3 10.1 10.1 3 12l7.1 1.9L12 21l1.9-7.1L21 12l-7.1-1.9Z";
3402
+ var FEATURED_SPARKLE_OFF_SLASH_PATH = "M4 4 20 20";
3403
+ /**
3404
+ * Build inline SVG markup for the shared featured sparkle icon.
3405
+ *
3406
+ * @param options - Render options for the sparkle icon.
3407
+ * @returns SVG markup string for inline insertion.
3408
+ * @example
3409
+ * getFeaturedIconSvg({ off: true, className: "icon-fine" });
3410
+ */ function getFeaturedIconSvg(options = {}) {
3411
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round"${options.className ? ` class="${options.className}"` : ""} aria-hidden="true"><path d="${FEATURED_SPARKLE_PATH}" />${options.off ? `<path d="${FEATURED_SPARKLE_OFF_SLASH_PATH}" />` : ""}</svg>`;
3412
+ }
3413
+ //#endregion
3414
+ //#region src/lib/decorative-quote-mark.ts
3415
+ var DECORATIVE_QUOTE_MARK_VIEWBOX = "0 0 96 96";
3416
+ var DECORATIVE_QUOTE_MARK_PATHS = ["M24.4 10.5C16.9 17.7 11.5 26.8 8.2 37.7C4.9 48.7 4.8 58.9 7.8 68.2C10.3 75.7 15.4 79.5 22.9 79.5C28 79.5 32.2 77.8 35.4 74.2C38.6 70.7 40.2 66.5 40.2 61.4C40.2 56.5 38.8 52.6 36 49.6C33.3 46.6 29.7 45.1 25.2 45.1C23.4 45.1 21.8 45.3 20.2 45.8C22.2 37.3 26.7 29.2 33.6 21.4L24.4 10.5Z", "M60.8 10.5C53.3 17.7 47.9 26.8 44.6 37.7C41.3 48.7 41.2 58.9 44.2 68.2C46.7 75.7 51.8 79.5 59.3 79.5C64.4 79.5 68.6 77.8 71.8 74.2C75 70.7 76.6 66.5 76.6 61.4C76.6 56.5 75.2 52.6 72.4 49.6C69.7 46.6 66.1 45.1 61.6 45.1C59.8 45.1 58.2 45.3 56.6 45.8C58.6 37.3 63.1 29.2 70 21.4L60.8 10.5Z"];
3417
+ DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}" />`).join("");
3418
+ //#endregion
3419
+ //#region src/ui/shared/custom-icons.ts
3420
+ /**
3421
+ * Custom (non-lucide) SVG symbol definitions used by the icon sprite.
3422
+ *
3423
+ * Icons here fall into three groups:
3424
+ * 1. Jant-specific paths (decorative quote mark, featured sparkle).
3425
+ * 2. Lucide-equivalent paths the UI uses with non-default stroke widths
3426
+ * or sizes that don't match the stock lucide symbol (we keep them as
3427
+ * custom symbols to preserve exact visual fidelity during refactor).
3428
+ * 3. Fixed-color SVGs (video play overlay) that don't use currentColor.
3429
+ *
3430
+ * Each entry provides everything needed to render a <symbol> element:
3431
+ * <symbol id="icon-${name}" viewBox={viewBox}>{inner}</symbol>
3432
+ * Consumers of <Icon name="..."> pass `size` / `className` on the outer
3433
+ * <svg><use/></svg>; `<symbol>` children inherit the outer attributes.
3434
+ */ var STROKE_THIN = "fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.35\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
3435
+ var STROKE_POST_BADGE = "fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
3436
+ var CUSTOM_SYMBOLS = {
3437
+ "featured-sparkle": {
3438
+ viewBox: "0 0 24 24",
3439
+ inner: `<path ${STROKE_THIN} d="${FEATURED_SPARKLE_PATH}" />`
3440
+ },
3441
+ "featured-sparkle-off": {
3442
+ viewBox: "0 0 24 24",
3443
+ inner: `<path ${STROKE_THIN} d="${FEATURED_SPARKLE_PATH}" /><path ${STROKE_THIN} d="${FEATURED_SPARKLE_OFF_SLASH_PATH}" />`
3444
+ },
3445
+ "decorative-quote": {
3446
+ viewBox: DECORATIVE_QUOTE_MARK_VIEWBOX,
3447
+ inner: DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}" />`).join("")
3448
+ },
3449
+ "post-collection-lock": {
3450
+ viewBox: "0 0 16 16",
3451
+ inner: `<rect ${STROKE_THIN} x="3" y="5.05" width="10" height="8.15" rx="2.2" /><path ${STROKE_THIN} d="M5.1 5.05V4.2a1.1 1.1 0 0 1 1.1-1.1h3.6a1.1 1.1 0 0 1 1.1 1.1v.85" />`
3452
+ },
3453
+ "post-menu-dots": {
3454
+ viewBox: "0 0 24 24",
3455
+ inner: `<circle cx="5" cy="12" r="1.75" fill="currentColor" /><circle cx="12" cy="12" r="1.75" fill="currentColor" /><circle cx="19" cy="12" r="1.75" fill="currentColor" />`
3456
+ },
3457
+ "post-external-link": {
3458
+ viewBox: "0 0 24 24",
3459
+ inner: `<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M7 17 17 7" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M9 7h8v8" />`
3460
+ },
3461
+ "post-reply": {
3462
+ viewBox: "0 0 24 24",
3463
+ inner: `<polyline fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" points="9 17 4 12 9 7" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M20 18v-2a4 4 0 0 0-4-4H4" />`
3464
+ },
3465
+ "link-domain": {
3466
+ viewBox: "0 0 24 24",
3467
+ inner: `<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />`
3468
+ },
3469
+ "link-preview-play": {
3470
+ viewBox: "0 0 68 48",
3471
+ inner: "<path class=\"link-preview-play-bg\" fill=\"rgba(0,0,0,.65)\" d=\"M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55C3.97 2.33 2.27 4.81 1.48 7.74.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z\" /><path fill=\"#fff\" d=\"M45 24L27 14v20\" />"
3472
+ },
3473
+ "link-preview-badge-play": {
3474
+ viewBox: "0 0 16 16",
3475
+ inner: "<path fill=\"currentColor\" d=\"M5.5 3.5v9l7-4.5z\" />"
3476
+ },
3477
+ "toast-success": {
3478
+ viewBox: "0 0 24 24",
3479
+ inner: `<circle fill="none" stroke="currentColor" stroke-width="2" cx="12" cy="12" r="10" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="m9 12 2 2 4-4" />`
3480
+ },
3481
+ "toast-error": {
3482
+ viewBox: "0 0 24 24",
3483
+ inner: `<circle fill="none" stroke="currentColor" stroke-width="2" cx="12" cy="12" r="10" /><path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="m15 9-6 6M9 9l6 6" />`
3484
+ },
3485
+ "toast-close": {
3486
+ viewBox: "0 0 24 24",
3487
+ inner: `<path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M18 6 6 18M6 6l12 12" />`
3488
+ },
3489
+ "post-status-pin": {
3490
+ viewBox: "0 0 24 24",
3491
+ inner: `<line ${STROKE_POST_BADGE} x1="12" x2="12" y1="17" y2="22" /><path ${STROKE_POST_BADGE} d="M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z" />`
3492
+ },
3493
+ "post-status-private": {
3494
+ viewBox: "0 0 24 24",
3495
+ inner: `<path ${STROKE_POST_BADGE} d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" /><path ${STROKE_POST_BADGE} d="M14.084 14.158a3 3 0 0 1-4.242-4.242" /><path ${STROKE_POST_BADGE} d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" /><path ${STROKE_POST_BADGE} d="m2 2 20 20" />`
3496
+ }
3497
+ };
3498
+ function getCustomSymbol(name) {
3499
+ return CUSTOM_SYMBOLS[name] ?? null;
3500
+ }
3501
+ /**
3502
+ * Return the viewBox for an icon's outer <svg> wrapper.
3503
+ *
3504
+ * This must match the <symbol>'s viewBox so the browser computes the correct
3505
+ * intrinsic aspect ratio. Without this, outer <svg> with `height: auto` in
3506
+ * CSS falls back to the 300×150 replaced-element default instead of the
3507
+ * icon's real aspect ratio.
3508
+ *
3509
+ * Falls back to lucide's "0 0 24 24" for lucide-sourced icons.
3510
+ */ function getIconViewBox(name) {
3511
+ return CUSTOM_SYMBOLS[name]?.viewBox ?? "0 0 24 24";
3512
+ }
3513
+ //#endregion
3514
+ //#region src/ui/shared/Icon.tsx
3515
+ /**
3516
+ * <Icon> — sprite-based SVG icon for SSR pages.
3517
+ *
3518
+ * Renders a lightweight <svg><use href="#icon-${name}"/></svg> stub and
3519
+ * registers the icon name with the request-scoped collector so the final
3520
+ * sprite (rendered by <IconSprite>) contains exactly the icons used on
3521
+ * this page.
3522
+ *
3523
+ * Name can refer to any lucide-static icon (kebab-case) or one of the
3524
+ * custom symbols defined in `custom-icons.ts`. Unknown names render an
3525
+ * empty <svg> — the same failure mode as the previous getIconSvg() path.
3526
+ *
3527
+ * Size: outer <svg> width/height in pixels. Defaults to 24 (lucide default).
3528
+ * Pass `class` to add CSS classes, e.g. for sizing via stylesheet instead
3529
+ * of inline width/height.
3530
+ */ var Icon$1 = ({ name, size, class: cls, "aria-label": ariaLabel, "aria-hidden": ariaHidden }) => {
3531
+ collectIcon(name);
3532
+ const hidden = ariaHidden ?? (ariaLabel ? void 0 : true);
3533
+ return /* @__PURE__ */ jsxDEV$1("svg", {
3534
+ viewBox: getIconViewBox(name),
3535
+ ...size !== void 0 ? {
3536
+ width: size,
3537
+ height: size
3538
+ } : {},
3539
+ ...cls ? { class: cls } : {},
3540
+ ...ariaLabel ? {
3541
+ "aria-label": ariaLabel,
3542
+ role: "img"
3543
+ } : {},
3544
+ ...hidden ? { "aria-hidden": "true" } : {},
3545
+ children: /* @__PURE__ */ jsxDEV$1("use", { href: `#icon-${name}` })
3546
+ });
3547
+ };
3548
+ //#endregion
3549
+ //#region src/lib/icons.ts
3550
+ /**
3551
+ * Shared icon utilities.
3552
+ *
3553
+ * Provides a small wrapper around lucide-static so server-rendered UI can fetch
3554
+ * SVG markup by kebab-case icon name.
3555
+ */
3556
+ /**
3557
+ * Convert a kebab-case icon name to PascalCase for lucide-static lookup.
3558
+ *
3559
+ * @param name - Kebab-case icon name such as "book-open"
3560
+ * @returns PascalCase name such as "BookOpen"
3561
+ */ function toPascalCase(name) {
3562
+ return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
3563
+ }
3564
+ /**
3565
+ * Get SVG markup for a Lucide icon by kebab-case name.
3566
+ *
3567
+ * @param name - Kebab-case icon name
3568
+ * @returns SVG string or null when the icon is unknown
3569
+ *
3570
+ * @example
3571
+ * ```ts
3572
+ * getIconSvg("book-open");
3573
+ * ```
3574
+ */ function getIconSvg(name) {
3575
+ const svg = lucideIcons[toPascalCase(name)];
3576
+ return typeof svg === "string" ? svg : null;
3577
+ }
3578
+ /**
3579
+ * Get the inner SVG contents for a Lucide icon (the path children only,
3580
+ * without the outer <svg> wrapper). Used by the icon sprite to build
3581
+ * <symbol> definitions.
3582
+ *
3583
+ * @param name - Kebab-case icon name
3584
+ * @returns Inner SVG markup (e.g. "<path ... />"), or null when unknown
3585
+ *
3586
+ * @example
3587
+ * ```ts
3588
+ * getIconInnerSvg("book-open");
3589
+ * // -> "<path d=\"...\"/><path d=\"...\"/>"
3590
+ * ```
3591
+ */ function getIconInnerSvg(name) {
3592
+ const svg = getIconSvg(name);
3593
+ if (!svg) return null;
3594
+ const openTagEnd = svg.indexOf(">");
3595
+ const closeTagStart = svg.lastIndexOf("</svg>");
3596
+ if (openTagEnd < 0 || closeTagStart < 0) return null;
3597
+ return svg.slice(openTagEnd + 1, closeTagStart);
3598
+ }
3599
+ /**
3600
+ * Default stroke/fill attributes inherited by <symbol> children when a
3601
+ * lucide icon is referenced via <use>. These mirror the attributes lucide
3602
+ * normally sets on the outer <svg> so the currentColor-based theming keeps
3603
+ * working through <use>.
3604
+ */ var LUCIDE_SYMBOL_ATTRS = "fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"";
3605
+ var LUCIDE_VIEWBOX = "0 0 24 24";
3606
+ //#endregion
3607
+ //#region src/ui/shared/IconSprite.tsx
3608
+ /**
3609
+ * <IconSprite> — emits the SVG symbol definitions used by this render.
3610
+ *
3611
+ * Must be rendered AFTER all <Icon> usages in the document (e.g. at the
3612
+ * end of <body>) so the collector has the full set of icon names. Hono
3613
+ * JSX stringifies synchronously in document order, so children declared
3614
+ * earlier in the tree are evaluated before this component.
3615
+ *
3616
+ * <use href="#icon-x"> anywhere in the document resolves correctly even
3617
+ * when the <symbol> definition comes after the reference, since browsers
3618
+ * wire up the references after the full document is parsed.
3619
+ */ function buildSymbol(name) {
3620
+ const custom = getCustomSymbol(name);
3621
+ if (custom) return `<symbol id="icon-${name}" viewBox="${custom.viewBox}">${custom.inner}</symbol>`;
3622
+ const inner = getIconInnerSvg(name);
3623
+ if (inner === null) return null;
3624
+ return `<symbol id="icon-${name}" viewBox="${LUCIDE_VIEWBOX}" ${LUCIDE_SYMBOL_ATTRS}>${inner}</symbol>`;
3625
+ }
3626
+ var IconSprite = () => {
3627
+ const symbols = Array.from(getCollectedIcons()).sort().map(buildSymbol).filter((s) => s !== null).join("");
3628
+ if (!symbols) return null;
3629
+ return /* @__PURE__ */ jsxDEV$1("svg", {
3630
+ xmlns: "http://www.w3.org/2000/svg",
3631
+ style: "display:none",
3632
+ "aria-hidden": "true",
3633
+ "data-icon-sprite": true,
3634
+ children: raw(symbols)
3635
+ });
3636
+ };
3637
+ //#endregion
3364
3638
  //#region src/ui/layouts/BaseLayout.tsx
3365
3639
  /**
3366
3640
  * Base HTML Layout
@@ -3371,6 +3645,7 @@ var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
3371
3645
  * In dev mode (Vite), serves assets via Vite's dev server.
3372
3646
  * In production, serves pre-built assets with content-hashed filenames.
3373
3647
  */ var BaseLayout = ({ title, description, lang, c, toast, faviconHref, appleTouchHref, faviconUrl, faviconVersion, socialImageUrl, canonicalHref, noindex, isAuthenticated = false, clientBundle, children }) => {
3648
+ resetIconCollector();
3374
3649
  const resolvedLang = lang ?? (c ? c.get("lang") : "en");
3375
3650
  const appConfig = c ? c.get("appConfig") : void 0;
3376
3651
  const resolvedSocialImagePath = socialImageUrl ?? faviconUrl ?? appConfig?.siteAvatarUrl ?? getJantIconHref("socialImage", appConfig?.sitePathPrefix || "");
@@ -3398,7 +3673,7 @@ var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
3398
3673
  const cjkSerifFont = appConfig?.cjkSerifFont ?? "off";
3399
3674
  const cjkStylesheetPath = cjkSerifFont === "zh-Hans" ? IS_VITE_DEV ? assetPath("/src/style-cjk.css") : toPublicAssetPath(CLIENT_CJK_CSS_FILE, assetBasePath) : cjkSerifFont === "zh-Hant" ? IS_VITE_DEV ? assetPath("/src/style-cjk-tc.css") : toPublicAssetPath(CLIENT_CJK_TC_CSS_FILE, assetBasePath) : cjkSerifFont === "ja" ? IS_VITE_DEV ? assetPath("/src/style-cjk-jp.css") : toPublicAssetPath(CLIENT_CJK_JP_CSS_FILE, assetBasePath) : cjkSerifFont === "ko" ? IS_VITE_DEV ? assetPath("/src/style-cjk-kr.css") : toPublicAssetPath(CLIENT_CJK_KR_CSS_FILE, assetBasePath) : null;
3400
3675
  const clientScriptPath = IS_VITE_DEV ? resolvedClientBundle === "full" ? assetPath("/src/client-auth.ts") : assetPath("/src/client.ts") : toPublicAssetPath(resolvedClientBundle === "full" ? CLIENT_AUTH_JS_FILE : CLIENT_JS_FILE, assetBasePath);
3401
- const faviconAssetVersion = resolvedFaviconVersion || "0.3.43-8e1ad0b8fb20998c";
3676
+ const faviconAssetVersion = resolvedFaviconVersion || "0.3.45-63de2a28e1b65691";
3402
3677
  const resolvedFaviconHref = faviconHref ?? (faviconAssetVersion ? toPublicPath(`/favicon.ico?v=${faviconAssetVersion}`, sitePathPrefix) : toPublicPath("/favicon.ico", sitePathPrefix));
3403
3678
  const resolvedAppleTouchHref = appleTouchHref ?? (faviconAssetVersion ? toPublicPath(`/apple-touch-icon.png?v=${faviconAssetVersion}`, sitePathPrefix) : toPublicPath("/apple-touch-icon.png", sitePathPrefix));
3404
3679
  const socialImageHref = resolvedSocialImagePath && (isFullUrl(resolvedSocialImagePath) || resolvedSocialImagePath.startsWith("//") ? resolvedSocialImagePath : toAbsoluteSiteUrl(resolvedSocialImagePath, appConfig?.siteUrl || "", sitePathPrefix));
@@ -3559,46 +3834,18 @@ var CLIENT_CJK_KR_CSS_FILE = "/_assets/client-cjk-kr-_3ZNI2ZP.css";
3559
3834
  class: `toast ${toast.type === "error" ? "toast-error" : "toast-success"}`,
3560
3835
  "data-init": "el.closest('[popover]').showPopover(); history.replaceState({}, '', location.pathname); setTimeout(() => { el.classList.add('toast-out'); el.addEventListener('animationend', () => el.remove()) }, 3000)",
3561
3836
  children: [
3562
- toast.type === "error" ? /* @__PURE__ */ jsxDEV$1("svg", {
3563
- xmlns: "http://www.w3.org/2000/svg",
3564
- fill: "none",
3565
- viewBox: "0 0 24 24",
3566
- "stroke-width": "2",
3567
- stroke: "currentColor",
3568
- children: [/* @__PURE__ */ jsxDEV$1("circle", {
3569
- cx: "12",
3570
- cy: "12",
3571
- r: "10"
3572
- }), /* @__PURE__ */ jsxDEV$1("path", { d: "m15 9-6 6M9 9l6 6" })]
3573
- }) : /* @__PURE__ */ jsxDEV$1("svg", {
3574
- xmlns: "http://www.w3.org/2000/svg",
3575
- fill: "none",
3576
- viewBox: "0 0 24 24",
3577
- "stroke-width": "2",
3578
- stroke: "currentColor",
3579
- children: [/* @__PURE__ */ jsxDEV$1("circle", {
3580
- cx: "12",
3581
- cy: "12",
3582
- r: "10"
3583
- }), /* @__PURE__ */ jsxDEV$1("path", { d: "m9 12 2 2 4-4" })]
3584
- }),
3837
+ toast.type === "error" ? /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "toast-error" }) : /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "toast-success" }),
3585
3838
  /* @__PURE__ */ jsxDEV$1("span", { children: toast.message }),
3586
3839
  /* @__PURE__ */ jsxDEV$1("button", {
3587
3840
  class: "toast-close",
3588
3841
  "data-on:click": "el.closest('.toast').classList.add('toast-out'); el.closest('.toast').addEventListener('animationend', () => el.closest('.toast').remove())",
3589
- children: /* @__PURE__ */ jsxDEV$1("svg", {
3590
- xmlns: "http://www.w3.org/2000/svg",
3591
- fill: "none",
3592
- viewBox: "0 0 24 24",
3593
- "stroke-width": "2",
3594
- stroke: "currentColor",
3595
- children: /* @__PURE__ */ jsxDEV$1("path", { d: "M18 6 6 18M6 6l12 12" })
3596
- })
3842
+ children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "toast-close" })
3597
3843
  })
3598
3844
  ]
3599
3845
  })
3600
3846
  }),
3601
- customBodyEndHtml && raw(customBodyEndHtml)
3847
+ customBodyEndHtml && raw(customBodyEndHtml),
3848
+ /* @__PURE__ */ jsxDEV$1(IconSprite, {})
3602
3849
  ]
3603
3850
  })]
3604
3851
  })] });
@@ -3908,7 +4155,7 @@ var STORAGE_DRIVERS = [
3908
4155
  envKeys: ["DEFAULT_THEME"]
3909
4156
  },
3910
4157
  DEFAULT_FONT_THEME: {
3911
- defaultValue: "tufte",
4158
+ defaultValue: "classic",
3912
4159
  envOnly: true,
3913
4160
  envKeys: ["DEFAULT_FONT_THEME"]
3914
4161
  },
@@ -4374,6 +4621,7 @@ function createTypeIdSchema(prefix) {
4374
4621
  "reset",
4375
4622
  "collections",
4376
4623
  "compose",
4624
+ "new",
4377
4625
  "static",
4378
4626
  "assets",
4379
4627
  "_assets",
@@ -9054,25 +9302,6 @@ var MediaGallery = ({ attachments, postPermalink }) => {
9054
9302
  });
9055
9303
  };
9056
9304
  //#endregion
9057
- //#region src/lib/featured-icons.ts
9058
- /**
9059
- * Shared icon definitions for featured post affordances.
9060
- *
9061
- * These paths are reused across Hono JSX, Lit, and exported static markup so
9062
- * "Featured" keeps one visual language everywhere.
9063
- */ var FEATURED_SPARKLE_PATH = "M12 3 10.1 10.1 3 12l7.1 1.9L12 21l1.9-7.1L21 12l-7.1-1.9Z";
9064
- var FEATURED_SPARKLE_OFF_SLASH_PATH = "M4 4 20 20";
9065
- /**
9066
- * Build inline SVG markup for the shared featured sparkle icon.
9067
- *
9068
- * @param options - Render options for the sparkle icon.
9069
- * @returns SVG markup string for inline insertion.
9070
- * @example
9071
- * getFeaturedIconSvg({ off: true, className: "icon-fine" });
9072
- */ function getFeaturedIconSvg(options = {}) {
9073
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.35" stroke-linecap="round" stroke-linejoin="round"${options.className ? ` class="${options.className}"` : ""} aria-hidden="true"><path d="${FEATURED_SPARKLE_PATH}" />${options.off ? `<path d="${FEATURED_SPARKLE_OFF_SLASH_PATH}" />` : ""}</svg>`;
9074
- }
9075
- //#endregion
9076
9305
  //#region src/ui/shared/PostFooter.tsx
9077
9306
  /**
9078
9307
  * Post Footer
@@ -9099,22 +9328,7 @@ var FEATURED_SPARKLE_OFF_SLASH_PATH = "M4 4 20 20";
9099
9328
  children: [showIcon && /* @__PURE__ */ jsxDEV$1("span", {
9100
9329
  class: "post-collection-primary-icon",
9101
9330
  "aria-hidden": "true",
9102
- children: /* @__PURE__ */ jsxDEV$1("svg", {
9103
- xmlns: "http://www.w3.org/2000/svg",
9104
- viewBox: "0 0 16 16",
9105
- fill: "none",
9106
- stroke: "currentColor",
9107
- "stroke-width": "1.35",
9108
- "stroke-linecap": "round",
9109
- "stroke-linejoin": "round",
9110
- children: [/* @__PURE__ */ jsxDEV$1("rect", {
9111
- x: "3",
9112
- y: "5.05",
9113
- width: "10",
9114
- height: "8.15",
9115
- rx: "2.2"
9116
- }), /* @__PURE__ */ jsxDEV$1("path", { d: "M5.1 5.05V4.2a1.1 1.1 0 0 1 1.1-1.1h3.6a1.1 1.1 0 0 1 1.1 1.1v.85" })]
9117
- })
9331
+ children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-collection-lock" })
9118
9332
  }), /* @__PURE__ */ jsxDEV$1("span", {
9119
9333
  class: "post-collection-tag-text",
9120
9334
  children: first.title
@@ -9185,29 +9399,9 @@ var PostMenuTriggerButton = ({ className = "post-menu-trigger" }) => {
9185
9399
  "aria-label": i18n._({ id: "JcD7qf" }),
9186
9400
  "aria-expanded": "false",
9187
9401
  "data-post-menu-trigger": true,
9188
- children: /* @__PURE__ */ jsxDEV$1("svg", {
9189
- xmlns: "http://www.w3.org/2000/svg",
9190
- width: "15",
9191
- height: "15",
9192
- viewBox: "0 0 24 24",
9193
- fill: "currentColor",
9194
- children: [
9195
- /* @__PURE__ */ jsxDEV$1("circle", {
9196
- cx: "5",
9197
- cy: "12",
9198
- r: "1.75"
9199
- }),
9200
- /* @__PURE__ */ jsxDEV$1("circle", {
9201
- cx: "12",
9202
- cy: "12",
9203
- r: "1.75"
9204
- }),
9205
- /* @__PURE__ */ jsxDEV$1("circle", {
9206
- cx: "19",
9207
- cy: "12",
9208
- r: "1.75"
9209
- })
9210
- ]
9402
+ children: /* @__PURE__ */ jsxDEV$1(Icon$1, {
9403
+ name: "post-menu-dots",
9404
+ size: 15
9211
9405
  })
9212
9406
  });
9213
9407
  };
@@ -9235,17 +9429,7 @@ var PostFooter = ({ post, detail, display }) => {
9235
9429
  "aria-label": featuredLabel,
9236
9430
  "data-tooltip": featuredLabel,
9237
9431
  "data-align": "center",
9238
- children: /* @__PURE__ */ jsxDEV$1("svg", {
9239
- xmlns: "http://www.w3.org/2000/svg",
9240
- viewBox: "0 0 24 24",
9241
- fill: "none",
9242
- stroke: "currentColor",
9243
- "stroke-width": "1.35",
9244
- "stroke-linecap": "round",
9245
- "stroke-linejoin": "round",
9246
- "aria-hidden": "true",
9247
- children: /* @__PURE__ */ jsxDEV$1("path", { d: FEATURED_SPARKLE_PATH })
9248
- })
9432
+ children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "featured-sparkle" })
9249
9433
  }),
9250
9434
  showTimestamp && /* @__PURE__ */ jsxDEV$1(PostPublishedLink, {
9251
9435
  post,
@@ -9257,16 +9441,7 @@ var PostFooter = ({ post, detail, display }) => {
9257
9441
  target: "_blank",
9258
9442
  rel: "noopener noreferrer",
9259
9443
  "aria-label": i18n._({ id: "9dr9Nh" }),
9260
- children: /* @__PURE__ */ jsxDEV$1("svg", {
9261
- xmlns: "http://www.w3.org/2000/svg",
9262
- viewBox: "0 0 24 24",
9263
- fill: "none",
9264
- stroke: "currentColor",
9265
- "stroke-width": "2",
9266
- "stroke-linecap": "round",
9267
- "stroke-linejoin": "round",
9268
- children: [/* @__PURE__ */ jsxDEV$1("path", { d: "M7 17 17 7" }), /* @__PURE__ */ jsxDEV$1("path", { d: "M9 7h8v8" })]
9269
- })
9444
+ children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-external-link" })
9270
9445
  }),
9271
9446
  /* @__PURE__ */ jsxDEV$1(CompactCollectionTags, {
9272
9447
  collections: post.collections,
@@ -9281,17 +9456,9 @@ var PostFooter = ({ post, detail, display }) => {
9281
9456
  class: "reply-trigger",
9282
9457
  "aria-label": i18n._({ id: "ImOQa9" }),
9283
9458
  "data-reply-trigger": true,
9284
- children: /* @__PURE__ */ jsxDEV$1("svg", {
9285
- xmlns: "http://www.w3.org/2000/svg",
9286
- width: "14",
9287
- height: "14",
9288
- viewBox: "0 0 24 24",
9289
- fill: "none",
9290
- stroke: "currentColor",
9291
- "stroke-width": "2",
9292
- "stroke-linecap": "round",
9293
- "stroke-linejoin": "round",
9294
- children: [/* @__PURE__ */ jsxDEV$1("polyline", { points: "9 17 4 12 9 7" }), /* @__PURE__ */ jsxDEV$1("path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" })]
9459
+ children: /* @__PURE__ */ jsxDEV$1(Icon$1, {
9460
+ name: "post-reply",
9461
+ size: 14
9295
9462
  })
9296
9463
  }), /* @__PURE__ */ jsxDEV$1(PostMenuTriggerButton, {})]
9297
9464
  })]
@@ -9313,57 +9480,15 @@ var PostFooter = ({ post, detail, display }) => {
9313
9480
  children: [
9314
9481
  /* @__PURE__ */ jsxDEV$1("span", {
9315
9482
  class: "post-status-badge post-status-pinned",
9316
- children: [/* @__PURE__ */ jsxDEV$1("svg", {
9317
- xmlns: "http://www.w3.org/2000/svg",
9318
- viewBox: "0 0 24 24",
9319
- fill: "none",
9320
- stroke: "currentColor",
9321
- "stroke-width": "1.75",
9322
- "stroke-linecap": "round",
9323
- "stroke-linejoin": "round",
9324
- children: [/* @__PURE__ */ jsxDEV$1("line", {
9325
- x1: "12",
9326
- x2: "12",
9327
- y1: "17",
9328
- y2: "22"
9329
- }), /* @__PURE__ */ jsxDEV$1("path", { d: "M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z" })]
9330
- }), "Pinned"]
9483
+ children: [/* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-status-pin" }), "Pinned"]
9331
9484
  }),
9332
9485
  /* @__PURE__ */ jsxDEV$1("span", {
9333
9486
  class: "post-status-badge post-status-pinned-in-collection",
9334
- children: [/* @__PURE__ */ jsxDEV$1("svg", {
9335
- xmlns: "http://www.w3.org/2000/svg",
9336
- viewBox: "0 0 24 24",
9337
- fill: "none",
9338
- stroke: "currentColor",
9339
- "stroke-width": "1.75",
9340
- "stroke-linecap": "round",
9341
- "stroke-linejoin": "round",
9342
- children: [/* @__PURE__ */ jsxDEV$1("line", {
9343
- x1: "12",
9344
- x2: "12",
9345
- y1: "17",
9346
- y2: "22"
9347
- }), /* @__PURE__ */ jsxDEV$1("path", { d: "M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z" })]
9348
- }), "Pinned"]
9487
+ children: [/* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-status-pin" }), "Pinned"]
9349
9488
  }),
9350
9489
  /* @__PURE__ */ jsxDEV$1("span", {
9351
9490
  class: "post-status-badge post-status-private",
9352
- children: [/* @__PURE__ */ jsxDEV$1("svg", {
9353
- xmlns: "http://www.w3.org/2000/svg",
9354
- viewBox: "0 0 24 24",
9355
- fill: "none",
9356
- stroke: "currentColor",
9357
- "stroke-width": "1.75",
9358
- "stroke-linecap": "round",
9359
- "stroke-linejoin": "round",
9360
- children: [
9361
- /* @__PURE__ */ jsxDEV$1("path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" }),
9362
- /* @__PURE__ */ jsxDEV$1("path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }),
9363
- /* @__PURE__ */ jsxDEV$1("path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" }),
9364
- /* @__PURE__ */ jsxDEV$1("path", { d: "m2 2 20 20" })
9365
- ]
9366
- }), "Private"]
9491
+ children: [/* @__PURE__ */ jsxDEV$1(Icon$1, { name: "post-status-private" }), "Private"]
9367
9492
  })
9368
9493
  ]
9369
9494
  });
@@ -9497,29 +9622,17 @@ var LinkPreview = ({ imageUrl, linkUrl, kind, provider }) => {
9497
9622
  isVideo && /* @__PURE__ */ jsxDEV$1("div", {
9498
9623
  class: "link-preview-play",
9499
9624
  "aria-hidden": "true",
9500
- children: /* @__PURE__ */ jsxDEV$1("svg", {
9501
- class: "link-preview-play-icon",
9502
- viewBox: "0 0 68 48",
9503
- xmlns: "http://www.w3.org/2000/svg",
9504
- children: [/* @__PURE__ */ jsxDEV$1("path", {
9505
- class: "link-preview-play-bg",
9506
- d: "M66.52 7.74c-.78-2.93-2.49-5.41-5.42-6.19C55.79.13 34 0 34 0S12.21.13 6.9 1.55C3.97 2.33 2.27 4.81 1.48 7.74.06 13.05 0 24 0 24s.06 10.95 1.48 16.26c.78 2.93 2.49 5.41 5.42 6.19C12.21 47.87 34 48 34 48s21.79-.13 27.1-1.55c2.93-.78 4.64-3.26 5.42-6.19C67.94 34.95 68 24 68 24s-.06-10.95-1.48-16.26z",
9507
- fill: "rgba(0,0,0,.65)"
9508
- }), /* @__PURE__ */ jsxDEV$1("path", {
9509
- d: "M45 24L27 14v20",
9510
- fill: "#fff"
9511
- })]
9625
+ children: /* @__PURE__ */ jsxDEV$1(Icon$1, {
9626
+ name: "link-preview-play",
9627
+ class: "link-preview-play-icon"
9512
9628
  })
9513
9629
  }),
9514
9630
  providerLabel && /* @__PURE__ */ jsxDEV$1("span", {
9515
9631
  class: "link-preview-badge",
9516
9632
  "aria-hidden": "true",
9517
- children: [isVideo && /* @__PURE__ */ jsxDEV$1("svg", {
9518
- class: "link-preview-badge-icon",
9519
- viewBox: "0 0 16 16",
9520
- xmlns: "http://www.w3.org/2000/svg",
9521
- fill: "currentColor",
9522
- children: /* @__PURE__ */ jsxDEV$1("path", { d: "M5.5 3.5v9l7-4.5z" })
9633
+ children: [isVideo && /* @__PURE__ */ jsxDEV$1(Icon$1, {
9634
+ name: "link-preview-badge-play",
9635
+ class: "link-preview-badge-icon"
9523
9636
  }), providerLabel]
9524
9637
  })
9525
9638
  ]
@@ -9546,25 +9659,15 @@ var LinkPreview = ({ imageUrl, linkUrl, kind, provider }) => {
9546
9659
  class: "feed-link-domain",
9547
9660
  target: "_blank",
9548
9661
  rel: "noopener noreferrer",
9549
- children: [/* @__PURE__ */ jsxDEV$1("svg", {
9550
- class: "feed-link-domain-icon",
9551
- xmlns: "http://www.w3.org/2000/svg",
9552
- fill: "none",
9553
- viewBox: "0 0 24 24",
9554
- "stroke-width": "2",
9555
- stroke: "currentColor",
9556
- children: /* @__PURE__ */ jsxDEV$1("path", { d: "M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" })
9662
+ children: [/* @__PURE__ */ jsxDEV$1(Icon$1, {
9663
+ name: "link-domain",
9664
+ class: "feed-link-domain-icon"
9557
9665
  }), /* @__PURE__ */ jsxDEV$1("span", { children: domain })]
9558
9666
  }) : /* @__PURE__ */ jsxDEV$1("div", {
9559
9667
  class: "feed-link-domain",
9560
- children: [/* @__PURE__ */ jsxDEV$1("svg", {
9561
- class: "feed-link-domain-icon",
9562
- xmlns: "http://www.w3.org/2000/svg",
9563
- fill: "none",
9564
- viewBox: "0 0 24 24",
9565
- "stroke-width": "2",
9566
- stroke: "currentColor",
9567
- children: /* @__PURE__ */ jsxDEV$1("path", { d: "M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" })
9668
+ children: [/* @__PURE__ */ jsxDEV$1(Icon$1, {
9669
+ name: "link-domain",
9670
+ class: "feed-link-domain-icon"
9568
9671
  }), /* @__PURE__ */ jsxDEV$1("span", { children: domain })]
9569
9672
  }));
9570
9673
  const previewEl = !isCompact && post.previewImageUrl && /* @__PURE__ */ jsxDEV$1(LinkPreview, {
@@ -9645,11 +9748,6 @@ var LinkPreview = ({ imageUrl, linkUrl, kind, provider }) => {
9645
9748
  });
9646
9749
  };
9647
9750
  //#endregion
9648
- //#region src/lib/decorative-quote-mark.ts
9649
- var DECORATIVE_QUOTE_MARK_VIEWBOX = "0 0 96 96";
9650
- var DECORATIVE_QUOTE_MARK_PATHS = ["M24.4 10.5C16.9 17.7 11.5 26.8 8.2 37.7C4.9 48.7 4.8 58.9 7.8 68.2C10.3 75.7 15.4 79.5 22.9 79.5C28 79.5 32.2 77.8 35.4 74.2C38.6 70.7 40.2 66.5 40.2 61.4C40.2 56.5 38.8 52.6 36 49.6C33.3 46.6 29.7 45.1 25.2 45.1C23.4 45.1 21.8 45.3 20.2 45.8C22.2 37.3 26.7 29.2 33.6 21.4L24.4 10.5Z", "M60.8 10.5C53.3 17.7 47.9 26.8 44.6 37.7C41.3 48.7 41.2 58.9 44.2 68.2C46.7 75.7 51.8 79.5 59.3 79.5C64.4 79.5 68.6 77.8 71.8 74.2C75 70.7 76.6 66.5 76.6 61.4C76.6 56.5 75.2 52.6 72.4 49.6C69.7 46.6 66.1 45.1 61.6 45.1C59.8 45.1 58.2 45.3 56.6 45.8C58.6 37.3 63.1 29.2 70 21.4L60.8 10.5Z"];
9651
- DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}" />`).join("");
9652
- //#endregion
9653
9751
  //#region src/ui/shared/DecorativeQuoteMark.tsx
9654
9752
  /**
9655
9753
  * Decorative double-quote mark rendered as SVG so the shape stays consistent
@@ -9658,15 +9756,7 @@ DECORATIVE_QUOTE_MARK_PATHS.map((path) => `<path fill="currentColor" d="${path}"
9658
9756
  class: `decorative-quote-mark${cls ? ` ${cls}` : ""}`,
9659
9757
  "data-direction": direction,
9660
9758
  "aria-hidden": "true",
9661
- children: /* @__PURE__ */ jsxDEV$1("svg", {
9662
- viewBox: DECORATIVE_QUOTE_MARK_VIEWBOX,
9663
- role: "presentation",
9664
- focusable: "false",
9665
- children: DECORATIVE_QUOTE_MARK_PATHS.map((path) => /* @__PURE__ */ jsxDEV$1("path", {
9666
- fill: "currentColor",
9667
- d: path
9668
- }))
9669
- })
9759
+ children: /* @__PURE__ */ jsxDEV$1(Icon$1, { name: "decorative-quote" })
9670
9760
  });
9671
9761
  //#endregion
9672
9762
  //#region src/ui/feed/QuoteCard.tsx
@@ -10060,7 +10150,7 @@ var PaginatedPageHeader = ({ title, currentPage = 1, totalPages, description, ic
10060
10150
  * Home Page
10061
10151
  *
10062
10152
  * Timeline feed with per-type card components and thread previews.
10063
- */ var HomePage = ({ items, baseUrl, currentPage, totalPages }) => {
10153
+ */ var HomePage = ({ items, baseUrl, currentPage, totalPages, isAuthenticated, signinUrl }) => {
10064
10154
  const { i18n } = useLingui();
10065
10155
  return /* @__PURE__ */ jsxDEV$1("div", {
10066
10156
  "data-page": "home",
@@ -10079,8 +10169,12 @@ var PaginatedPageHeader = ({ title, currentPage = 1, totalPages, description, ic
10079
10169
  class: "flex flex-col",
10080
10170
  children: /* @__PURE__ */ jsxDEV$1("p", {
10081
10171
  id: "empty-timeline",
10082
- class: "py-12 text-center text-muted-foreground",
10083
- children: i18n._({ id: "WpXcBJ" })
10172
+ class: "py-8 text-muted-foreground",
10173
+ children: [i18n._({ id: "Q/uoSA" }), !isAuthenticated && /* @__PURE__ */ jsxDEV$1(Fragment$1, { children: [" ", /* @__PURE__ */ jsxDEV$1("a", {
10174
+ href: signinUrl,
10175
+ class: "underline-offset-2 hover:underline",
10176
+ children: i18n._({ id: "nfU386" })
10177
+ })] })]
10084
10178
  })
10085
10179
  })
10086
10180
  })
@@ -10169,7 +10263,9 @@ homeRoutes.get("/", async (c) => {
10169
10263
  items,
10170
10264
  baseUrl: toPublicPath("/", navData.sitePathPrefix),
10171
10265
  currentPage,
10172
- totalPages
10266
+ totalPages,
10267
+ isAuthenticated,
10268
+ signinUrl: `${toPublicPath("/signin", navData.sitePathPrefix)}?redirect=${encodeURIComponent(toPublicPath("/", navData.sitePathPrefix))}`
10173
10269
  })
10174
10270
  });
10175
10271
  });
@@ -10370,7 +10466,6 @@ function buildPostMeta(post, siteName) {
10370
10466
  siteDomains: () => siteDomains$1,
10371
10467
  siteMembers: () => siteMembers$1,
10372
10468
  sites: () => sites$1,
10373
- syncJobs: () => syncJobs$1,
10374
10469
  uploadSessions: () => uploadSessions$1,
10375
10470
  user: () => user$1,
10376
10471
  verification: () => verification$1
@@ -10427,9 +10522,14 @@ var sites$1 = sqliteTable("site", {
10427
10522
  id: text("id").primaryKey(),
10428
10523
  key: text("key").notNull(),
10429
10524
  status: text("status", { enum: SITE_STATUSES$1 }).notNull().default("active"),
10525
+ provisioningIdempotencyKey: text("provisioning_idempotency_key"),
10430
10526
  createdAt: integer("created_at").notNull(),
10431
10527
  updatedAt: integer("updated_at").notNull()
10432
- }, (table) => [uniqueIndex("uq_site_key").on(table.key), check("chk_site_status", sql`${table.status} IN (${sqlTextEnum$1(SITE_STATUSES$1)})`)]);
10528
+ }, (table) => [
10529
+ uniqueIndex("uq_site_key").on(table.key),
10530
+ uniqueIndex("uq_site_provisioning_idempotency_key").on(table.provisioningIdempotencyKey).where(sql`${table.provisioningIdempotencyKey} IS NOT NULL`),
10531
+ check("chk_site_status", sql`${table.status} IN (${sqlTextEnum$1(SITE_STATUSES$1)})`)
10532
+ ]);
10433
10533
  var siteDomains$1 = sqliteTable("site_domain", {
10434
10534
  id: text("id").primaryKey(),
10435
10535
  siteId: text("site_id").notNull().references(() => sites$1.id, { onDelete: "cascade" }),
@@ -10768,28 +10868,6 @@ var account$1 = sqliteTable("account", {
10768
10868
  createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
10769
10869
  updatedAt: integer("updated_at", { mode: "timestamp" }).notNull()
10770
10870
  });
10771
- var SYNC_JOB_STATUSES = [
10772
- "pending",
10773
- "processing",
10774
- "completed",
10775
- "failed"
10776
- ];
10777
- var syncJobs$1 = sqliteTable("sync_job", {
10778
- id: text("id").primaryKey(),
10779
- siteId: text("site_id").notNull().references(() => sites$1.id, { onDelete: "cascade" }),
10780
- kind: text("kind").notNull(),
10781
- payload: text("payload").notNull(),
10782
- status: text("status", { enum: SYNC_JOB_STATUSES }).notNull().default("pending"),
10783
- attempts: integer("attempts").notNull().default(0),
10784
- maxAttempts: integer("max_attempts").notNull().default(3),
10785
- createdAt: integer("created_at").notNull(),
10786
- updatedAt: integer("updated_at").notNull(),
10787
- lockedUntil: integer("locked_until")
10788
- }, (table) => [
10789
- index("idx_sync_job_status_created").on(table.status, table.createdAt),
10790
- index("idx_sync_job_site_id").on(table.siteId),
10791
- check("chk_sync_job_status", sql`${table.status} IN (${sqlTextEnum$1(SYNC_JOB_STATUSES)})`)
10792
- ]);
10793
10871
  var verification$1 = sqliteTable("verification", {
10794
10872
  id: text("id").primaryKey(),
10795
10873
  identifier: text("identifier").notNull(),
@@ -10935,7 +11013,6 @@ function isNodeSqliteDatabase(db) {
10935
11013
  siteDomains: () => siteDomains,
10936
11014
  siteMembers: () => siteMembers,
10937
11015
  sites: () => sites,
10938
- syncJobs: () => syncJobs,
10939
11016
  uploadSessions: () => uploadSessions,
10940
11017
  user: () => user,
10941
11018
  verification: () => verification
@@ -10995,9 +11072,14 @@ var sites = pgTable("site", {
10995
11072
  id: text$1("id").primaryKey(),
10996
11073
  key: text$1("key").notNull(),
10997
11074
  status: text$1("status", { enum: SITE_STATUSES }).notNull().default("active"),
11075
+ provisioningIdempotencyKey: text$1("provisioning_idempotency_key"),
10998
11076
  createdAt: integer$1("created_at").notNull(),
10999
11077
  updatedAt: integer$1("updated_at").notNull()
11000
- }, (table) => [uniqueIndex$1("uq_site_key").on(table.key), check$1("chk_site_status", sql`${table.status} IN (${sqlTextEnum(SITE_STATUSES)})`)]);
11078
+ }, (table) => [
11079
+ uniqueIndex$1("uq_site_key").on(table.key),
11080
+ uniqueIndex$1("uq_site_provisioning_idempotency_key").on(table.provisioningIdempotencyKey).where(sql`${table.provisioningIdempotencyKey} IS NOT NULL`),
11081
+ check$1("chk_site_status", sql`${table.status} IN (${sqlTextEnum(SITE_STATUSES)})`)
11082
+ ]);
11001
11083
  var siteDomains = pgTable("site_domain", {
11002
11084
  id: text$1("id").primaryKey(),
11003
11085
  siteId: text$1("site_id").notNull().references(() => sites.id, { onDelete: "cascade" }),
@@ -11370,23 +11452,6 @@ var account = pgTable("account", {
11370
11452
  mode: "date"
11371
11453
  }).notNull()
11372
11454
  });
11373
- var syncJobs = pgTable("sync_job", {
11374
- id: text$1("id").primaryKey(),
11375
- siteId: text$1("site_id").notNull().references(() => sites.id, { onDelete: "cascade" }),
11376
- kind: text$1("kind").notNull(),
11377
- payload: text$1("payload").notNull(),
11378
- status: text$1("status", { enum: [
11379
- "pending",
11380
- "processing",
11381
- "completed",
11382
- "failed"
11383
- ] }).notNull().default("pending"),
11384
- attempts: integer$1("attempts").notNull().default(0),
11385
- maxAttempts: integer$1("max_attempts").notNull().default(3),
11386
- createdAt: integer$1("created_at").notNull(),
11387
- updatedAt: integer$1("updated_at").notNull(),
11388
- lockedUntil: integer$1("locked_until")
11389
- }, (table) => [index$1("idx_sync_job_status_created").on(table.status, table.createdAt), index$1("idx_sync_job_site_id").on(table.siteId)]);
11390
11455
  var verification = pgTable("verification", {
11391
11456
  id: text$1("id").primaryKey(),
11392
11457
  identifier: text$1("identifier").notNull(),
@@ -11848,36 +11913,6 @@ function createMediaService(db, siteId, databaseSchema = sqliteSchemaBundle, dat
11848
11913
  };
11849
11914
  }
11850
11915
  //#endregion
11851
- //#region src/lib/icons.ts
11852
- /**
11853
- * Shared icon utilities.
11854
- *
11855
- * Provides a small wrapper around lucide-static so server-rendered UI can fetch
11856
- * SVG markup by kebab-case icon name.
11857
- */
11858
- /**
11859
- * Convert a kebab-case icon name to PascalCase for lucide-static lookup.
11860
- *
11861
- * @param name - Kebab-case icon name such as "book-open"
11862
- * @returns PascalCase name such as "BookOpen"
11863
- */ function toPascalCase(name) {
11864
- return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
11865
- }
11866
- /**
11867
- * Get SVG markup for a Lucide icon by kebab-case name.
11868
- *
11869
- * @param name - Kebab-case icon name
11870
- * @returns SVG string or null when the icon is unknown
11871
- *
11872
- * @example
11873
- * ```ts
11874
- * getIconSvg("book-open");
11875
- * ```
11876
- */ function getIconSvg(name) {
11877
- const svg = lucideIcons[toPascalCase(name)];
11878
- return typeof svg === "string" ? svg : null;
11879
- }
11880
- //#endregion
11881
11916
  //#region src/ui/pages/ArchivePage.tsx
11882
11917
  /**
11883
11918
  * Archive Page
@@ -14328,7 +14363,9 @@ latestRoutes.get("/", async (c) => {
14328
14363
  items,
14329
14364
  baseUrl: toPublicPath("/latest", navData.sitePathPrefix),
14330
14365
  currentPage,
14331
- totalPages
14366
+ totalPages,
14367
+ isAuthenticated: navData.isAuthenticated,
14368
+ signinUrl: `${toPublicPath("/signin", navData.sitePathPrefix)}?redirect=${encodeURIComponent(toPublicPath("/latest", navData.sitePathPrefix))}`
14332
14369
  })
14333
14370
  });
14334
14371
  });
@@ -15724,22 +15761,6 @@ var CJK_SERIF_FALLBACK_VAR = "var(--font-cjk-serif-fallback)";
15724
15761
  }
15725
15762
  }
15726
15763
  var BUILTIN_FONT_THEMES = [
15727
- {
15728
- id: "tufte",
15729
- name: {
15730
- id: "Tufte",
15731
- message: "Tufte",
15732
- comment: "@context: Font theme name"
15733
- },
15734
- headingFontFamily: TUFTE_SERIF,
15735
- bodyFontFamily: TUFTE_SERIF,
15736
- cssVariables: {},
15737
- description: {
15738
- id: "Palatino-based old-style serif matching Tufte CSS proportions",
15739
- message: "Palatino-based old-style serif matching Tufte CSS proportions",
15740
- comment: "@context: Font theme description"
15741
- }
15742
- },
15743
15764
  {
15744
15765
  id: "classic",
15745
15766
  name: {
@@ -15756,6 +15777,22 @@ var BUILTIN_FONT_THEMES = [
15756
15777
  comment: "@context: Font theme description"
15757
15778
  }
15758
15779
  },
15780
+ {
15781
+ id: "tufte",
15782
+ name: {
15783
+ id: "Tufte",
15784
+ message: "Tufte",
15785
+ comment: "@context: Font theme name"
15786
+ },
15787
+ headingFontFamily: TUFTE_SERIF,
15788
+ bodyFontFamily: TUFTE_SERIF,
15789
+ cssVariables: {},
15790
+ description: {
15791
+ id: "Palatino-based old-style serif matching Tufte CSS proportions",
15792
+ message: "Palatino-based old-style serif matching Tufte CSS proportions",
15793
+ comment: "@context: Font theme description"
15794
+ }
15795
+ },
15759
15796
  {
15760
15797
  id: "system-sans",
15761
15798
  name: {
@@ -17775,13 +17812,19 @@ function SettingsRootContent({ sitePathPrefix = "", demoMode = false }) {
17775
17812
  }),
17776
17813
  /* @__PURE__ */ jsxDEV$1(SettingsDirectorySection, {
17777
17814
  title: i18n._({ id: "ebQKK7" }),
17778
- children: /* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
17815
+ children: [/* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
17779
17816
  href: toPublicPath("/settings/general", sitePathPrefix),
17780
17817
  icon: ICONS$1.settings,
17781
17818
  tone: "subtle",
17782
17819
  name: i18n._({ id: "Weq9zb" }),
17783
17820
  description: i18n._({ id: "GMMWcy" })
17784
- })
17821
+ }), /* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
17822
+ href: toPublicPath("/settings/custom-urls", sitePathPrefix),
17823
+ icon: ICONS$1.arrowRightLeft,
17824
+ tone: "subtle",
17825
+ name: i18n._({ id: "ke1gWS" }),
17826
+ description: i18n._({ id: "9T7Cwm" })
17827
+ })]
17785
17828
  }),
17786
17829
  /* @__PURE__ */ jsxDEV$1(SettingsDirectorySection, {
17787
17830
  title: i18n._({ id: "aAIQg2" }),
@@ -17832,29 +17875,19 @@ function SettingsRootContent({ sitePathPrefix = "", demoMode = false }) {
17832
17875
  }),
17833
17876
  /* @__PURE__ */ jsxDEV$1(SettingsDirectorySection, {
17834
17877
  title: i18n._({ id: "sxkWRg" }),
17835
- children: [
17836
- /* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
17837
- href: toPublicPath("/settings/custom-urls", sitePathPrefix),
17838
- icon: ICONS$1.arrowRightLeft,
17839
- tone: "subtle",
17840
- name: i18n._({ id: "ke1gWS" }),
17841
- description: i18n._({ id: "9T7Cwm" })
17842
- }),
17843
- /* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
17844
- href: toPublicPath("/settings/code-injection", sitePathPrefix),
17845
- icon: ICONS$1.terminal,
17846
- tone: "subtle",
17847
- name: i18n._({ id: "fWYqkz" }),
17848
- description: i18n._({ id: "UFK415" })
17849
- }),
17850
- /* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
17851
- href: toPublicPath("/settings/api-tokens", sitePathPrefix),
17852
- icon: ICONS$1.key,
17853
- tone: "subtle",
17854
- name: i18n._({ id: "ZiooJI" }),
17855
- description: i18n._({ id: "egK+Yy" })
17856
- })
17857
- ]
17878
+ children: [/* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
17879
+ href: toPublicPath("/settings/code-injection", sitePathPrefix),
17880
+ icon: ICONS$1.terminal,
17881
+ tone: "subtle",
17882
+ name: i18n._({ id: "fWYqkz" }),
17883
+ description: i18n._({ id: "UFK415" })
17884
+ }), /* @__PURE__ */ jsxDEV$1(SettingsDirectoryLink, {
17885
+ href: toPublicPath("/settings/api-tokens", sitePathPrefix),
17886
+ icon: ICONS$1.key,
17887
+ tone: "subtle",
17888
+ name: i18n._({ id: "ZiooJI" }),
17889
+ description: i18n._({ id: "egK+Yy" })
17890
+ })]
17858
17891
  }),
17859
17892
  /* @__PURE__ */ jsxDEV$1(SettingsDirectorySection, {
17860
17893
  title: i18n._({ id: "AeXO77" }),
@@ -19948,31 +19981,6 @@ async function syncHostedControlPlaneSiteAvatar(input) {
19948
19981
  });
19949
19982
  }
19950
19983
  //#endregion
19951
- //#region src/lib/job-queue.ts
19952
- /**
19953
- * Job Queue abstraction.
19954
- *
19955
- * Provides a unified interface for enqueueing background jobs across
19956
- * Cloudflare Workers (CF Queues) and Node/Postgres (DB-backed polling).
19957
- */
19958
- /**
19959
- * A no-op queue that silently drops jobs.
19960
- * Used as a fallback when neither CF Queue nor DB queue is configured.
19961
- */ var noopQueue = { async enqueue() {} };
19962
- //#endregion
19963
- //#region src/lib/job-queue-cf.ts
19964
- /**
19965
- * Cloudflare Queue adapter for the job queue.
19966
- */ /**
19967
- * Create a job queue backed by a Cloudflare Queue binding.
19968
- *
19969
- * @param queue - The CF Queue binding (e.g. `env.GITHUB_SYNC_QUEUE`)
19970
- */ function createCfJobQueue(queue) {
19971
- return { async enqueue(payload) {
19972
- await queue.send(payload);
19973
- } };
19974
- }
19975
- //#endregion
19976
19984
  //#region src/lib/github-sync-site-config.ts
19977
19985
  /**
19978
19986
  * Build the SiteConfig the GitHub Sync service needs from a request
@@ -20020,33 +20028,6 @@ async function syncHostedControlPlaneSiteAvatar(input) {
20020
20028
  rssFeedLimit: appConfig.rssFeedLimit
20021
20029
  };
20022
20030
  }
20023
- //#endregion
20024
- //#region src/lib/github-sync-trigger.ts
20025
- /**
20026
- * GitHub Sync Trigger
20027
- *
20028
- * Two dispatch paths:
20029
- *
20030
- * - `triggerGitHubSync` (queue-based): the original design, kept for
20031
- * future use when a Cloudflare Queue binding is actually wired up.
20032
- * Falls back to a no-op queue today, which silently drops jobs.
20033
- * - `triggerGitHubSyncInline` (inline): runs pushFullSync in the
20034
- * current worker invocation via `c.executionCtx.waitUntil`. Works
20035
- * uniformly on Workers and Node, no queue binding required. This
20036
- * is what every caller uses today.
20037
- *
20038
- * Both paths debounce through a PENDING flag. When a new trigger
20039
- * arrives while a sync is running, the inline runner records it via
20040
- * DIRTY; the running sync re-runs once more after completion so the
20041
- * new edits land.
20042
- */
20043
- /**
20044
- * Resolve the appropriate job queue from the environment.
20045
- * Returns the CF Queue adapter if available, otherwise noop.
20046
- */ function resolveJobQueue(env) {
20047
- if (env.GITHUB_SYNC_QUEUE) return createCfJobQueue(env.GITHUB_SYNC_QUEUE);
20048
- return noopQueue;
20049
- }
20050
20031
  /**
20051
20032
  * Returns the effective "sync in progress" state.
20052
20033
  *
@@ -20118,7 +20099,7 @@ async function syncHostedControlPlaneSiteAvatar(input) {
20118
20099
  return;
20119
20100
  }
20120
20101
  await markSyncPending(settings);
20121
- const { createGitHubSyncService } = await import("./github-sync-7y_nTXx1.js").then((n) => n.r);
20102
+ const { createGitHubSyncService } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
20122
20103
  const { getGitHubAppConfig } = await import("./env-CgaH9Mut.js").then((n) => n.t);
20123
20104
  const run = runBackgroundSync(settings, createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
20124
20105
  storage: c.var.storage,
@@ -20905,7 +20886,7 @@ settingsRoutes.post("/github-sync/connect", async (c) => {
20905
20886
  await c.var.services.settings.set("GITHUB_SYNC_AUTH_MODE", "pat");
20906
20887
  await c.var.services.settings.set("GITHUB_SYNC_APP_INSTALLATION_ID", "");
20907
20888
  await c.var.services.settings.set("GITHUB_SYNC_ENABLED", "true");
20908
- const { createGitHubSyncService } = await import("./github-sync-7y_nTXx1.js").then((n) => n.r);
20889
+ const { createGitHubSyncService } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
20909
20890
  const syncService = createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
20910
20891
  storage: c.var.storage,
20911
20892
  githubApp: getGitHubAppConfig(c.env)
@@ -20924,7 +20905,7 @@ settingsRoutes.post("/github-sync/connect", async (c) => {
20924
20905
  return dsRedirect(publicPath(c, "/settings/github-sync"));
20925
20906
  });
20926
20907
  settingsRoutes.post("/github-sync/push", async (c) => {
20927
- const { createGitHubSyncService } = await import("./github-sync-7y_nTXx1.js").then((n) => n.r);
20908
+ const { createGitHubSyncService } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
20928
20909
  const syncService = createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
20929
20910
  storage: c.var.storage,
20930
20911
  githubApp: getGitHubAppConfig(c.env)
@@ -20944,7 +20925,7 @@ settingsRoutes.post("/github-sync/push", async (c) => {
20944
20925
  });
20945
20926
  });
20946
20927
  settingsRoutes.post("/github-sync/disconnect", async (c) => {
20947
- const { createGitHubSyncService } = await import("./github-sync-7y_nTXx1.js").then((n) => n.r);
20928
+ const { createGitHubSyncService } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
20948
20929
  await createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), { githubApp: getGitHubAppConfig(c.env) }).teardownWebhook();
20949
20930
  return dsRedirect(publicPath(c, "/settings/github-sync"));
20950
20931
  });
@@ -21135,7 +21116,7 @@ function buildRepoPickerLabels(c) {
21135
21116
  const { parseRepoSlug, createGitHubClient } = await import("./github-api-BkRWnqMx.js").then((n) => n.n);
21136
21117
  const parsed = parseRepoSlug(repo);
21137
21118
  if (!parsed) return wantsJson ? c.json({ error: "Invalid repository format." }, 400) : c.text("Invalid repository format.", 400);
21138
- const { classifyRepoForSync } = await import("./github-sync-7y_nTXx1.js").then((n) => n.r);
21119
+ const { classifyRepoForSync } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
21139
21120
  const ghClient = createGitHubClient(() => getInstallationTokenFromApp(app, installationId));
21140
21121
  let classification;
21141
21122
  try {
@@ -21165,7 +21146,7 @@ function buildRepoPickerLabels(c) {
21165
21146
  await c.var.services.settings.set("GITHUB_SYNC_REPO", repo);
21166
21147
  await c.var.services.settings.set("GITHUB_SYNC_TOKEN", "");
21167
21148
  await c.var.services.settings.set("GITHUB_SYNC_ENABLED", "true");
21168
- const { createGitHubSyncService } = await import("./github-sync-7y_nTXx1.js").then((n) => n.r);
21149
+ const { createGitHubSyncService } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
21169
21150
  const syncService = createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
21170
21151
  storage: c.var.storage,
21171
21152
  githubApp: app
@@ -21281,7 +21262,7 @@ function requireGitHubApp(c) {
21281
21262
  const { parseRepoSlug, createGitHubClient } = await import("./github-api-BkRWnqMx.js").then((n) => n.n);
21282
21263
  const parsed = parseRepoSlug(repo);
21283
21264
  if (!parsed) return c.json({ error: "Invalid repository format." }, 400);
21284
- const { classifyRepoForSync } = await import("./github-sync-7y_nTXx1.js").then((n) => n.r);
21265
+ const { classifyRepoForSync } = await import("./github-sync-CQ1x271f.js").then((n) => n.r);
21285
21266
  const client = createGitHubClient(() => getInstallationTokenFromApp(app, installationId));
21286
21267
  try {
21287
21268
  const classification = await classifyRepoForSync(client, parsed.owner, parsed.repo, c.var.currentSite.id);
@@ -24639,7 +24620,8 @@ var CreateManagedSiteSchema = z.object({
24639
24620
  primaryHost: z.string().trim().toLowerCase().min(1).max(255).regex(/^(?=.{1,255}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/, "Primary host must be a valid hostname."),
24640
24621
  siteName: z.string().trim().min(1).max(120),
24641
24622
  siteLanguage: z.string().trim().max(35).optional(),
24642
- timeZone: z.string().trim().max(100).optional()
24623
+ timeZone: z.string().trim().max(100).optional(),
24624
+ idempotencyKey: z.string().trim().min(1).max(128).optional()
24643
24625
  });
24644
24626
  var ManagedSiteDomainSchema = z.object({
24645
24627
  host: z.string().trim().toLowerCase().min(1).max(255).regex(/^(?=.{1,255}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/, "Domain host must be a valid hostname."),
@@ -24739,6 +24721,38 @@ internalSitesRoutes.delete("/:siteId/domains/:domainId", requireInternalAdminApi
24739
24721
  })) });
24740
24722
  });
24741
24723
  //#endregion
24724
+ //#region src/middleware/config.ts
24725
+ /**
24726
+ * Config Middleware
24727
+ *
24728
+ * Loads settings from DB, resolves app config and theme.
24729
+ * Apply only to route groups that need config/theme data —
24730
+ * skip for /healthz, /media/*, /favicon.ico, /api/auth/*, etc.
24731
+ */
24732
+ /**
24733
+ * Middleware that loads settings, resolves app config, and builds theme CSS.
24734
+ *
24735
+ * Sets `allSettings`, `appConfig`, and `themeStyle` on the Hono context.
24736
+ */ function withConfig() {
24737
+ return async (c, next) => {
24738
+ const allSettings = await c.var.services.settings.getAll();
24739
+ c.set("allSettings", allSettings);
24740
+ const publicRequestOrigin = new URL(c.var.publicRequestUrl).origin;
24741
+ const siteUrlOverride = getSiteResolutionMode(c.env) === "host-based" ? `${publicRequestOrigin}${c.var.currentSiteDomain?.pathPrefix ?? ""}` : getConfiguredSingleSiteUrl(c.env) || `${publicRequestOrigin}${getConfiguredSingleSitePathPrefix(c.env)}`;
24742
+ const appConfig = resolveConfig(c.env, allSettings, { siteUrl: siteUrlOverride });
24743
+ c.set("appConfig", appConfig);
24744
+ const activeTheme = resolveBuiltinTheme(appConfig.themeId);
24745
+ const fontTheme = BUILTIN_FONT_THEMES.find((f) => f.id === appConfig.fontThemeId);
24746
+ const fontOverrides = {
24747
+ ...getCjkSerifCssVariables(appConfig.cjkSerifFont),
24748
+ ...fontTheme ? getFontThemeCssVariables(fontTheme) : {}
24749
+ };
24750
+ const themeStyle = buildThemeStyle(activeTheme, appConfig.themeMode, fontOverrides);
24751
+ c.set("themeStyle", themeStyle);
24752
+ await next();
24753
+ };
24754
+ }
24755
+ //#endregion
24742
24756
  //#region src/lib/webhook-signature.ts
24743
24757
  /**
24744
24758
  * GitHub webhook HMAC-SHA256 signature verification.
@@ -24794,7 +24808,7 @@ function timingSafeEqual(a, b) {
24794
24808
  * Webhook receiver (HMAC-verified, no session auth) and admin endpoints
24795
24809
  * (session/token auth) for managing GitHub Sync configuration.
24796
24810
  */ var githubSyncWebhookRoutes = new Hono();
24797
- githubSyncWebhookRoutes.post("/webhook", async (c) => {
24811
+ githubSyncWebhookRoutes.post("/webhook", withConfig(), async (c) => {
24798
24812
  const secret = getGitHubAppConfig(c.env)?.webhookSecret ?? await c.var.services.settings.get("GITHUB_SYNC_WEBHOOK_SECRET");
24799
24813
  if (!secret) return c.json({ error: "GitHub Sync not configured" }, 404);
24800
24814
  const signature = c.req.header("X-Hub-Signature-256") ?? "";
@@ -24809,16 +24823,23 @@ githubSyncWebhookRoutes.post("/webhook", async (c) => {
24809
24823
  ok: true,
24810
24824
  skipped: "jant-sync commits"
24811
24825
  });
24812
- await resolveJobQueue(c.env).enqueue({
24813
- kind: "github-sync-pull",
24814
- siteId: c.var.currentSite.id,
24815
- data: {
24816
- ref: payload.ref,
24817
- before: payload.before,
24818
- after: payload.after,
24819
- commits: payload.commits
24820
- }
24826
+ const syncService = createGitHubSyncService(c.var.services, c.var.currentSite.id, await buildSyncSiteConfig(c), {
24827
+ storage: c.var.storage,
24828
+ githubApp: getGitHubAppConfig(c.env)
24821
24829
  });
24830
+ const settings = c.var.services.settings;
24831
+ const run = (async () => {
24832
+ try {
24833
+ await syncService.handleWebhookPush(payload);
24834
+ await settings.set("GITHUB_SYNC_LAST_ERROR", "");
24835
+ } catch (err) {
24836
+ const message = err instanceof Error ? err.message : String(err);
24837
+ await settings.set("GITHUB_SYNC_LAST_ERROR", message);
24838
+ }
24839
+ })();
24840
+ try {
24841
+ c.executionCtx?.waitUntil(run);
24842
+ } catch {}
24822
24843
  return c.json({
24823
24844
  ok: true,
24824
24845
  queued: true
@@ -25778,6 +25799,20 @@ manifestRoutes.get("/manifest.webmanifest", (c) => {
25778
25799
  //#endregion
25779
25800
  //#region src/lib/startup-config.ts
25780
25801
  var HOSTED_SHARED_SECRET_MIN_LENGTH = 32;
25802
+ var AUTH_SECRET_GENERATION_HINT = "Generate one with `openssl rand -base64 32`.";
25803
+ var AUTH_SECRET_PLACEHOLDER_MARKER = "replace-me";
25804
+ function getAuthSecretIssueKind(env) {
25805
+ const secret = getAuthSecret(env);
25806
+ if (!secret) return "missing";
25807
+ if (secret.toLowerCase().includes(AUTH_SECRET_PLACEHOLDER_MARKER)) return "placeholder";
25808
+ if (secret.length < 32) return "too-short";
25809
+ return null;
25810
+ }
25811
+ function getAuthSecretReadinessError(kind) {
25812
+ if (kind === "placeholder") return `AUTH_SECRET still uses the placeholder value from .env.example. ${AUTH_SECRET_GENERATION_HINT}`;
25813
+ if (kind === "too-short") return `AUTH_SECRET must be at least 32 characters before Jant can accept traffic. ${AUTH_SECRET_GENERATION_HINT}`;
25814
+ return "AUTH_SECRET must be set before Jant can accept traffic.";
25815
+ }
25781
25816
  function renderConfigurationErrorPage(input) {
25782
25817
  return `<!DOCTYPE html>
25783
25818
  <html lang="en">
@@ -25796,11 +25831,20 @@ ${input.bodyHtml}
25796
25831
  </body>
25797
25832
  </html>`;
25798
25833
  }
25799
- function getAuthSecretErrorHtml() {
25834
+ function getAuthSecretErrorHtml(kind) {
25835
+ const runtimeInstructions = `<p>Set <code>AUTH_SECRET=...</code> in the environment used to start Jant. Generate one with <code>openssl rand -base64 32</code>.</p>
25836
+ <p><strong>Cloudflare Workers:</strong> add <code>AUTH_SECRET</code> as a Worker secret in the dashboard under Variables and Secrets, or run <code>wrangler secret put AUTH_SECRET</code>.</p>`;
25800
25837
  return renderConfigurationErrorPage({
25801
- title: "AUTH_SECRET is not set",
25802
- bodyHtml: `<p>Jant needs a 32+ character auth secret to sign sessions.</p><p>Set <code>AUTH_SECRET=...</code> in the environment used to start Jant.</p>
25803
- <p><strong>Cloudflare Workers:</strong> add <code>AUTH_SECRET</code> as a Worker secret in the dashboard under Variables and Secrets, or run <code>wrangler secret put AUTH_SECRET</code>.</p>`,
25838
+ title: {
25839
+ missing: "AUTH_SECRET is not set",
25840
+ placeholder: "AUTH_SECRET is still the placeholder from .env.example",
25841
+ "too-short": `AUTH_SECRET is too short (must be at least 32 characters)`
25842
+ }[kind],
25843
+ bodyHtml: `${{
25844
+ missing: `<p>Jant needs a 32+ character auth secret to sign sessions.</p>`,
25845
+ placeholder: `<p>The current <code>AUTH_SECRET</code> still contains the <code>replace-me</code> placeholder from <code>.env.example</code>. This value is publicly known and unsafe to use; replace it with a real secret before serving traffic.</p>`,
25846
+ "too-short": `<p>Jant needs an auth secret of at least 32 characters to sign sessions. The current value is too short.</p>`
25847
+ }[kind]}${runtimeInstructions}`,
25804
25848
  docsHref: "https://github.com/jant-me/jant/blob/main/docs/configuration.md#required"
25805
25849
  });
25806
25850
  }
@@ -25887,7 +25931,8 @@ function getRuntimeConfigurationErrorPage(message) {
25887
25931
  * getStartupConfigurationErrorPage({ AUTH_SECRET: "secret" }) // null
25888
25932
  * ```
25889
25933
  */ function getStartupConfigurationErrorPage(env) {
25890
- if (!getAuthSecret(env)) return getAuthSecretErrorHtml();
25934
+ const authSecretIssue = getAuthSecretIssueKind(env);
25935
+ if (authSecretIssue) return getAuthSecretErrorHtml(authSecretIssue);
25891
25936
  const hostBasedIssues = collectHostBasedStartupConfigurationIssues(env);
25892
25937
  if (hostBasedIssues.length > 0) return getHostBasedConfigurationErrorHtml(hostBasedIssues);
25893
25938
  return null;
@@ -25938,38 +25983,6 @@ function getRuntimeConfigurationErrorPage(message) {
25938
25983
  throw err;
25939
25984
  };
25940
25985
  //#endregion
25941
- //#region src/middleware/config.ts
25942
- /**
25943
- * Config Middleware
25944
- *
25945
- * Loads settings from DB, resolves app config and theme.
25946
- * Apply only to route groups that need config/theme data —
25947
- * skip for /healthz, /media/*, /favicon.ico, /api/auth/*, etc.
25948
- */
25949
- /**
25950
- * Middleware that loads settings, resolves app config, and builds theme CSS.
25951
- *
25952
- * Sets `allSettings`, `appConfig`, and `themeStyle` on the Hono context.
25953
- */ function withConfig() {
25954
- return async (c, next) => {
25955
- const allSettings = await c.var.services.settings.getAll();
25956
- c.set("allSettings", allSettings);
25957
- const publicRequestOrigin = new URL(c.var.publicRequestUrl).origin;
25958
- const siteUrlOverride = getSiteResolutionMode(c.env) === "host-based" ? `${publicRequestOrigin}${c.var.currentSiteDomain?.pathPrefix ?? ""}` : getConfiguredSingleSiteUrl(c.env) || `${publicRequestOrigin}${getConfiguredSingleSitePathPrefix(c.env)}`;
25959
- const appConfig = resolveConfig(c.env, allSettings, { siteUrl: siteUrlOverride });
25960
- c.set("appConfig", appConfig);
25961
- const activeTheme = resolveBuiltinTheme(appConfig.themeId);
25962
- const fontTheme = BUILTIN_FONT_THEMES.find((f) => f.id === appConfig.fontThemeId);
25963
- const fontOverrides = {
25964
- ...getCjkSerifCssVariables(appConfig.cjkSerifFont),
25965
- ...fontTheme ? getFontThemeCssVariables(fontTheme) : {}
25966
- };
25967
- const themeStyle = buildThemeStyle(activeTheme, appConfig.themeMode, fontOverrides);
25968
- c.set("themeStyle", themeStyle);
25969
- await next();
25970
- };
25971
- }
25972
- //#endregion
25973
25986
  //#region ../../node_modules/.pnpm/hono@4.11.9/node_modules/hono/dist/middleware/secure-headers/secure-headers.js
25974
25987
  var HEADERS_MAP = {
25975
25988
  crossOriginEmbedderPolicy: ["Cross-Origin-Embedder-Policy", "require-corp"],
@@ -30366,10 +30379,28 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
30366
30379
  const pathPrefix = domain.pathPrefix ?? "";
30367
30380
  return `${protocol}//${domain.host}${pathPrefix}`;
30368
30381
  }
30382
+ async function loadByIdempotencyKey(targetDb, idempotencyKey) {
30383
+ const siteRow = (await targetDb.select().from(sites).where(eq(sites.provisioningIdempotencyKey, idempotencyKey)).limit(1))[0];
30384
+ if (!siteRow) return null;
30385
+ const domainRow = (await targetDb.select().from(siteDomains).where(and(eq(siteDomains.siteId, siteRow.id), eq(siteDomains.kind, "primary"))).limit(1))[0];
30386
+ if (!domainRow) return null;
30387
+ return {
30388
+ site: toSite(siteRow),
30389
+ domain: toSiteDomain(domainRow)
30390
+ };
30391
+ }
30369
30392
  async function createWithDatabase(targetDb, input) {
30370
30393
  const siteKey = input.key.trim();
30371
30394
  const primaryHost = input.primaryHost.trim().toLowerCase();
30372
30395
  const siteName = input.siteName.trim();
30396
+ const idempotencyKey = input.idempotencyKey?.trim() || null;
30397
+ if (idempotencyKey) {
30398
+ const existing = await loadByIdempotencyKey(targetDb, idempotencyKey);
30399
+ if (existing) {
30400
+ if (existing.site.key !== siteKey || existing.domain.host !== primaryHost) throw new ConflictError("Idempotency key was reused with a different site key or primary host.");
30401
+ return existing;
30402
+ }
30403
+ }
30373
30404
  if ((await targetDb.select({ id: sites.id }).from(sites).where(eq(sites.key, siteKey)).limit(1))[0]) throw new ConflictError("Site key is already in use.");
30374
30405
  if ((await targetDb.select({ id: siteDomains.id }).from(siteDomains).where(eq(siteDomains.host, primaryHost)).limit(1))[0]) throw new ConflictError("Primary host is already in use.");
30375
30406
  const timestamp = now();
@@ -30379,6 +30410,7 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
30379
30410
  id: siteId,
30380
30411
  key: siteKey,
30381
30412
  status: "active",
30413
+ provisioningIdempotencyKey: idempotencyKey,
30382
30414
  createdAt: timestamp,
30383
30415
  updatedAt: timestamp
30384
30416
  }).returning())[0];
@@ -31484,7 +31516,8 @@ async function createRequestRuntime(env, publicRequestUrl) {
31484
31516
  //#region src/runtime/readiness.ts
31485
31517
  function getStartupConfigurationReadiness(env) {
31486
31518
  const errors = [];
31487
- if (!getAuthSecret(env)) errors.push("AUTH_SECRET must be set before Jant can accept traffic.");
31519
+ const authSecretIssue = getAuthSecretIssueKind(env);
31520
+ if (authSecretIssue) errors.push(getAuthSecretReadinessError(authSecretIssue));
31488
31521
  for (const issue of getHostBasedStartupConfigurationIssues(env)) errors.push(`${issue.variable}: ${issue.message}`);
31489
31522
  return errors.length > 0 ? {
31490
31523
  ok: false,
@@ -31859,4 +31892,4 @@ async function servePublicStorage(c) {
31859
31892
  return app;
31860
31893
  }
31861
31894
  //#endregion
31862
- export { NAV_ITEM_TYPES$2 as A, toPostView as C, MAX_MEDIA_ATTACHMENTS as D, FORMATS$2 as E, BUILTIN_COLOR_THEMES as F, getPublicAssetBasePath as I, isAssetPath as L, STATUSES$2 as M, TEXT_ATTACHMENT_CONTENT_FORMATS as N, MAX_PINNED_POSTS as O, buildThemeStyle as P, toNavItemViews as S, toSearchResultView as T, createMediaContext as _, createSiteService as a, toMediaView as b, resolveConfig as c, getFontThemeCssVariables as d, defaultFeedRenderer as f, schema_exports$1 as g, createNodeDatabase as h, createNodeRequestRuntime as i, SORT_ORDERS as j, MEDIA_KINDS as k, BUILTIN_FONT_THEMES as l, sqliteSchemaBundle as m, createRequestRuntime as n, resolveDatabaseDialect as o, pgSchemaBundle as p, createNodeCliRuntime as r, getHostBasedStartupConfigurationIssues as s, createApp as t, getCjkSerifCssVariables as u, toArchiveGroups as v, toPostViews as w, toNavItemView as x, toArchiveGroupsWithMedia as y };
31895
+ export { SORT_ORDERS as A, toPostViews as C, MAX_PINNED_POSTS as D, MAX_MEDIA_ATTACHMENTS as E, getPublicAssetBasePath as F, isAssetPath as I, TEXT_ATTACHMENT_CONTENT_FORMATS as M, buildThemeStyle as N, MEDIA_KINDS as O, BUILTIN_COLOR_THEMES as P, toPostView as S, FORMATS$2 as T, toArchiveGroups as _, resolveDatabaseDialect as a, toNavItemView as b, BUILTIN_FONT_THEMES as c, defaultFeedRenderer as d, pgSchemaBundle as f, createMediaContext as g, schema_exports$1 as h, createSiteService as i, STATUSES$2 as j, NAV_ITEM_TYPES$2 as k, getCjkSerifCssVariables as l, createNodeDatabase as m, createNodeCliRuntime as n, getHostBasedStartupConfigurationIssues as o, sqliteSchemaBundle as p, createNodeRequestRuntime as r, resolveConfig as s, createApp as t, getFontThemeCssVariables as u, toArchiveGroupsWithMedia as v, toSearchResultView as w, toNavItemViews as x, toMediaView as y };