domma-cms 1.10.17 → 1.10.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.10.19] - 2026-10-25
4
+
5
+ - **Built against domma-js 0.58.2** - see [its release notes](https://github.com/pinpointzero73/domma/releases/tag/v0.58.2).
6
+ - **Forms refuse every file the server would, before it is sent.** A file `/media/` would serve as a type no form collects - Markdown, JSON, calendar (`.ics`) and mail (`.eml`) files, stylesheets, code and the like - is now refused as it is dropped or picked ("… that kind of file cannot be uploaded here"), not only by the server after the upload. The page and the server use one list, made from the server's rule (`scripts/build-form-file-types.mjs`).
7
+ - **A visitor's file that fails to save part way (a full disk) is removed**, and a form upload is never written over an existing file.
8
+ - **The admin command palette's recent picks from "Open page…", "Open collection…" and "Open form…" survive a reload** (domma-js 0.58.2).
9
+ - **A cached page with a form keeps its file cap in step with server.json.** Changing `uploads.maxSubmissionBytes` (or `maxFileSize`) drops the cached pages that drew the old cap; no restart or form save needed.
10
+
11
+ ## [1.10.18] - 2026-10-25
12
+
13
+ - **A form's files are checked against the per-submission size cap in the browser.** A form with a file field now tells the page `uploads.maxSubmissionBytes`, so a file that would take the form's files past it is refused as it is dropped or picked ("… the files together would be more than 50 MB"), counting files kept on earlier wizard steps, and the total is checked again before posting - in the `[form]` shortcode and embedded forms alike. The server still enforces it.
14
+
3
15
  ## [1.10.17] - 2026-10-25
4
16
 
5
17
  - **Form file fields can take several files.** "Several files" and "Most files" in the form editor (`multiple`, `file.maxFiles`, default 5, at most 20). Every file is kept - stored as a list of `{url, name, size, mime}`; a single-file field is unchanged - and every file passes the type and size checks. Submissions, collection entries, `[collection]` displays, the email notification and the CSV export show each file; email links are full addresses. One submission's files are capped at `uploads.maxSubmissionBytes` (default the larger of 50 MB and `uploads.maxFileSize`). Files already written are removed when a submission fails after all, and the submissions CSV guards every cell against spreadsheet formulas. A file field's value now only ever comes from an uploaded file.
@@ -1,9 +1,9 @@
1
1
  /*!
2
- * Domma Tools CSS v0.58.1
2
+ * Domma Tools CSS v0.58.2
3
3
  * Dynamic Object Manipulation & Modeling API
4
4
  * (c) 2026 Darryl Waterhouse & DCBW-IT
5
- * Built: 2026-10-25T09:08:50.530Z
6
- * Commit: bcc485e
5
+ * Built: 2026-10-25T15:33:34.161Z
6
+ * Commit: e547926
7
7
  */
8
8
 
9
9
  /*!
@@ -1,9 +1,9 @@
1
1
  /*!
2
- * Domma Tools v0.58.1
2
+ * Domma Tools v0.58.2
3
3
  * Developer tools: Theme Roller & Page Roller
4
4
  * (c) 2026 Darryl Waterhouse & DCBW-IT
5
- * Built: 2026-10-25T09:08:32.626Z
6
- * Commit: bcc485e
5
+ * Built: 2026-10-25T15:33:15.786Z
6
+ * Commit: e547926
7
7
  *
8
8
  * Requires: domma.min.js
9
9
  */
@@ -29,7 +29,7 @@ Controls the HTTP server.
29
29
  | `csp.connectHosts` | string[] | `[]` | Other sites a public page may read from in the browser (`fetch`), added to the Content-Security-Policy's `connect-src` as `https://<host>`. The site itself and cdn.jsdelivr.net are always allowed. Bare hostnames only (`dcm.dommajs.org`); a scheme, path, port or wildcard is ignored. Read at boot, so restart after changing it |
30
30
  | `csp.imgHosts` | string[] | `[]` | Other sites a public page may show images from (an `<img>` hosted elsewhere, such as a badge or partner logo), added to the Content-Security-Policy's `img-src` as `https://<host>`. The site itself, `data:` and `blob:` are always allowed; without this an image from another site is blocked - upload it to Media instead if you can. Bare hostnames only (`statics.startupbase.io`); a scheme, path, port or wildcard is ignored. Read at boot, so restart after changing it |
31
31
  | `uploads.maxFileSize` | number | `10485760` (10 MB) | Maximum allowed upload size in bytes |
32
- | `uploads.maxSubmissionBytes` | number | 50 MB, or `uploads.maxFileSize` when that is larger | The most a public form submission's files may add up to, all file fields together. The server holds a submission's files in memory until every check has passed, so it stops reading and refuses the submission (`400`, nothing written) the moment the running total passes this. Read fresh on every submission |
32
+ | `uploads.maxSubmissionBytes` | number | 50 MB, or `uploads.maxFileSize` when that is larger | The most a public form submission's files may add up to, all file fields together. The server holds a submission's files in memory until every check has passed, so it stops reading and refuses the submission (`400`, nothing written) the moment the running total passes this. Read fresh on every submission. A form with a file field is sent to the page with `fileLimits.maxTotalBytes`, so the page refuses a file that would pass it as it is added, and checks again before posting. A cached page with a `[form]` is dropped when a change to server.json changes the cap, so it is drawn again with the new one |
33
33
  | `logging.level` | string | Log verbosity: `fatal`, `error`, `warn`, `info`, `debug`, `trace`, `silent`. Defaults to `info` in development, `warn` otherwise. `warn` hides 4xx responses and plugin `log.info` output, so a failed admin action leaves no trace - raise it to `info` if you need to diagnose one. Overridden by the `LOG_LEVEL` env var. |
34
34
  | `restartOnPluginToggle` | boolean | `true` | Restart the server automatically when an admin enables or disables a plugin. See **Plugin toggles restart the server** below |
35
35
  | `requireSignedPlugins` | boolean | `false` | When `true`, the site runs only plugin code the Marketplace signed: an unsigned `.dcmsplugin` (e.g. one exported from another site) is refused even if the installer confirms it, New plugin is refused, a plugin's files cannot be changed in the code editor (it opens read only), licensed source cannot be read even by a Super Admin, and MongoDB connection options that start a program or read a file (`autoEncryption`, `tls…File`, …) cannot be saved. A bundle with a signature that does not verify is always refused, whatever this is set to. Read fresh on every check, and also as it was at boot, so changing the file on a running site can switch it on but not off. `DOMMA_SIGNED_ONLY=1` in the environment turns it on whatever the file says - Domma CMS Manager sets it on the sites it hosts. Nothing in the admin writes `server.json`. See `server/services/codePolicy.js` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.10.17",
3
+ "version": "1.10.19",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -86,7 +86,7 @@
86
86
  "bcryptjs": "^3.0.3",
87
87
  "css-select": "^7.0.0",
88
88
  "dom-serializer": "^3.1.1",
89
- "domma-js": "0.58.1",
89
+ "domma-js": "0.58.2",
90
90
  "dotenv": "^17.2.3",
91
91
  "fastify": "5.12.7",
92
92
  "gray-matter": "^4.0.3",
@@ -1 +1 @@
1
- export const DEFAULT_MAX_SIZE=5242880,DEFAULT_MAX_FILES=5,MAX_FILES_LIMIT=20;export function fileRules(e){const t=e&&e.file||{},n=!!(e&&e.multiple),i=Math.floor(Number(t.maxFiles));return{accept:String(t.accept||"").split(",").map(o=>o.trim()).filter(Boolean),maxSize:Number(t.maxSize)||5242880,multiple:n,maxFiles:n?Math.min(i>=1?i:5,20):1}}function c(e){return e===1?"one file":`${e} files`}export function countRefusal(e,t){const{maxFiles:n,multiple:i}=fileRules(e);if(Number(t)<=n)return null;const o=e&&(e.label||e.name)||"File";return i?`"${o}" takes at most ${c(n)}.`:`"${o}" takes one file.`}export function fileRefs(e){return(Array.isArray(e)?e:[e]).filter(n=>n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.url=="string"&&(typeof n.name=="string"||n.name===void 0))}export function isFileValue(e){const t=fileRefs(e);return t.length>0&&t.length===(Array.isArray(e)?e.length:1)}export function fileValueText(e,{base:t="",urls:n=!0}={}){return fileRefs(e).map(i=>{const o=i.name||i.url.split("/").pop()||"file";return!n||!i.url?o:`${o} (${fileUrl(i,t)})`}).join("; ")}export function fileUrl(e,t=""){const n=String(e&&e.url||""),i=String(t||"").replace(/\/+$/,"");return i&&n.startsWith("/")?`${i}${n}`:n}export function mimeAccepted(e,t){if(!e||!e.length)return!0;const n=String(t||"");return e.some(i=>i.endsWith("/*")?n.startsWith(i.slice(0,-1)):i===n)}export function cleanUploadName(e){const t=n=>n.replace(/^[-.]+|[-.]+$/g,"");return t(t(String(e||"upload").toLowerCase().replace(/[^\w.-]+/g,"-").replace(/-+/g,"-")).slice(0,200))||"upload"}const p=/^(text\/html|text\/xml|text\/xsl|text\/javascript|text\/ecmascript|application\/(x-)?javascript|application\/ecmascript|application\/xml|application\/[\w.-]+\+xml|image\/svg\+xml)\b/i,m=/\.(html?|xht(ml)?|shtml?|svgz?|xml|xsd|xslt?|xul|rdf|owl|atom|rss|wsdl|smil?|kml|opf|xlf|gpx|ttml|mathml|mml|xop|m?js|mht(ml)?)$/i;function d(e){const t=/\.[^.]+$/.exec(cleanUploadName(e&&(e.filename??e.name)||""));return t?t[0]:""}export function isActiveFile(e){const t=String(e&&(e.mimetype??e.type)||""),n=e&&(e.filename??e.name)||"";return p.test(t)||m.test(cleanUploadName(n))}export function fileRefusal(e,t){const n=fileRules(e),i=e&&(e.label||e.name)||"File";if(Number(t&&t.size)>n.maxSize)return`"${i}" file is too large (max ${Math.round(n.maxSize/1024)} KB).`;const o=t&&(t.mimetype??t.type)||"";return isActiveFile(t)?`"${i}" file type "${o||d(t)}" not allowed (it could run as a page on this site).`:n.accept.length&&!mimeAccepted(n.accept,o)?`"${i}" file type "${o}" not allowed (expected: ${String(e.file.accept).trim()}).`:null}const x=[[/wordprocessingml|^msword$/,"Word"],[/spreadsheetml|ms-excel/,"Excel"],[/presentationml|ms-powerpoint/,"PowerPoint"],[/^plain$/,"text"]];export function describeAccept(e){const t=[];return(e||[]).forEach(n=>{const[i,o]=String(n).split("/");let r;o==="*"?r={image:"images",audio:"audio",video:"video",text:"text files"}[i]||`${i} files`:o?r=x.find(([s])=>s.test(o))?.[1]||o.replace(/^(x-|vnd\.)/,"").split(/[.+]/)[0].toUpperCase():r=String(n),r&&!t.includes(r)&&t.push(r)}),t.length<2?t[0]||"":`${t.slice(0,-1).join(", ")} or ${t[t.length-1]}`}export function formatSize(e){const t=Number(e)||0;if(t<1024)return`${t} B`;const n=["KB","MB","GB"];let i=t,o=-1;do i/=1024,o++;while(i>=1024&&o<n.length-1);return`${i>=10?Math.round(i):Math.round(i*10)/10} ${n[o]}`}export function fileHint(e){const t=fileRules(e),n=describeAccept(t.accept),i=t.multiple?`up to ${c(t.maxFiles)}, ${formatSize(t.maxSize)} each`:`up to ${formatSize(t.maxSize)}`;return n?`${n[0].toUpperCase()}${n.slice(1)} - ${i}`:`Any file - ${i}`}export function dropzoneOptions(e){const t=fileRules(e);return{accept:t.accept.length?t.accept.join(","):null,maxSize:t.maxSize,multiple:t.multiple,maxFiles:t.multiple?t.maxFiles:null,labels:{prompt:t.multiple?"Drop files here, paste, or click to browse":"Drop a file here, paste, or click to browse",hint:fileHint(e),required:"Please add a file",type:refusalMessage(e,{name:"{name}"},"type"),size:refusalMessage(e,{name:"{name}"},"size"),count:refusalMessage(e,{name:"{name}"},"count")}}}export function fileBlueprint(e,t){return{...t,type:"dropzone",formConfig:{...t.formConfig||{},...dropzoneOptions(e)}}}export function refusalMessage(e,t,n){const i=fileRules(e),o=t&&t.name||"That file";if(n==="size")return`${o} was not added: it is larger than ${formatSize(i.maxSize)}.`;if(n==="type"){const r=describeAccept(i.accept);return`${o} was not added: ${r?`only ${r} can be uploaded here`:"that type of file is not accepted"}.`}return n==="active"?`${o} was not added: web pages, SVG images and scripts cannot be uploaded.`:`${o} was not added: only ${c(i.maxFiles)} can be added here.`}function h(e){return Array.isArray(e)&&e.length>0&&typeof File<"u"&&e.every(t=>t instanceof File)}export function splitFiles(e,t,n){const i={...e||{}},o=new Map,r=new Set((t||[]).filter(s=>s&&s.type==="file"&&s.name).map(s=>s.name));for(const[s,l]of Object.entries(i))h(l)?(o.set(s,l),delete i[s]):r.has(s)&&(Array.isArray(l)||l==null)&&(i[s]="");for(const[s,l]of n||[])o.set(s,l||[]);for(const[s,l]of o)l.length?delete i[s]:(o.delete(s),(r.has(s)||s in i)&&(i[s]=""));return{values:i,files:o}}export function submitBody(e,t){if(![...(t||new Map).values()].some(o=>o&&o.length))return{body:JSON.stringify(e),headers:{"Content-Type":"application/json"},multipart:!1};const i=new FormData;for(const[o,r]of Object.entries(e))r==null||t.has(o)||i.append(o,typeof r=="object"?JSON.stringify(r):String(r));for(const[o,r]of t)for(const s of r||[])i.append(o,s,s.name);return{body:i,headers:{},multipart:!0}}export function captureFileFields(e,t){!e||!t||e.querySelectorAll('input[type="file"]').forEach(n=>{n.name&&t.set(n.name,Array.from(n.files||[]))})}export function restoreFileFields(e,t){!e||!t||!t.size||e.querySelectorAll('input[type="file"]').forEach(n=>{const i=t.get(n.name);if(!i||!i.length||n.files&&n.files.length)return;const o=f(n);if(o){o.addFiles(i);return}try{const r=new DataTransfer;i.forEach(s=>r.items.add(s)),n.files=r.files}catch{}})}function f(e){const t=globalThis.E;if(!t||typeof t.get!="function")return null;const n=e.closest("[data-dropzone-field]"),i=n&&t.get(n,"dropzone")||t.get(e,"dropzone");return i&&typeof i.addFiles=="function"?i:null}function u(e,t){const n=String(t&&t.name||"").replace(/\[\]$/,"");return(e||[]).find(i=>i&&i.name===n)||{name:n}}function g(e){let t=e.nextElementSibling;return(!t||!t.classList.contains("fb-file-refusal"))&&(t=document.createElement("p"),t.className="fb-file-refusal",t.hidden=!0,e.after(t)),t}export function wireFileFields(e,t){if(!e||e._fbFilesWired)return;e._fbFilesWired=!0;const n=new WeakMap,i=(o,r,s)=>{const l=g(o.zone),a=n.get(o.zone)||[];a.push(refusalMessage(u(t,o.input),r,s)),n.set(o.zone,a),l.textContent=a.join(" "),l.hidden=!1,o.zone.classList.add("is-invalid"),queueMicrotask(()=>n.delete(o.zone))};e.addEventListener("dm:dropzone:reject",o=>{const r=o.detail&&o.detail.instance,[s,l]=o.detail&&o.detail.args||[];r&&r.zone&&i(r,s,l)}),e.addEventListener("dm:dropzone:beforeadd",o=>{const r=o.detail&&o.detail.instance,[s]=o.detail&&o.detail.args||[];!r||!r.zone||!s||!isActiveFile(s.file||s)||(o.preventDefault(),i(r,s,"active"))}),e.addEventListener("dm:dropzone:add",o=>{const r=o.detail&&o.detail.instance;if(!r||!r.zone||n.get(r.zone))return;const s=r.zone.nextElementSibling;s&&s.classList.contains("fb-file-refusal")&&(s.hidden=!0,s.textContent="")}),e.addEventListener("dm:dropzone:change",o=>{const r=o.detail&&o.detail.instance;if(!r||!r.input)return;r.input.dispatchEvent(new Event("input",{bubbles:!0}));const s=r.input.closest(".fb-field-invalid");s&&r.getFiles().length&&s.classList.remove("fb-field-invalid")})}export function enhanceFileInputs(e,t){const n=globalThis.E;!e||!n||typeof n.dropzone!="function"||(e.querySelectorAll('input[type="file"]').forEach(i=>{f(i)||n.dropzone(i,dropzoneOptions(u(t,i)))}),wireFileFields(e,t))}export function checkFileInputs(e,t){const n=[];return e&&e.querySelectorAll('input[type="file"]').forEach(i=>{const o=u(t,i),r=Array.from(i.files||[]),s=countRefusal(o,r.length);s&&n.push(s),r.forEach(l=>{const a=fileRefusal(o,l);a&&n.push(a)})}),n}export function destroyDropzones(e){const t=globalThis.E;!e||!t||typeof t.get!="function"||e.querySelectorAll('.dm-dropzone, input[type="file"]').forEach(n=>{const i=t.get(n,"dropzone");i&&typeof i.destroy=="function"&&i.destroy()})}
1
+ export const DEFAULT_MAX_SIZE=5242880,DEFAULT_MAX_FILES=5,MAX_FILES_LIMIT=20;export function fileRules(e){const t=e&&e.file||{},s=!!(e&&e.multiple),n=Math.floor(Number(t.maxFiles));return{accept:String(t.accept||"").split(",").map(l=>l.trim()).filter(Boolean),maxSize:Number(t.maxSize)||5242880,multiple:s,maxFiles:s?Math.min(n>=1?n:5,20):1}}function f(e){return e===1?"one file":`${e} files`}export function countRefusal(e,t){const{maxFiles:s,multiple:n}=fileRules(e);if(Number(t)<=s)return null;const l=e&&(e.label||e.name)||"File";return n?`"${l}" takes at most ${f(s)}.`:`"${l}" takes one file.`}export function fileRefs(e){return(Array.isArray(e)?e:[e]).filter(s=>s&&typeof s=="object"&&!Array.isArray(s)&&typeof s.url=="string"&&(typeof s.name=="string"||s.name===void 0))}export function isFileValue(e){const t=fileRefs(e);return t.length>0&&t.length===(Array.isArray(e)?e.length:1)}export function fileValueText(e,{base:t="",urls:s=!0}={}){return fileRefs(e).map(n=>{const l=n.name||n.url.split("/").pop()||"file";return!s||!n.url?l:`${l} (${fileUrl(n,t)})`}).join("; ")}export function fileUrl(e,t=""){const s=String(e&&e.url||""),n=String(t||"").replace(/\/+$/,"");return n&&s.startsWith("/")?`${n}${s}`:s}export function mimeAccepted(e,t){if(!e||!e.length)return!0;const s=String(t||"");return e.some(n=>n.endsWith("/*")?s.startsWith(n.slice(0,-1)):n===s)}export function cleanUploadName(e){const t=s=>s.replace(/^[-.]+|[-.]+$/g,"");return t(t(String(e||"upload").toLowerCase().replace(/[^\w.-]+/g,"-").replace(/-+/g,"-")).slice(0,200))||"upload"}const z=/^(text\/html|text\/xml|text\/xsl|text\/javascript|text\/ecmascript|application\/(x-)?javascript|application\/ecmascript|application\/xml|application\/[\w.-]+\+xml|image\/svg\+xml)\b/i,y=/\.(html?|xht(ml)?|shtml?|svgz?|xml|xsd|xslt?|xul|rdf|owl|atom|rss|wsdl|smil?|kml|opf|xlf|gpx|ttml|mathml|mml|xop|m?js|mht(ml)?)$/i,w=new Set("123 1km 3dml 3mf aab aam aas abw ac acc ace acu acutc aep afm afp ahead ai air ait ami apk appcache application apr arc arj asc asm aso atc atom atomcat atomdeleted atomsvc atx aw azf azs azw bat bcpio bdf bdm bdoc bed bh2 blb blorb bmi bmml book box boz bsp bz bz2 c c11amc c11amz c4d c4f c4g c4p c4u cap car cb7 cba cbr cbt cbz cc cco cct ccxml cdbcmsg cdf cdfx cdkey cdmia cdmic cdmid cdmio cdmiq cdx cdxml cdy cer cfs chat chrt cif cii cjs cla class clkk clkp clkt clkw clkx clp cmc cmdf cml cmp cod coffee com cpio cpp cpt crd crl crt crx cryptonote csh csl csml csp css cst cu curl cww cxt cxx dae daf dart dataless davmount dbf dbk dcr dcurl dd2 ddd ddf der dfac dgc dic dir dis disposition-notification dmp dna dp dpg dsc dssc dtb dtd dvi dwd dwf dxp dxr ear ecma edm edx efif ei6 eml emma emotionml emz eps es es3 esa esf et3 etx eva evy exi exp ext ez ez2 ez3 f f77 f90 fcdt fcs fdf fdt fe_launch fg5 fgd fig flo flw flx fly fm fnc fo for frame fsc ftc fti fxp fxpl fzs g2w g3w gac gam gbr gca gdl gdoc geo geojson gex ggb ggt ghf gim glb gltf gml gmx gnumeric gph gpx gqf gqs gram gramps gre grv grxml gsf gsheet gslides gtar gtm gtw gv gxf gxt h hal hbci hbs hdd hdf held hh hjson hlp hpgl hpid hps hqx htc htke htm html hvd hvp hvs i2g icc ice icm ics ifb ifm iges igl igm igs igx iif imp ink inkml install iota ipfix ipk irm irp itp its ivp ivu jad jade jam jar jardiff java jisp jlt jnlp joda js json json5 jsonld jsonml jsx karbon kdbx key kfo kia kml kmz kne knp kon kpr kpt kpxx ksp ktr ktz kwd kwt lasxml latex lbd lbe les less lgr lha link66 list3820 listafp litcoffee lnk lostxml ltf lua luac lwp lzh m13 m14 m21 m3u8 m4p ma mads maei mag maker man manifest map markdown mathml mb mbk mbox mc1 mcd mcurl md mdb mdx me mesh meta4 metalink mets mfm mft mgp mgz mie mif mime mjs mkd mlp mmd mmf mml mny mobi mods mp21 mp4s mpc mpd mpkg mpm mpn mpy mqy mrc mrcx ms mscml mseed mseq msf msh msl msty mtl mts mus musd musicxml mvb mvt mwf mxf mxl mxml mxs n-gage n3 nb nbp nc ncx nfo ngdat nitf nlu nml nnd nns nnw nq nsc nsf nt ntf numbers nzb oa2 oa3 oas obd obgx obj oda ogex ogx omdoc onepkg onetmp onetoc onetoc2 opf opml oprc org osf osfpvg osm otf ova ovf owl oxps oxt p p10 p12 p7b p7c p7m p7r p7s p8 pac pages pas paw pbd pcap pcf pcl pclxl pcurl pdb pde pem pfa pfb pfm pfr pfx pgn pgp php pki pkipath pkpass pl plb plc plf pls pm pml portpkg ppd pqa prc pre prf provx ps psb psf pskcxml ptid pub pvb pwn qam qbo qfx qps qwd qwt qxb qxd qxl qxt raml rapd rcprofile rdf rdz relo rep res rif ris rl rld rm rms rmvb rnc rng roa roff rp9 rpm rpss rpst rq rs rsat rsd rsheet rss rtx run rusd s saf sass sbml sc scd scm scq scs scss scurl sda sdc sdd sdkd sdkm sdp sdw sea see seed sema semd semf senmlx sensmlx ser setpay setreg sfd-hdstx sfs sfv sgl sgm sgml sh shar shex shf shtml sieve sig silo sis sisx sit sitx siv skd skm skp skt slim slm sls slt sm smf smi smil smzip snf spc spdx spf spl spot spp spq sql src srt sru srx ssdl sse ssf ssml st stc std stf sti stk stl stpx stpxz stpz str stw styl stylus sub sus susp sv4cpio sv4crc svc svd svg svgz swa swf swi swidtag sxc sxd sxg sxi sxm sxw t t3 taglet tao tcap tcl td teacher tei teicorpus tex texi texinfo tfi tfm tk tmo toml torrent tpl tpt tr tra trig trm tsd tsv ttc ttf ttl ttml twd twds txd txf u32 u8dsn u8hdr u8mdn u8msg ubj udeb ufd ufdl ulx umj unityweb uoml uri uris urls usdz ustar utz uu uvd uvf uvt uvvd uvvf uvvt uvvx uvvz uvx uvz vbox vbox-extpack vcard vcd vcf vcg vcs vcx vdi vds vhd vis vmdk vor vox vrml vsd vsf vss vst vsw vtt vtu vxml w3d wad wadl war wasm wbs wbxml webapp webmanifest wg wgt wmd wml wmlc wmls wmlsc wmz woff woff2 wpd wqd wri wrl wsc wsdl wspolicy wtb x32 x3d x3db x3dbz x3dv x3dvz x3dz x_b x_t xaml xap xar xav xbap xbd xca xcs xdf xdm xdp xdssc xdw xel xenc xer xfdf xfdl xht xhtml xhvml xlf xml xns xo xop xpi xpl xpr xpw xpx xsd xsl xslt xsm xspf xul xvm xvml xyz xz yaml yang yin yml ymp z1 z2 z3 z4 z5 z6 z7 z8 zaz zir zirz zmm ".trim().split(" "));export function isRefusedKind(e){const t=cleanUploadName(e&&(e.filename??e.name)||"");return w.has(t.replace(/^.*\./,""))}function h(e){const t=/\.[^.]+$/.exec(cleanUploadName(e&&(e.filename??e.name)||""));return t?t[0]:""}export function isActiveFile(e){const t=String(e&&(e.mimetype??e.type)||""),s=e&&(e.filename??e.name)||"";return z.test(t)||y.test(cleanUploadName(s))}export function fileRefusal(e,t){const s=fileRules(e),n=e&&(e.label||e.name)||"File";if(Number(t&&t.size)>s.maxSize)return`"${n}" file is too large (max ${Math.round(s.maxSize/1024)} KB).`;const l=t&&(t.mimetype??t.type)||"";return isActiveFile(t)?`"${n}" file type "${l||h(t)}" not allowed (it could run as a page on this site).`:isRefusedKind(t)?`"${n}" file type "${h(t)||l}" cannot be uploaded here.`:s.accept.length&&!mimeAccepted(s.accept,l)?`"${n}" file type "${l}" not allowed (expected: ${String(e.file.accept).trim()}).`:null}const v=[[/wordprocessingml|^msword$/,"Word"],[/spreadsheetml|ms-excel/,"Excel"],[/presentationml|ms-powerpoint/,"PowerPoint"],[/^plain$/,"text"]];export function describeAccept(e){const t=[];return(e||[]).forEach(s=>{const[n,l]=String(s).split("/");let o;l==="*"?o={image:"images",audio:"audio",video:"video",text:"text files"}[n]||`${n} files`:l?o=v.find(([i])=>i.test(l))?.[1]||l.replace(/^(x-|vnd\.)/,"").split(/[.+]/)[0].toUpperCase():o=String(s),o&&!t.includes(o)&&t.push(o)}),t.length<2?t[0]||"":`${t.slice(0,-1).join(", ")} or ${t[t.length-1]}`}export function formatSize(e){const t=Number(e)||0;if(t<1024)return`${t} B`;const s=["KB","MB","GB"];let n=t,l=-1;do n/=1024,l++;while(n>=1024&&l<s.length-1);return`${n>=10?Math.round(n):Math.round(n*10)/10} ${s[l]}`}export function fileHint(e){const t=fileRules(e),s=describeAccept(t.accept),n=t.multiple?`up to ${f(t.maxFiles)}, ${formatSize(t.maxSize)} each`:`up to ${formatSize(t.maxSize)}`;return s?`${s[0].toUpperCase()}${s.slice(1)} - ${n}`:`Any file - ${n}`}export function dropzoneOptions(e){const t=fileRules(e);return{accept:t.accept.length?t.accept.join(","):null,maxSize:t.maxSize,multiple:t.multiple,maxFiles:t.multiple?t.maxFiles:null,labels:{prompt:t.multiple?"Drop files here, paste, or click to browse":"Drop a file here, paste, or click to browse",hint:fileHint(e),required:"Please add a file",type:refusalMessage(e,{name:"{name}"},"type"),size:refusalMessage(e,{name:"{name}"},"size"),count:refusalMessage(e,{name:"{name}"},"count")}}}export function fileBlueprint(e,t){return{...t,type:"dropzone",formConfig:{...t.formConfig||{},...dropzoneOptions(e)}}}export function refusalMessage(e,t,s,n){const l=fileRules(e),o=t&&t.name||"That file";if(s==="total")return`${o} was not added: the files together would be more than ${formatSize(n)}.`;if(s==="size")return`${o} was not added: it is larger than ${formatSize(l.maxSize)}.`;if(s==="type"){const i=describeAccept(l.accept);return`${o} was not added: ${i?`only ${i} can be uploaded here`:"that type of file is not accepted"}.`}return s==="active"?`${o} was not added: web pages, SVG images and scripts cannot be uploaded.`:s==="kind"?`${o} was not added: that kind of file cannot be uploaded here.`:`${o} was not added: only ${f(l.maxFiles)} can be added here.`}function k(e){return Array.isArray(e)&&e.length>0&&typeof File<"u"&&e.every(t=>t instanceof File)}export function splitFiles(e,t,s){const n={...e||{}},l=new Map,o=new Set((t||[]).filter(i=>i&&i.type==="file"&&i.name).map(i=>i.name));for(const[i,r]of Object.entries(n))k(r)?(l.set(i,r),delete n[i]):o.has(i)&&(Array.isArray(r)||r==null)&&(n[i]="");for(const[i,r]of s||[])l.set(i,r||[]);for(const[i,r]of l)r.length?delete n[i]:(l.delete(i),(o.has(i)||i in n)&&(n[i]=""));return{values:n,files:l}}export function submitBody(e,t){if(![...(t||new Map).values()].some(l=>l&&l.length))return{body:JSON.stringify(e),headers:{"Content-Type":"application/json"},multipart:!1};const n=new FormData;for(const[l,o]of Object.entries(e))o==null||t.has(l)||n.append(l,typeof o=="object"?JSON.stringify(o):String(o));for(const[l,o]of t)for(const i of o||[])n.append(l,i,i.name);return{body:n,headers:{},multipart:!0}}export function captureFileFields(e,t){!e||!t||e.querySelectorAll('input[type="file"]').forEach(s=>{s.name&&t.set(s.name,Array.from(s.files||[]))})}export function restoreFileFields(e,t){!e||!t||!t.size||e.querySelectorAll('input[type="file"]').forEach(s=>{const n=t.get(s.name);if(!n||!n.length||s.files&&s.files.length)return;const l=b(s);if(l){l.addFiles(n);return}try{const o=new DataTransfer;n.forEach(i=>o.items.add(i)),s.files=o.files}catch{}})}function b(e){const t=globalThis.E;if(!t||typeof t.get!="function")return null;const s=e.closest("[data-dropzone-field]"),n=s&&t.get(s,"dropzone")||t.get(e,"dropzone");return n&&typeof n.addFiles=="function"?n:null}function p(e,t){const s=String(t&&t.name||"").replace(/\[\]$/,"");return(e||[]).find(n=>n&&n.name===s)||{name:s}}function S(e){let t=e.nextElementSibling;return(!t||!t.classList.contains("fb-file-refusal"))&&(t=document.createElement("p"),t.className="fb-file-refusal",t.hidden=!0,e.after(t)),t}export function submissionTooLarge(e){const t=e/1048576;return`The files sent are too large together (at most ${t>=1?`${Math.round(t*10)/10} MB`:`${Math.round(e/1024)} KB`} in one submission). Send fewer or smaller files.`}export function setFileLimits(e,t,s){e&&(e._fbFileLimits=t||null,s&&(e._fbRetained=s))}function u(e){return Number(e&&e._fbFileLimits&&e._fbFileLimits.maxTotalBytes)||0}function m(e){let t=0;for(const s of e||[])t+=Number(s&&s.size)||0;return t}export function totalTooLarge(e,t){const s=u(e);if(!s||!t)return null;let n=0;for(const l of t.values())n+=m(l);return n>s?submissionTooLarge(s):null}function $(e,t){const s=globalThis.E,n=a=>s&&typeof s.get=="function"?s.get(a,"dropzone"):null,l=new Set;e.querySelectorAll('.dm-dropzone, [data-dropzone-field], input[type="file"]').forEach(a=>{const c=n(a);c&&l.add(c)});const o=new Set,i=new Set;let r=0;for(const a of l)a.input&&(o.add(a.input),a.input.name&&i.add(a.input.name)),a.zone&&a.zone.querySelectorAll('input[type="file"]').forEach(c=>o.add(c)),a!==t&&(r+=m(a.getFiles()));e.querySelectorAll('input[type="file"]').forEach(a=>{o.has(a)||(a.name&&i.add(a.name),r+=m(a.files))});for(const[a,c]of e._fbRetained||[])i.has(a)||(r+=m(c));return r}export function wireFileFields(e,t){if(!e||e._fbFilesWired)return;e._fbFilesWired=!0;const s=new WeakMap,n=(o,i,r)=>{const a=S(o.zone),c=s.get(o.zone)||[];c.push(refusalMessage(p(t,o.input),i,r,u(e))),s.set(o.zone,c),a.textContent=c.join(" "),a.hidden=!1,o.zone.classList.add("is-invalid"),queueMicrotask(()=>s.delete(o.zone))};e.addEventListener("dm:dropzone:reject",o=>{const i=o.detail&&o.detail.instance,[r,a]=o.detail&&o.detail.args||[];i&&i.zone&&n(i,r,a)});const l=new Map;e.addEventListener("dm:dropzone:beforeadd",o=>{const i=o.detail&&o.detail.instance,[r]=o.detail&&o.detail.args||[];if(!i||!i.zone||!r)return;const a=r.file||r;if(isActiveFile(a)){o.preventDefault(),n(i,r,"active");return}if(isRefusedKind(a)){o.preventDefault(),n(i,r,"kind");return}const c=u(e);if(!c)return;const d=i.options&&i.options.multiple===!1,x=Number(r.size??a.size)||0,g=d?0:l.get(i)||0;if($(e,d?i:null)+g+x>c){o.preventDefault(),n(i,r,"total");return}d||(l.has(i)||queueMicrotask(()=>l.delete(i)),l.set(i,g+x))}),e.addEventListener("dm:dropzone:change",o=>{const i=o.detail&&o.detail.instance;i&&l.delete(i)}),e.addEventListener("dm:dropzone:add",o=>{const i=o.detail&&o.detail.instance;if(!i||!i.zone||s.get(i.zone))return;const r=i.zone.nextElementSibling;r&&r.classList.contains("fb-file-refusal")&&(r.hidden=!0,r.textContent="")}),e.addEventListener("dm:dropzone:change",o=>{const i=o.detail&&o.detail.instance;if(!i||!i.input)return;i.input.dispatchEvent(new Event("input",{bubbles:!0}));const r=i.input.closest(".fb-field-invalid");r&&i.getFiles().length&&r.classList.remove("fb-field-invalid")})}export function enhanceFileInputs(e,t){const s=globalThis.E;!e||!s||typeof s.dropzone!="function"||(e.querySelectorAll('input[type="file"]').forEach(n=>{b(n)||s.dropzone(n,dropzoneOptions(p(t,n)))}),wireFileFields(e,t))}export function checkFileInputs(e,t){const s=[];return e&&e.querySelectorAll('input[type="file"]').forEach(n=>{const l=p(t,n),o=Array.from(n.files||[]),i=countRefusal(l,o.length);i&&s.push(i),o.forEach(r=>{const a=fileRefusal(l,r);a&&s.push(a)})}),s}export function destroyDropzones(e){const t=globalThis.E;!e||!t||typeof t.get!="function"||e.querySelectorAll('.dm-dropzone, input[type="file"]').forEach(s=>{const n=t.get(s,"dropzone");n&&typeof n.destroy=="function"&&n.destroy()})}
@@ -1,2 +1,2 @@
1
- import{installSmoothCarets as H}from"/public/js/smooth-caret.js";import{buildWizardSteps as K}from"/public/js/form-steps.mjs";import{applyFormClasses as F,applySubmitButtons as _,applyWizardButtons as J,resolveSecondaryButton as Y,submitWrapperClasses as $}from"/public/js/form-submit.mjs";import{captureFileFields as L,checkFileInputs as G,destroyDropzones as Q,enhanceFileInputs as X,fileBlueprint as Z,restoreFileFields as ee,splitFiles as te,submitBody as ne,wireFileFields as j}from"/public/js/form-files.mjs";H(document);const B=document.querySelectorAll("[data-form]");B.length&&B.forEach(me);function T(t){return(t||[]).some(o=>o.logic||Array.isArray(o.triggers)&&o.triggers.length)}function oe(t,o){const n=t.querySelector('[name="'+o+'"], [name="'+o+'[]"]')||t.querySelector('[data-chooser-field="'+o+'"]');if(!n)return null;const e=n.closest("form")||t;let i=n;for(;i.parentNode&&i.parentNode!==e;)i=i.parentNode;return i}export function markInvalidFields(t,o){t.querySelectorAll(".fb-field-invalid").forEach(e=>e.classList.remove("fb-field-invalid"));let n=null;(o||[]).forEach(function(e){const i=oe(t,e.name);i&&(i.classList.add("fb-field-invalid"),n||(n=i))}),n&&n.scrollIntoView&&n.scrollIntoView({behavior:"smooth",block:"center"})}export function showValidationPopup(t){const o=(t.missing||[]).map(e=>"<strong>"+R(e.label)+"</strong> is required.").concat((t.errors||[]).map(R)),n=o.join("<br>");if(typeof E<"u"&&E.alert){E.alert(n,{title:"Please complete this step"});return}window.alert(o.join(`
2
- `).replace(/<[^>]+>/g,""))}function R(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function q(t,o){let n=!1;t.addEventListener("dm-form-end",function(e){const i=e.detail&&e.detail.validation;if(i&&!i.valid){markInvalidFields(t,i.missing),showValidationPopup(i);return}n||(n=!0,t.classList.add("fb-form-loading"),Promise.resolve(o(e.detail&&e.detail.values||{})).catch(function(){}).finally(function(){n=!1,t.classList.remove("fb-form-loading")}))})}function v(t,o,n){const e=t.querySelector(".fb-form-success, .fb-form-error");e&&e.remove();const i=document.createElement("div");i.className=n==="success"?"fb-form-success":"fb-form-error",i.textContent=o,t.appendChild(i)}function A(t,o){if(t._formLogicRuntime=o,t._formLogicObserver&&(t._formLogicObserver.disconnect(),t._formLogicObserver=null),!t.parentNode||typeof MutationObserver>"u"||t._formLogicObserved)return;t._formLogicObserved=!0;const n=new MutationObserver(function(e){for(const i of e)for(const r of i.removedNodes)if(r===t||r.nodeType===1&&r.contains&&r.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),n.disconnect();return}});n.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=n}function re(t){const o=t&&t.logic;return!!(o&&(o.visibility||o.requirement))}function z(t,o){const n={};return t.forEach(function(e){if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const i=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),r=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":i?"chooser":e.type,d={...e.formConfig||{}};d.span==="full"&&o&&(d.span=o);const s={type:r,label:e.label||e.name,required:re(e)?!1:e.required||!1,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{helperText:e.helper},...e.tooltip&&{tooltip:e.tooltip},...d}};e.minLength!==void 0&&(s.minLength=e.minLength),e.maxLength!==void 0&&(s.maxLength=e.maxLength),e.min!==void 0&&(s.min=e.min),e.max!==void 0&&(s.max=e.max),i&&(e.variant&&(s.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(s.multiple=!0),e.density&&(s.density=e.density),e.columns&&(s.columns=e.columns),e.accent&&(s.accent=e.accent),e.accentStyle&&(s.accentStyle=e.accentStyle),e.glow&&(s.glow=!0),e.glowColour&&(s.glowColour=e.glowColour),e.shadow&&(s.shadow=e.shadow),e.shadowColour&&(s.shadowColour=e.shadowColour)),n[e.name]=e.type==="file"?Z(e,s):s}),n}function ie(t){const o={};return t.forEach(function(n){if(!(!n.name||n.type==="page-break"||n.type==="spacer")&&(n.type==="select"||n.type==="multiselect")&&n.required){const e=(n.options||[])[0];e&&(o[n.name]=typeof e=="object"?e.value:e)}}),o}function P(t,o){(o||[]).forEach(function(n){if(n.type!=="date"||!n.name)return;const e=t.querySelector('[name="'+n.name+'"]');e&&e.type!=="date"&&(e.type="date")})}const O="padding:.45rem .7rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:transparent;color:inherit;cursor:pointer;font:inherit;font-size:.85em;flex-shrink:0;line-height:1.2;";function D(t){const o=document.createElement("div");o.style.cssText="position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;padding:20px;";const n=document.createElement("div");n.style.cssText="background:var(--dm-surface-overlay,var(--dm-surface));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:10px;width:100%;max-width:560px;max-height:75vh;display:flex;flex-direction:column;gap:.6rem;padding:1rem;box-shadow:var(--dm-shadow-lg,0 12px 40px rgba(0,0,0,.35));";const e=document.createElement("div");e.style.cssText="display:flex;align-items:center;justify-content:space-between;gap:.5rem;";const i=document.createElement("strong");i.textContent=t;const r=document.createElement("button");r.type="button",r.textContent="\xD7",r.setAttribute("aria-label","Close"),r.style.cssText=O+"font-size:1rem;padding:.15rem .55rem;",e.appendChild(i),e.appendChild(r),n.appendChild(e);const d=document.createElement("div");d.style.cssText="display:flex;flex-direction:column;gap:.6rem;overflow:hidden;min-height:0;",n.appendChild(d),o.appendChild(n);const s=l=>{l.key==="Escape"&&a()};function a(){document.removeEventListener("keydown",s),o.remove()}return r.addEventListener("click",a),o.addEventListener("click",l=>{l.target===o&&a()}),document.addEventListener("keydown",s),document.body.appendChild(o),{body:d,close:a}}function ae(t){const{body:o,close:n}=D("Choose an icon"),e=document.createElement("input");e.type="text",e.placeholder="Search icons\u2026",e.className="form-input",e.style.cssText="padding:.45rem .6rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:var(--dm-input-bg,transparent);color:var(--dm-text);",o.appendChild(e);const i=document.createElement("div");i.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(46px,1fr));gap:4px;overflow-y:auto;min-height:0;",o.appendChild(i);const r=window.I&&typeof I.list=="function"?I.list():[],d=r.map(s=>{const a=document.createElement("button");a.type="button",a.title=s,a.dataset.iconName=s,a.style.cssText="display:flex;align-items:center;justify-content:center;padding:.55rem 0;border:1px solid transparent;border-radius:6px;background:transparent;color:inherit;cursor:pointer;";const l=document.createElement("span");return l.setAttribute("data-icon",s),l.setAttribute("data-icon-size","18"),a.appendChild(l),a.addEventListener("mouseenter",()=>{a.style.borderColor="var(--dm-border)"}),a.addEventListener("mouseleave",()=>{a.style.borderColor="transparent"}),a.addEventListener("click",()=>{n(),t(s)}),i.appendChild(a),a});if(!r.length){const s=document.createElement("p");s.textContent="Icon engine unavailable - type an icon name into the field instead.",i.appendChild(s)}window.I?.scan?.(i),e.addEventListener("input",()=>{const s=e.value.trim().toLowerCase();d.forEach(a=>{a.style.display=!s||a.dataset.iconName.includes(s)?"":"none"})}),e.focus()}async function se(){const t=typeof S<"u"&&S.get?S.get("auth_token"):null;if(!t)return{error:"auth"};const o=i=>fetch("/api/media",{headers:{Authorization:"Bearer "+i}});let n;try{if(n=await o(t),n.status===401&&S.get("auth_refresh_token")){const i=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:S.get("auth_refresh_token")})});if(i.ok){const{token:r}=await i.json();S.set("auth_token",r),n=await o(r)}}}catch{return{error:"load"}}if(!n.ok)return{error:n.status===401||n.status===403?"auth":"load"};const e=await n.json().catch(()=>[]);return{files:(Array.isArray(e)?e:[]).filter(i=>/\.(png|jpe?g|gif|webp|svg|avif)$/i.test(i.name||i.url||""))}}function ce(t){const{body:o,close:n}=D("Choose an image"),e=document.createElement("p");e.textContent="Loading media library\u2026",e.style.cssText="margin:0;font-size:.85em;color:var(--dm-text-muted);",o.appendChild(e),se().then(({files:i,error:r})=>{if(r){e.textContent=r==="auth"?"Sign in with media access to browse the library - or paste an image URL into the field.":"Could not load the media library - paste an image URL into the field instead.";return}if(!i.length){e.textContent="No images in the media library yet.";return}e.remove();const d=document.createElement("div");d.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px;overflow-y:auto;min-height:0;",i.forEach(s=>{const a=document.createElement("button");a.type="button",a.title=s.name||s.url,a.style.cssText="display:flex;flex-direction:column;gap:.25rem;padding:.35rem;border:1px solid var(--dm-border);border-radius:8px;background:transparent;color:inherit;cursor:pointer;min-width:0;";const l=document.createElement("img");l.src=s.url,l.alt=s.name||"",l.loading="lazy",l.style.cssText="width:100%;height:72px;object-fit:cover;border-radius:5px;";const f=document.createElement("span");f.textContent=s.name||s.url,f.style.cssText="font-size:.68em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",a.appendChild(l),a.appendChild(f),a.addEventListener("click",()=>{n(),t(s.url)}),d.appendChild(a)}),o.appendChild(d)})}function M(t,o){t.value=o,t.dispatchEvent(new Event("input",{bubbles:!0})),t.dispatchEvent(new Event("change",{bubbles:!0}))}function V(t){const o=document.createElement("div");return o.style.cssText="display:flex;align-items:center;gap:.5rem;",t.parentNode.insertBefore(o,t),o.appendChild(t),t.style.flex="1",t.style.minWidth="0",o}function W(t){const o=document.createElement("button");return o.type="button",o.textContent=t,o.style.cssText=O,o}function le(t){if(!window.I||typeof I.list!="function")return;const o=V(t),n=document.createElement("span");n.style.cssText="display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;flex-shrink:0;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);";const e=()=>{n.textContent="";const r=t.value.trim();if(!r)return;const d=document.createElement("span");d.setAttribute("data-icon",r),d.setAttribute("data-icon-size","16"),n.appendChild(d),window.I?.scan?.(n)};t.addEventListener("input",e),e();const i=W("Browse");i.addEventListener("click",()=>ae(r=>{M(t,r),e()})),o.appendChild(n),o.appendChild(i)}function ue(t){const o=V(t),n=document.createElement("img");n.alt="",n.style.cssText="width:2rem;height:2rem;flex-shrink:0;object-fit:cover;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);display:none;";const e=()=>{const r=t.value.trim();n.style.display=r?"":"none",r&&(n.src=r)};t.addEventListener("input",e),e();const i=W("Browse");i.addEventListener("click",()=>ce(r=>{M(t,r),e()})),o.appendChild(n),o.appendChild(i)}export function enhancePickerFields(t,o){const n=typeof t=="string"?document.querySelector(t):t;n&&(o||[]).forEach(function(e){if(!e.name||e.type!=="icon"&&e.type!=="image")return;const i=n.querySelector('[name="'+e.name+'"]');!i||i.dataset.pickerEnhanced||(i.dataset.pickerEnhanced="1",e.type==="icon"?le(i):ue(i))})}function N(t,o){const n=document.createElement("div");n.className="fb-form-honeypot",n.setAttribute("aria-hidden","true");const e=document.createElement("input");e.name=t.querySelector('[name="website"]')?"_website":"website",n.dataset.hpName=e.name,e.type="text",e.tabIndex=-1,e.autocomplete="url",e.placeholder="https://",n.appendChild(e);const i=document.createElement("input");i.name="_t",i.type="hidden",i.value=o||Date.now(),n.appendChild(i),t.appendChild(n)}function U(t,o){const n=t.querySelector("form");if(!n)return;const e=Array.from(n.querySelectorAll(".form-group"));let i=0;o.forEach(function(r){if(r.type==="spacer"){const d=document.createElement("div");d.className="fb-spacer";const s=e[i];if(s)n.insertBefore(d,s);else{const a=n.querySelector('[type="submit"]');a?n.insertBefore(d,a):n.appendChild(d)}}else r.type!=="page-break"&&i++})}function x(t,o,n,e,i,r,d){const s=i||e,a=s.querySelector(".fb-form-honeypot"),l=a?.querySelector('input[type="text"]')?.value||"",f=a?.querySelector('[name="_t"]')?.value||"",u=Object.assign({},o);delete u._t,a?.dataset.hpName&&delete u[a.dataset.hpName];const c=new Map(r||[]);L(s,c);const h=te(u,d,c),{body:p,headers:w}=ne(Object.assign(h.values,{_hp:l,_t:f}),h.files);return fetch("/api/forms/submit/"+t,{method:"POST",headers:w,body:p}).then(g=>g.json().then(b=>({ok:g.ok,body:b}))).then(g=>{g.ok&&g.body.ok?(Q(e),e.textContent="",v(e,g.body.message||n.successMessage||"Thank you.","success")):v(e,g.body.error||"Something went wrong.","error")}).catch(()=>{v(e,"Unable to submit. Please check your connection.","error")})}function de(t,o,n,e,i){const r=document.createElement("form");r.noValidate=!0,o.forEach(function(a){const l=document.createElement("div");l.className="form-group",l.style.marginBottom="1.25rem",a.name&&(l.dataset.field=a.name);const f=document.createElement("label");if(f.className="form-label",f.textContent=a.label||a.name,a.required){const c=document.createElement("span");c.textContent=" *",c.style.color="var(--dm-danger)",f.appendChild(c)}let u;a.type==="textarea"?(u=document.createElement("textarea"),u.rows=a.formConfig?.rows||4,u.className="form-input"):a.type==="select"?(u=document.createElement("select"),u.className="form-input",(a.options||[]).forEach(function(c){const h=document.createElement("option");h.value=typeof c=="string"?c:c.value??"",h.textContent=typeof c=="string"?c:c.label||h.value,u.appendChild(h)})):(u=document.createElement("input"),u.type=a.type==="icon"||a.type==="image"?"text":a.type||"text",u.className="form-input",a.placeholder&&(u.placeholder=a.placeholder),a.type==="file"&&(a.file?.accept&&(u.accept=a.file.accept),a.multiple&&(u.multiple=!0))),u.name=a.name,u.required=a.required||!1,l.appendChild(f),l.appendChild(u),r.appendChild(l)}),n.honeypot&&N(r);const d=document.createElement("button");if(d.type="submit",d.className="btn btn-primary",d.textContent=n.submitText||"Submit",$(n,"stacked").length||Y(n)){const a=document.createElement("div");a.className="form-group form-buttons",a.appendChild(d),r.appendChild(a)}else r.appendChild(d);if(r.addEventListener("submit",function(a){a.preventDefault();const l={};if(o.forEach(function(u){const c=r.querySelector('[name="'+u.name+'"]');c&&(l[u.name]=c.value)}),window.FormLogicEngine&&i){const u=window.FormLogicEngine,c=[],h=[];if(o.forEach(function(p){if(u.evaluateFieldVisibility(p,l)==="hidden"){delete l[p.name];return}const g=u.evaluateFieldRequirement(p,l),b=l[p.name];g&&(!b||!String(b).trim())&&c.push(p.label||p.name);const C=u.validateField(p,b||"",l);C.length&&h.push(C[0].message)}),c.length||h.length){const p=[];c.length&&p.push("Required: "+c.join(", ")),h.length&&p.push(h.join("; ")),v(t,p.join(". "),"error");return}}const f=G(r,o);if(f.length){v(t,f.join(" "),"error");return}if(t.dataset.triggerBlocked==="true"){const u=t._formLogicRuntime,c=u&&u.submitBlock?u.submitBlock().reasons:[];v(t,c.join(" ")||"This form cannot be submitted yet.","error");return}t.classList.add("fb-form-loading"),d.disabled=!0,x(e,l,n,t,r,null,o).finally(function(){t.classList.remove("fb-form-loading"),d.disabled=!1})}),t.appendChild(r),_(r,n,"stacked"),F(r,n,o),enhancePickerFields(t,o),X(t,o),q(t,function(a){return x(e,a,n,t,r,null,o)}),window.FormLogicEngine&&i&&T(o)){const a=new window.FormLogicEngine.FormLogicRuntime(i,t);a.init(),t._formLogicRuntime=a}}function me(t){const o=t.getAttribute("data-form");o&&fetch("/api/forms/"+o+"/public").then(n=>{if(!n.ok)throw new Error("Form not found: "+o);return n.json()}).then(n=>{const e=n.fields||[],i=n.settings||{},r=document.createElement("div");r.className="fb-form-wrapper",t.appendChild(r);const d=e.some(s=>s.type==="page-break");if(typeof Domma<"u"&&Domma.forms){const s=Number(i.columns)===3?3:2,a=i.layout||"grid";if(d&&Domma.forms.wizard){const l=K(e,n.title),f=l.map(m=>m.fields),u=l.map(function(m){return{title:m.title,description:m.description,fields:z(m.fields,s)}});let c=0,h=null,p=null;const w=Date.now(),g=new Map;j(r,e);const b=function(m){const y=window.FormLogicEngine;if(!y||!y.validateFields)return;const k=y.validateFields(r,f[c]||[]);k.valid||(m.preventDefault(),m.stopImmediatePropagation(),markInvalidFields(r,k.missing),showValidationPopup(k))},C=function(){const m=f[c]||e;if(J(r,i),F(r,i,m),P(r,m),enhancePickerFields(r,m),ee(r,g),m.some(y=>y.type==="spacer")&&U(r,m),i.honeypot){const y=r.querySelector("form");y&&N(y,w)}window.FormLogicEngine&&T(e)&&(h&&h.destroy(),h=new window.FormLogicEngine.FormLogicRuntime(n,r,{baseValues:p&&p.getStepData?p.getStepData():{}}),h.init(),A(r,h)),r.querySelectorAll(".wizard-next, .wizard-submit").forEach(y=>y.addEventListener("click",b,!0))};p=Domma.forms.wizard(r,{schema:{steps:u},layout:a,columns:s,finishText:i.submitText||"Finish",onStepChange:function(m){L(r,g),c=m,setTimeout(C,0)},onSubmit:function(m){return L(r,g),x(o,m,i,r,null,g,e)}}),q(r,function(m){return L(r,g),x(o,m,i,r,null,g,e)}),r.addEventListener("dm-form-trigger",function(m){if(!m.detail||m.detail.type!=="goto-step"||!p||!p.goToStep)return;const y=Number(m.detail.action.step);!isFinite(y)||y<0||y>=u.length||y!==c&&p.goToStep(y)}),Promise.resolve(p).then(C)}else if(Domma.forms.render){const l=z(e,s),f=ie(e);j(r,e),q(r,function(c){return x(o,c,i,r,null,null,e)});const u=Domma.forms.render(r,l,f,{submitText:i.submitText||"Submit",layout:a,columns:s,onSubmit:function(c){return x(o,c,i,r,null,null,e)}});Promise.resolve(u).then(function(){if(_(r,i,a),F(r,i,e),P(r,e),enhancePickerFields(r,e),window.FormLogicEngine&&T(e)){const c=new window.FormLogicEngine.FormLogicRuntime(n,r);c.init(),A(r,c)}if(e.some(c=>c.type==="spacer")&&U(r,e),i.honeypot){const c=r.querySelector("form");c&&N(c)}})}}else de(r,e.filter(s=>s.type!=="page-break"&&s.type!=="spacer"),i,o,n)}).catch(n=>{const e=document.createElement("p");e.textContent="Form unavailable.",e.style.cssText="color:var(--dm-danger);font-style:italic;",t.appendChild(e),console.warn("[forms]",n.message)})}
1
+ import{installSmoothCarets as K}from"/public/js/smooth-caret.js";import{buildWizardSteps as J}from"/public/js/form-steps.mjs";import{applyFormClasses as F,applySubmitButtons as _,applyWizardButtons as Y,resolveSecondaryButton as $,submitWrapperClasses as G}from"/public/js/form-submit.mjs";import{captureFileFields as L,checkFileInputs as Q,destroyDropzones as X,enhanceFileInputs as Z,fileBlueprint as ee,restoreFileFields as te,setFileLimits as j,splitFiles as ne,submitBody as oe,totalTooLarge as re,wireFileFields as B}from"/public/js/form-files.mjs";K(document);const R=document.querySelectorAll("[data-form]");R.length&&R.forEach(fe);function T(n){return(n||[]).some(o=>o.logic||Array.isArray(o.triggers)&&o.triggers.length)}function ie(n,o){const t=n.querySelector('[name="'+o+'"], [name="'+o+'[]"]')||n.querySelector('[data-chooser-field="'+o+'"]');if(!t)return null;const e=t.closest("form")||n;let i=t;for(;i.parentNode&&i.parentNode!==e;)i=i.parentNode;return i}export function markInvalidFields(n,o){n.querySelectorAll(".fb-field-invalid").forEach(e=>e.classList.remove("fb-field-invalid"));let t=null;(o||[]).forEach(function(e){const i=ie(n,e.name);i&&(i.classList.add("fb-field-invalid"),t||(t=i))}),t&&t.scrollIntoView&&t.scrollIntoView({behavior:"smooth",block:"center"})}export function showValidationPopup(n){const o=(n.missing||[]).map(e=>"<strong>"+A(e.label)+"</strong> is required.").concat((n.errors||[]).map(A)),t=o.join("<br>");if(typeof E<"u"&&E.alert){E.alert(t,{title:"Please complete this step"});return}window.alert(o.join(`
2
+ `).replace(/<[^>]+>/g,""))}function A(n){return String(n).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function q(n,o){let t=!1;n.addEventListener("dm-form-end",function(e){const i=e.detail&&e.detail.validation;if(i&&!i.valid){markInvalidFields(n,i.missing),showValidationPopup(i);return}t||(t=!0,n.classList.add("fb-form-loading"),Promise.resolve(o(e.detail&&e.detail.values||{})).catch(function(){}).finally(function(){t=!1,n.classList.remove("fb-form-loading")}))})}function x(n,o,t){const e=n.querySelector(".fb-form-success, .fb-form-error");e&&e.remove();const i=document.createElement("div");i.className=t==="success"?"fb-form-success":"fb-form-error",i.textContent=o,n.appendChild(i)}function P(n,o){if(n._formLogicRuntime=o,n._formLogicObserver&&(n._formLogicObserver.disconnect(),n._formLogicObserver=null),!n.parentNode||typeof MutationObserver>"u"||n._formLogicObserved)return;n._formLogicObserved=!0;const t=new MutationObserver(function(e){for(const i of e)for(const r of i.removedNodes)if(r===n||r.nodeType===1&&r.contains&&r.contains(n)){n._formLogicRuntime&&n._formLogicRuntime.destroy(),t.disconnect();return}});t.observe(n.parentNode,{childList:!0,subtree:!1}),n._formLogicObserver=t}function ae(n){const o=n&&n.logic;return!!(o&&(o.visibility||o.requirement))}function z(n,o){const t={};return n.forEach(function(e){if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const i=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),r=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":i?"chooser":e.type,d={...e.formConfig||{}};d.span==="full"&&o&&(d.span=o);const s={type:r,label:e.label||e.name,required:ae(e)?!1:e.required||!1,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{helperText:e.helper},...e.tooltip&&{tooltip:e.tooltip},...d}};e.minLength!==void 0&&(s.minLength=e.minLength),e.maxLength!==void 0&&(s.maxLength=e.maxLength),e.min!==void 0&&(s.min=e.min),e.max!==void 0&&(s.max=e.max),i&&(e.variant&&(s.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(s.multiple=!0),e.density&&(s.density=e.density),e.columns&&(s.columns=e.columns),e.accent&&(s.accent=e.accent),e.accentStyle&&(s.accentStyle=e.accentStyle),e.glow&&(s.glow=!0),e.glowColour&&(s.glowColour=e.glowColour),e.shadow&&(s.shadow=e.shadow),e.shadowColour&&(s.shadowColour=e.shadowColour)),t[e.name]=e.type==="file"?ee(e,s):s}),t}function se(n){const o={};return n.forEach(function(t){if(!(!t.name||t.type==="page-break"||t.type==="spacer")&&(t.type==="select"||t.type==="multiselect")&&t.required){const e=(t.options||[])[0];e&&(o[t.name]=typeof e=="object"?e.value:e)}}),o}function O(n,o){(o||[]).forEach(function(t){if(t.type!=="date"||!t.name)return;const e=n.querySelector('[name="'+t.name+'"]');e&&e.type!=="date"&&(e.type="date")})}const D="padding:.45rem .7rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:transparent;color:inherit;cursor:pointer;font:inherit;font-size:.85em;flex-shrink:0;line-height:1.2;";function M(n){const o=document.createElement("div");o.style.cssText="position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;padding:20px;";const t=document.createElement("div");t.style.cssText="background:var(--dm-surface-overlay,var(--dm-surface));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:10px;width:100%;max-width:560px;max-height:75vh;display:flex;flex-direction:column;gap:.6rem;padding:1rem;box-shadow:var(--dm-shadow-lg,0 12px 40px rgba(0,0,0,.35));";const e=document.createElement("div");e.style.cssText="display:flex;align-items:center;justify-content:space-between;gap:.5rem;";const i=document.createElement("strong");i.textContent=n;const r=document.createElement("button");r.type="button",r.textContent="\xD7",r.setAttribute("aria-label","Close"),r.style.cssText=D+"font-size:1rem;padding:.15rem .55rem;",e.appendChild(i),e.appendChild(r),t.appendChild(e);const d=document.createElement("div");d.style.cssText="display:flex;flex-direction:column;gap:.6rem;overflow:hidden;min-height:0;",t.appendChild(d),o.appendChild(t);const s=l=>{l.key==="Escape"&&a()};function a(){document.removeEventListener("keydown",s),o.remove()}return r.addEventListener("click",a),o.addEventListener("click",l=>{l.target===o&&a()}),document.addEventListener("keydown",s),document.body.appendChild(o),{body:d,close:a}}function ce(n){const{body:o,close:t}=M("Choose an icon"),e=document.createElement("input");e.type="text",e.placeholder="Search icons\u2026",e.className="form-input",e.style.cssText="padding:.45rem .6rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);background:var(--dm-input-bg,transparent);color:var(--dm-text);",o.appendChild(e);const i=document.createElement("div");i.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(46px,1fr));gap:4px;overflow-y:auto;min-height:0;",o.appendChild(i);const r=window.I&&typeof I.list=="function"?I.list():[],d=r.map(s=>{const a=document.createElement("button");a.type="button",a.title=s,a.dataset.iconName=s,a.style.cssText="display:flex;align-items:center;justify-content:center;padding:.55rem 0;border:1px solid transparent;border-radius:6px;background:transparent;color:inherit;cursor:pointer;";const l=document.createElement("span");return l.setAttribute("data-icon",s),l.setAttribute("data-icon-size","18"),a.appendChild(l),a.addEventListener("mouseenter",()=>{a.style.borderColor="var(--dm-border)"}),a.addEventListener("mouseleave",()=>{a.style.borderColor="transparent"}),a.addEventListener("click",()=>{t(),n(s)}),i.appendChild(a),a});if(!r.length){const s=document.createElement("p");s.textContent="Icon engine unavailable - type an icon name into the field instead.",i.appendChild(s)}window.I?.scan?.(i),e.addEventListener("input",()=>{const s=e.value.trim().toLowerCase();d.forEach(a=>{a.style.display=!s||a.dataset.iconName.includes(s)?"":"none"})}),e.focus()}async function le(){const n=typeof S<"u"&&S.get?S.get("auth_token"):null;if(!n)return{error:"auth"};const o=i=>fetch("/api/media",{headers:{Authorization:"Bearer "+i}});let t;try{if(t=await o(n),t.status===401&&S.get("auth_refresh_token")){const i=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:S.get("auth_refresh_token")})});if(i.ok){const{token:r}=await i.json();S.set("auth_token",r),t=await o(r)}}}catch{return{error:"load"}}if(!t.ok)return{error:t.status===401||t.status===403?"auth":"load"};const e=await t.json().catch(()=>[]);return{files:(Array.isArray(e)?e:[]).filter(i=>/\.(png|jpe?g|gif|webp|svg|avif)$/i.test(i.name||i.url||""))}}function ue(n){const{body:o,close:t}=M("Choose an image"),e=document.createElement("p");e.textContent="Loading media library\u2026",e.style.cssText="margin:0;font-size:.85em;color:var(--dm-text-muted);",o.appendChild(e),le().then(({files:i,error:r})=>{if(r){e.textContent=r==="auth"?"Sign in with media access to browse the library - or paste an image URL into the field.":"Could not load the media library - paste an image URL into the field instead.";return}if(!i.length){e.textContent="No images in the media library yet.";return}e.remove();const d=document.createElement("div");d.style.cssText="display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px;overflow-y:auto;min-height:0;",i.forEach(s=>{const a=document.createElement("button");a.type="button",a.title=s.name||s.url,a.style.cssText="display:flex;flex-direction:column;gap:.25rem;padding:.35rem;border:1px solid var(--dm-border);border-radius:8px;background:transparent;color:inherit;cursor:pointer;min-width:0;";const l=document.createElement("img");l.src=s.url,l.alt=s.name||"",l.loading="lazy",l.style.cssText="width:100%;height:72px;object-fit:cover;border-radius:5px;";const h=document.createElement("span");h.textContent=s.name||s.url,h.style.cssText="font-size:.68em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",a.appendChild(l),a.appendChild(h),a.addEventListener("click",()=>{t(),n(s.url)}),d.appendChild(a)}),o.appendChild(d)})}function V(n,o){n.value=o,n.dispatchEvent(new Event("input",{bubbles:!0})),n.dispatchEvent(new Event("change",{bubbles:!0}))}function W(n){const o=document.createElement("div");return o.style.cssText="display:flex;align-items:center;gap:.5rem;",n.parentNode.insertBefore(o,n),o.appendChild(n),n.style.flex="1",n.style.minWidth="0",o}function U(n){const o=document.createElement("button");return o.type="button",o.textContent=n,o.style.cssText=D,o}function de(n){if(!window.I||typeof I.list!="function")return;const o=W(n),t=document.createElement("span");t.style.cssText="display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;flex-shrink:0;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);";const e=()=>{t.textContent="";const r=n.value.trim();if(!r)return;const d=document.createElement("span");d.setAttribute("data-icon",r),d.setAttribute("data-icon-size","16"),t.appendChild(d),window.I?.scan?.(t)};n.addEventListener("input",e),e();const i=U("Browse");i.addEventListener("click",()=>ce(r=>{V(n,r),e()})),o.appendChild(t),o.appendChild(i)}function me(n){const o=W(n),t=document.createElement("img");t.alt="",t.style.cssText="width:2rem;height:2rem;flex-shrink:0;object-fit:cover;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md,6px);display:none;";const e=()=>{const r=n.value.trim();t.style.display=r?"":"none",r&&(t.src=r)};n.addEventListener("input",e),e();const i=U("Browse");i.addEventListener("click",()=>ue(r=>{V(n,r),e()})),o.appendChild(t),o.appendChild(i)}export function enhancePickerFields(n,o){const t=typeof n=="string"?document.querySelector(n):n;t&&(o||[]).forEach(function(e){if(!e.name||e.type!=="icon"&&e.type!=="image")return;const i=t.querySelector('[name="'+e.name+'"]');!i||i.dataset.pickerEnhanced||(i.dataset.pickerEnhanced="1",e.type==="icon"?de(i):me(i))})}function N(n,o){const t=document.createElement("div");t.className="fb-form-honeypot",t.setAttribute("aria-hidden","true");const e=document.createElement("input");e.name=n.querySelector('[name="website"]')?"_website":"website",t.dataset.hpName=e.name,e.type="text",e.tabIndex=-1,e.autocomplete="url",e.placeholder="https://",t.appendChild(e);const i=document.createElement("input");i.name="_t",i.type="hidden",i.value=o||Date.now(),t.appendChild(i),n.appendChild(t)}function H(n,o){const t=n.querySelector("form");if(!t)return;const e=Array.from(t.querySelectorAll(".form-group"));let i=0;o.forEach(function(r){if(r.type==="spacer"){const d=document.createElement("div");d.className="fb-spacer";const s=e[i];if(s)t.insertBefore(d,s);else{const a=t.querySelector('[type="submit"]');a?t.insertBefore(d,a):t.appendChild(d)}}else r.type!=="page-break"&&i++})}function C(n,o,t,e,i,r,d){const s=i||e,a=s.querySelector(".fb-form-honeypot"),l=a?.querySelector('input[type="text"]')?.value||"",h=a?.querySelector('[name="_t"]')?.value||"",u=Object.assign({},o);delete u._t,a?.dataset.hpName&&delete u[a.dataset.hpName];const c=new Map(r||[]);L(s,c);const f=ne(u,d,c),p=re(e,f.files);if(p)return x(e,p,"error"),Promise.resolve();const{body:w,headers:b}=oe(Object.assign(f.values,{_hp:l,_t:h}),f.files);return fetch("/api/forms/submit/"+n,{method:"POST",headers:b,body:w}).then(y=>y.json().then(v=>({ok:y.ok,body:v}))).then(y=>{y.ok&&y.body.ok?(X(e),e.textContent="",x(e,y.body.message||t.successMessage||"Thank you.","success")):x(e,y.body.error||"Something went wrong.","error")}).catch(()=>{x(e,"Unable to submit. Please check your connection.","error")})}function pe(n,o,t,e,i){const r=document.createElement("form");r.noValidate=!0,o.forEach(function(a){const l=document.createElement("div");l.className="form-group",l.style.marginBottom="1.25rem",a.name&&(l.dataset.field=a.name);const h=document.createElement("label");if(h.className="form-label",h.textContent=a.label||a.name,a.required){const c=document.createElement("span");c.textContent=" *",c.style.color="var(--dm-danger)",h.appendChild(c)}let u;a.type==="textarea"?(u=document.createElement("textarea"),u.rows=a.formConfig?.rows||4,u.className="form-input"):a.type==="select"?(u=document.createElement("select"),u.className="form-input",(a.options||[]).forEach(function(c){const f=document.createElement("option");f.value=typeof c=="string"?c:c.value??"",f.textContent=typeof c=="string"?c:c.label||f.value,u.appendChild(f)})):(u=document.createElement("input"),u.type=a.type==="icon"||a.type==="image"?"text":a.type||"text",u.className="form-input",a.placeholder&&(u.placeholder=a.placeholder),a.type==="file"&&(a.file?.accept&&(u.accept=a.file.accept),a.multiple&&(u.multiple=!0))),u.name=a.name,u.required=a.required||!1,l.appendChild(h),l.appendChild(u),r.appendChild(l)}),t.honeypot&&N(r);const d=document.createElement("button");if(d.type="submit",d.className="btn btn-primary",d.textContent=t.submitText||"Submit",G(t,"stacked").length||$(t)){const a=document.createElement("div");a.className="form-group form-buttons",a.appendChild(d),r.appendChild(a)}else r.appendChild(d);if(r.addEventListener("submit",function(a){a.preventDefault();const l={};if(o.forEach(function(u){const c=r.querySelector('[name="'+u.name+'"]');c&&(l[u.name]=c.value)}),window.FormLogicEngine&&i){const u=window.FormLogicEngine,c=[],f=[];if(o.forEach(function(p){if(u.evaluateFieldVisibility(p,l)==="hidden"){delete l[p.name];return}const b=u.evaluateFieldRequirement(p,l),y=l[p.name];b&&(!y||!String(y).trim())&&c.push(p.label||p.name);const v=u.validateField(p,y||"",l);v.length&&f.push(v[0].message)}),c.length||f.length){const p=[];c.length&&p.push("Required: "+c.join(", ")),f.length&&p.push(f.join("; ")),x(n,p.join(". "),"error");return}}const h=Q(r,o);if(h.length){x(n,h.join(" "),"error");return}if(n.dataset.triggerBlocked==="true"){const u=n._formLogicRuntime,c=u&&u.submitBlock?u.submitBlock().reasons:[];x(n,c.join(" ")||"This form cannot be submitted yet.","error");return}n.classList.add("fb-form-loading"),d.disabled=!0,C(e,l,t,n,r,null,o).finally(function(){n.classList.remove("fb-form-loading"),d.disabled=!1})}),n.appendChild(r),_(r,t,"stacked"),F(r,t,o),enhancePickerFields(n,o),Z(n,o),q(n,function(a){return C(e,a,t,n,r,null,o)}),window.FormLogicEngine&&i&&T(o)){const a=new window.FormLogicEngine.FormLogicRuntime(i,n);a.init(),n._formLogicRuntime=a}}function fe(n){const o=n.getAttribute("data-form");o&&fetch("/api/forms/"+o+"/public").then(t=>{if(!t.ok)throw new Error("Form not found: "+o);return t.json()}).then(t=>{const e=t.fields||[],i=t.settings||{},r=document.createElement("div");r.className="fb-form-wrapper",n.appendChild(r),j(r,t.fileLimits);const d=e.some(s=>s.type==="page-break");if(typeof Domma<"u"&&Domma.forms){const s=Number(i.columns)===3?3:2,a=i.layout||"grid";if(d&&Domma.forms.wizard){const l=J(e,t.title),h=l.map(m=>m.fields),u=l.map(function(m){return{title:m.title,description:m.description,fields:z(m.fields,s)}});let c=0,f=null,p=null;const w=Date.now(),b=new Map;j(r,t.fileLimits,b),B(r,e);const y=function(m){const g=window.FormLogicEngine;if(!g||!g.validateFields)return;const k=g.validateFields(r,h[c]||[]);k.valid||(m.preventDefault(),m.stopImmediatePropagation(),markInvalidFields(r,k.missing),showValidationPopup(k))},v=function(){const m=h[c]||e;if(Y(r,i),F(r,i,m),O(r,m),enhancePickerFields(r,m),te(r,b),m.some(g=>g.type==="spacer")&&H(r,m),i.honeypot){const g=r.querySelector("form");g&&N(g,w)}window.FormLogicEngine&&T(e)&&(f&&f.destroy(),f=new window.FormLogicEngine.FormLogicRuntime(t,r,{baseValues:p&&p.getStepData?p.getStepData():{}}),f.init(),P(r,f)),r.querySelectorAll(".wizard-next, .wizard-submit").forEach(g=>g.addEventListener("click",y,!0))};p=Domma.forms.wizard(r,{schema:{steps:u},layout:a,columns:s,finishText:i.submitText||"Finish",onStepChange:function(m){L(r,b),c=m,setTimeout(v,0)},onSubmit:function(m){return L(r,b),C(o,m,i,r,null,b,e)}}),q(r,function(m){return L(r,b),C(o,m,i,r,null,b,e)}),r.addEventListener("dm-form-trigger",function(m){if(!m.detail||m.detail.type!=="goto-step"||!p||!p.goToStep)return;const g=Number(m.detail.action.step);!isFinite(g)||g<0||g>=u.length||g!==c&&p.goToStep(g)}),Promise.resolve(p).then(v)}else if(Domma.forms.render){const l=z(e,s),h=se(e);B(r,e),q(r,function(c){return C(o,c,i,r,null,null,e)});const u=Domma.forms.render(r,l,h,{submitText:i.submitText||"Submit",layout:a,columns:s,onSubmit:function(c){return C(o,c,i,r,null,null,e)}});Promise.resolve(u).then(function(){if(_(r,i,a),F(r,i,e),O(r,e),enhancePickerFields(r,e),window.FormLogicEngine&&T(e)){const c=new window.FormLogicEngine.FormLogicRuntime(t,r);c.init(),P(r,c)}if(e.some(c=>c.type==="spacer")&&H(r,e),i.honeypot){const c=r.querySelector("form");c&&N(c)}})}}else pe(r,e.filter(s=>s.type!=="page-break"&&s.type!=="spacer"),i,o,t)}).catch(t=>{const e=document.createElement("p");e.textContent="Form unavailable.",e.style.cssText="color:var(--dm-danger);font-style:italic;",n.appendChild(e),console.warn("[forms]",t.message)})}
package/public/js/site.js CHANGED
@@ -1 +1 @@
1
- import{applyMobileMenu as se,initCarouselPopup as ie}from"/public/js/mobile-ui.mjs?v=20261016-mobile-options";import{installSmoothCarets as re}from"/public/js/smooth-caret.js";import{decodeBase64Json as W}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as U,applyColour as G,applyPill as ce,applyFloat as le,applyHighlight as V,applyItemHighlight as de,itemHighlightAttrs as me,isCurrentItem as K,menuAlign as ue,applyClasses as P,cssClasses as Y,applyEntrance as J}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as pe}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as Q,markInvalidFields as Z,showValidationPopup as ee}from"/public/js/forms.js?v=20261025-form-dropzone";import{buildWizardSteps as fe}from"/public/js/form-steps.mjs";import{applyFormClasses as te,applySubmitButtons as he,applyWizardButtons as ge}from"/public/js/form-submit.mjs";import{captureFileFields as oe,destroyDropzones as ye,fileBlueprint as ve,restoreFileFields as be,splitFiles as we,submitBody as Ce,wireFileFields as Se}from"/public/js/form-files.mjs";document.addEventListener("click",m=>{const c=m.target?.closest?.(".dm-banner__dismiss");c&&(m.preventDefault(),c.closest(".dm-banner")?.remove())}),$(()=>{re(document);const m=window.__CMS_NAV__||{},c=window.__CMS_SITE__||{};if(c.autoTheme?.enabled){let o=function(e){const a=(e||"07:00").split(":");return+a[0]*60+(+a[1]||0)},s=function(){const e=new Date,a=e.getHours()*60+e.getMinutes();return a>=o(n.dayStart)&&a<o(n.nightStart)?n.dayTheme:n.nightTheme},t=function(e){const a=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const d of[...document.body.classList])d.startsWith("dm-custom-")&&document.body.classList.remove(d);a.custom&&document.body.classList.add(a.custom)};const n=c.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&m.brand){let s=function(u,y=0){return Array.isArray(u)?u.filter(h=>!(h&&(h.type==="spacer"||h.type==="theme-switcher"))).filter(h=>!(h&&h.type==="separator"&&y===0)).map(h=>h&&h.type==="separator"?{divider:!0}:Array.isArray(h?.items)?{...h,items:s(h.items,y+1)}:h):u};const n={...m.brand},o=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let u="";n.logo?u+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(u+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(u+=`<span class="navbar-brand-text${o}">${n.text}</span>`),n.tagline&&(u+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=u}else o&&n.text&&(n.html=`<span class="navbar-brand-text${o}">${n.text}</span>`);const t=s(m.items||[]),e=m.position==="floating",a=m.orientation==="vertical";x.get(0).querySelector(":scope > .cms-nojs-links")?.remove(),Domma.elements.navbar("#site-navbar",{brand:n,items:t,variant:m.variant||"dark",position:e?"static":m.position||"sticky",collapsible:!0,appearOnHover:!!m.appearOnHover}),Domma.icons.scan("#site-navbar");const d=x.get(0);se(d,m.mobile);const p=()=>{if(!a)return;const y=d.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");y&&!y.classList.contains("open")&&y.querySelector(":scope > .navbar-dropdown-toggle")?.click()};d.classList.add(`dm-nav-align-${ue(m)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",m.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const i=m.dock||{};a&&!e&&xe(d,m.side==="right"),!a&&i.hideOnScroll&&ke(d),V(d,m),J(d,m),P(d,m.class),e&&(le(x.get(0),m),pe(x.get(0).parentNode||document));const r=d.querySelector(".navbar-toggle"),g=d.querySelector(".navbar-collapse");if(r&&g){let u=null;const y=()=>{g.classList.contains("show")&&r.click()},h=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},f=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",y),document.body.appendChild(u),document.body.classList.add("dm-drawer-open"))};r.addEventListener("click",()=>{requestAnimationFrame(()=>{g.classList.contains("show")?(p(),f()):h()})}),g.addEventListener("click",w=>{w.target.closest("a")&&requestAnimationFrame(h)}),document.addEventListener("keydown",w=>{w.key==="Escape"&&g.classList.contains("show")&&y()})}const b=d.querySelector(".navbar-nav");if(b){const u=Array.from(b.children);let y=0;for(const h of m.items||[])if(!(h&&h.type==="separator")){if(h&&h.type==="spacer"){const f=document.createElement("li"),w=["xs","sm","md","lg","xl","flex"].includes(h.size)?h.size:"md";f.className=`dm-menu-spacer dm-menu-spacer--${w}`,f.setAttribute("aria-hidden","true"),b.insertBefore(f,u[y]||null);continue}if(h&&h.type==="theme-switcher"){const f=document.createElement("li");f.className="dm-menu-theme-switcher";const w=document.createElement("span");w.dataset.themeSwitcher="menu",w.dataset.position="inline",f.appendChild(w),b.insertBefore(f,u[y]||null);continue}y++}window.CMSThemeSwitcher?.scan(b)}d.querySelectorAll("[data-path]").forEach(u=>{const y=u.dataset.path.split(".").map(Number),h=y.length>1,f=y.reduce((A,I)=>A?.items?.[I],{items:t});if(!f)return;f.colour&&G(u,f.colour),f.pill&&!h&&ce(u,f.pill),de(u,f),P(u.closest("li"),f.class);const w=U(f.badge);w&&u.appendChild(w),K(f.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),p()}const v=$("#site-footer");if(v.length){let e=function(l){return String(l??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},a=function(l){return e(l).replace(/'/g,"&#39;")},d=function(l){if(l&&l.type==="separator")return'<li class="footer-sep"><hr></li>';if(l&&l.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(l.size)?l.size:"md"}" aria-hidden="true"></li>`;if(l&&l.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const D=a(l.url||"#"),L=e(l.text||""),q=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",j=me(l),ne=[j.hover&&` data-menu-item-hover="${a(j.hover)}"`,j.active&&` data-menu-item-active="${a(j.active)}"`].filter(Boolean).join(""),ae=j.accent?` style="--dm-menu-highlight:${a(j.accent)}"`:"",X=Y(l.class).join(" ");return`<li${X?` class="${a(X)}"`:""}><a href="${D}"${ne}${ae}>${L}</a>${q}</li>`},i=function(){if(!c.footer)return"";const l=Object.keys(o).filter(L=>n[L]);if(!l.length)return"";const D=Y(c.footer.socialClass).join(" ");return`<div class="footer-social">${l.map(L=>{const{label:q,svg:j}=o[L];return`<a href="${n[L]}" target="_blank" rel="noopener noreferrer" aria-label="${q}" class="footer-social-link${D?` ${a(D)}`:""}">${j}</a>`}).join("")}</div>`},u=function(l){if(!l||l.type==="separator"||l.type==="spacer")return"";if(l.type==="theme-switcher")return`<li class="cms-footer-col">${d(l).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const D=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",L=b(l)?`<span class="cms-footer-col-title">${e(l.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(l.url||"#")}">${e(l.text||"")}</a>`,q=["cms-footer-col",...Y(l.class)].join(" ");return`<li class="${a(q)}">${L}${D}</li>`},f=function(l,D,L=!1){return(l||[]).forEach(q=>{q&&(q.type==="separator"||q.type==="spacer"||q.type==="theme-switcher")||(L&&b(q)||D.push(q),Array.isArray(q.items)&&q.items.length&&f(q.items,D))}),D};const n=c.social||{},o={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let s='<div class="footer-inner container">';const t=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""},p=(()=>{const l=c.cookieConsent;if(!l||l.showSettingsLink===!1)return"";const D=L=>(L||[]).some(q=>q&&(q.url==="#cookie-settings"||D(q.items)));return D(t.primary)||D(t.legal)?"":`<li class="cms-cookie-settings"><a href="#cookie-settings">${e(l.settingsLinkText||"Cookie settings")}</a></li>`})(),r=["columns","row"].includes(c.footer?.layout)?c.footer.layout:"auto",g=r==="columns"||r==="auto"&&t.primary.some(l=>Array.isArray(l?.items)&&l.items.length),b=l=>g&&!l.url&&Array.isArray(l.items)&&l.items.length>0;if(g){const l=m.brand||{},D=l.text||c.title||"",L=l.logo?`<img src="${a(l.logo)}" class="cms-footer-brand-logo" alt="">`:l.icon?'<span class="cms-footer-brand-icon"></span>':"",q=String(c.footer?.blurb||"").trim();s='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',s+=`<div class="cms-footer-brand">${D||L?`<a class="cms-footer-brand-name" href="${a(l.url||"/")}">${L}<span>${e(D)}</span></a>`:""}${q?`<p class="cms-footer-blurb">${e(q)}</p>`:""}${i()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(u).join("")}</ul></nav>`),s+='</div><div class="cms-footer-bottom">',t.copyright&&(s+=`<p class="cms-footer-copyright">${e(t.copyright)}</p>`),(t.legal.length||p)&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(d).join("")}${p}</ul></nav>`),s+="</div>"}else t.copyright&&(s+=`<p>${e(t.copyright)}</p>`),t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${t.primary.map(d).join("")}</ul></nav>`),(t.legal.length||p)&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(d).join("")}${p}</ul></nav>`),s+=i();s+="</div>",v.get(0).querySelector(":scope > .cms-nojs-links")?.remove();const y=v.get(0).querySelector(":scope > .cms-footer-blocks");y?.remove(),v.html(s),y&&v.get(0).prepend(y),window.CMSThemeSwitcher?.scan(v.get(0));const h=v.get(0).querySelector(".cms-footer-brand-icon");h&&m.brand?.icon&&h.setAttribute("data-icon",m.brand.icon),P(v.get(0),c.footer?.class);const w=[...f(t.primary,[],!0),...f(t.legal,[])];v.get(0).querySelectorAll(".footer-nav a").forEach((l,D)=>{const L=w[D];if(!L)return;L.colour&&G(l,L.colour);const q=U(L.badge);q&&l.appendChild(q),K(L.url,location.pathname)&&(l.classList.add("is-active"),l.setAttribute("aria-current","page"))}),V(v.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),V(v.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),J(v.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),J(v.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),P(v.get(0).querySelector(".footer-nav--primary"),t.primaryClass),P(v.get(0).querySelector(".footer-nav--legal"),t.legalClass);const I=c.footer?.showMotionToggle!==!1,N=S.get("reduced_motion"),O=c.footer?.motionDefault,B=N!=null?!!N:O==="on"?!0:O==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),M=c.footer?.motionControl==="checkbox",z=v.get(0).querySelector(".cms-footer-bottom")||v.get(0).querySelector(".footer-inner");if(I&&z){const l=document.createElement("input");l.type="checkbox",l.className=M?"footer-motion-checkbox":"form-switch-input",l.id="dm-motion-switch",l.checked=B,l.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const D=document.createElement("span");D.className=M?"footer-motion-label":"form-switch-label",D.textContent="Reduce motion";const L=document.createElement("label");L.className=M?"footer-motion-check":"form-switch footer-motion-switch",L.title="Reduce motion",L.appendChild(l),L.appendChild(D),z.appendChild(L)}}const k=document.getElementById("site-sidebar");k&&Le(k),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const C=$(".page-body");if(C.length){C.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),C.find(".tabs").each(function(){Domma.elements.tabs(this)}),C.find(".carousel").each(function(){const n={primary:"--dm-primary",secondary:"--dm-secondary",accent:"--dm-primary",text:"--dm-text",surface:"--dm-surface",background:"--dm-background",success:"--dm-success",warning:"--dm-warning",danger:"--dm-danger",info:"--dm-info"},o=p=>{const i=String(p||"").trim();return i?n[i.toLowerCase()]?`var(${n[i.toLowerCase()]})`:CSS.supports("color",i)?i:"":""},s=o(this.dataset.background),t=o(this.dataset.arrowBackground),e=o(this.dataset.arrowColor);s&&(this.style.setProperty("--dm-carousel-background",s),this.classList.add("dm-carousel-custom-background")),t&&this.style.setProperty("--dm-carousel-arrow-background",t),e&&this.style.setProperty("--dm-carousel-arrow-color",e);const a=["top","middle","bottom"].includes(this.dataset.arrowPosition)?this.dataset.arrowPosition:"middle";this.classList.add(`dm-carousel-arrows-${a}`);const d={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide",heightMode:this.dataset.heightMode==="average"?"average":"crop"};if(this.dataset.height){const p=Number(this.dataset.height);Number.isFinite(p)&&p>0&&(d.height=p)}if(this.dataset.animationDuration){const p=parseInt(this.dataset.animationDuration,10);Number.isFinite(p)&&(d.animationDuration=p)}this.dataset.animationEasing&&(d.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(p=>p.cloneNode(!0)),this._dmCarouselOpts=d),this._dmCarousel=Domma.elements.carousel(this,d),ie(this)}),C.find(".dm-countdown").each(function(){const n={autoStart:!0},o=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),t=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=t?new Date(+t[1],+t[2]-1,+t[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?n.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(n.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const a=/^dd:/i.test(o);n.format=a?"hh:mm:ss":o||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const d=this,p=r=>String(r).padStart(2,"0"),i=r=>{const g=Math.max(0,Math.floor(r/1e3)),b=d.querySelector(".dm-timer-time");b&&(b.textContent=`${p(Math.floor(g/86400))}:${p(Math.floor(g%86400/3600))}:${p(Math.floor(g%3600/60))}:${p(g%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),C.find("[data-tooltip]:not(.dm-range-slider)").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),C.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(t=>{const e={title:t.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:t.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return t.dataset.date&&(e.date=t.dataset.date),t.dataset.status&&(e.status=t.dataset.status),t.dataset.eventIcon&&(e.icon=t.dataset.eventIcon),t.dataset.entryId&&(e._entryId=t.dataset.entryId),e}),o=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:o,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});if((this.dataset.mode||"timeline")==="timeline"){const t={completed:{label:"Completed",icon:"check-circle",tone:"success"},planned:{label:"Planned",icon:"circle",tone:"secondary"},"in-progress":{label:"In progress",icon:"clock",tone:"primary"},blocked:{label:"Blocked",icon:"alert-circle",tone:"danger"}};this.querySelectorAll(".dm-progression-item").forEach((e,a)=>{const d=n[a];if(!d)return;const p=e.querySelector(".dm-progression-title");if(d.icon&&p){const r=document.createElement("span");r.dataset.icon=d.icon,r.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",r.setAttribute("aria-hidden","true"),p.prepend(r)}const i=t[d.status];if(i&&p){const r=document.createElement("span");r.className=`badge badge-${i.tone} cms-timeline-status`,r.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const g=document.createElement("span");g.dataset.icon=i.icon,g.dataset.iconSize="14",g.setAttribute("aria-hidden","true"),r.append(g,document.createTextNode(i.label)),p.after(r)}})}Domma.icons.scan(this),n.length&&(this._dmCtx={instance:s,items:n})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(n=>{const o=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",t=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,a=parseInt(n.dataset.revealDelay,10)||0,d=n.dataset.revealDirection==="rtl",p=Array.from(n.children),i={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};p.forEach((r,g)=>{r.style.opacity="0",r.style.transform=i[s]||"",r.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=d?p.length-1-g:g;r.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const r=new IntersectionObserver(g=>{g.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",r.unobserve(b.target))})},{threshold:.1});p.forEach(g=>r.observe(g))}))}),C.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),C.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,o=document.getElementById(n);if(!o)return;const s=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",s.setContent(o),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const s=W(o.dataset.config);Object.assign(n,s)}catch{}}),Object.keys(n).length>0){const o={};for(const[s,t]of Object.entries(n)){const e=t?.events?.click,{confirm:a,toast:d,alert:p,prompt:i,...r}=e||{};a||d||p||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let u=null;if(!(i&&(u=await E.prompt(i,{inputPlaceholder:r.promptPlaceholder||"",inputValue:r.promptDefault||""}),u===null))){if(r.target){const y=$(r.target);r.toggleClass&&y.toggleClass(r.toggleClass),r.addClass&&y.addClass(r.addClass),r.removeClass&&y.removeClass(r.removeClass),u!==null&&(r.setText&&y.text(u),r.setVal&&y.val(u),r.setAttr&&y.attr(r.setAttr,u))}r.href&&(window.location.href=r.href),d&&E.toast(d,{type:r.toastType||"success"}),p&&E.alert(p)}}),Object.keys(r).length&&(o[s]={...t,events:{...t.events,click:r}})):o[s]=t}$.setup(o)}}C.length&&R(C.get(0))}),window.wireCTAButtons=R;function R(m){m.querySelectorAll(".dm-cta-trigger").forEach(c=>{c.__ctaWired||(c.__ctaWired=!0,c.addEventListener("click",async()=>{const x=c.dataset.action,v=c.dataset.entry,k=c.dataset.confirm;let C=S.get("auth_token");if(!C){E.toast("Please log in to perform this action.",{type:"warning"});return}if(k&&!await E.confirm(k))return;const n=Array.from(c.childNodes).map(s=>s.cloneNode(!0));c.disabled=!0,c.textContent="Running\u2026";const o=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(c.dataset.scope?{entryId:v,scope:c.dataset.scope}:{entryId:v})});try{let s=await o(C);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const a=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(a.ok){const{token:d}=await a.json();S.set("auth_token",d),C=d,s=await o(C)}}}const t=await s.json().catch(()=>({}));if(!s.ok||t.success===!1)throw new Error(t.error||t.message||`Error ${s.status}`);E.toast(t.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{c.disabled=!1,c.textContent="",n.forEach(s=>c.appendChild(s)),Domma.icons.scan(c)}}))})}(function(){const c=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!c.length)return;const x=S.get("auth_token");function v(C,n){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){s.appendChild(document.createTextNode("Please "));const t=document.createElement("a");t.href="/login",t.textContent="sign in",s.appendChild(t),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=C;return o.appendChild(s),o}function k(C,n){const o=document.createElement("template");o.innerHTML=n||"",C.replaceWith(o.content)}c.forEach(async C=>{const n=C.dataset.collectionAttrs;if(!n)return;if(!x){C.replaceChildren(v("",!0));return}const o=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=x,t=await o(s);if(t.status===401){const a=S.get("auth_refresh_token");if(a){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(d.ok){const{token:p}=await d.json();S.set("auth_token",p),s=p,t=await o(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));C.replaceChildren(v(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();k(C,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{C.replaceChildren(v("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),R(document.body)},100)})(),(function(){const c=document.querySelectorAll("[data-collection-table]");!c.length||typeof T>"u"||!T.create||c.forEach(x=>{let v;try{v=W(x.dataset.payload)}catch{return}const{columns:k,rows:C,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:d,exportCfg:p}=v;if(!k?.length)return;if(a){const g=k.findIndex(b=>b.key==="_cta");g!==-1&&(k[g]={key:"_cta",title:"",render:(b,u)=>{const y=document.createElement("button");if(y.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,y.dataset.action=a.action||"",a.scope&&(y.dataset.scope=a.scope),y.dataset.entry=u._entryId||"",a.confirm&&(y.dataset.confirm=a.confirm),a.icon){const h=document.createElement("span");h.dataset.icon=a.icon,y.appendChild(h),y.appendChild(document.createTextNode(" "))}return y.appendChild(document.createTextNode(a.label||"Run")),y}})}const i="col-table-"+Math.random().toString(36).slice(2,7),r=document.createElement("div");r.id=i,x.replaceChildren(r),T.create("#"+i,{data:C,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r),x._dmTable={columns:k,rows:C,slug:d,exportCfg:p,render(g){T.create("#"+i,{data:g,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r)}}})})(),(function(){const c=document.querySelectorAll("[data-form-inline]");if(!c.length||typeof F>"u")return;function x(o){const s=o&&o.logic;return!!(s&&(s.visibility||s.requirement))}function v(o,s){const t={};return(o||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const a=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),d=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,p={...e.formConfig||{}};p.span==="full"&&s&&(p.span=s);const i={type:d,required:x(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...p}};e.minLength!==void 0&&(i.minLength=e.minLength),e.maxLength!==void 0&&(i.maxLength=e.maxLength),e.min!==void 0&&(i.min=e.min),e.max!==void 0&&(i.max=e.max),a&&(e.variant&&(i.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(i.multiple=!0),e.density&&(i.density=e.density),e.columns&&(i.columns=e.columns),e.accent&&(i.accent=e.accent),e.accentStyle&&(i.accentStyle=e.accentStyle),e.glow&&(i.glow=!0),e.glowColour&&(i.glowColour=e.glowColour),e.shadow&&(i.shadow=e.shadow),e.shadowColour&&(i.shadowColour=e.shadowColour)),t[e.name]=e.type==="file"?ve(e,i):i}),t}function k(o){const s={};return(o||[]).forEach(t=>{if(!(!t.name||t.type==="page-break"||t.type==="spacer")&&(t.type==="select"||t.type==="multiselect")&&t.required){const e=(t.options||[])[0];e&&(s[t.name]=typeof e=="object"?e.value:e)}}),s}function C(o,s){(s||[]).forEach(t=>{if(t.type!=="date"||!t.name)return;const e=o.querySelector(`[name="${t.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(o,s,t){let e=o.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",o.appendChild(e)),e.textContent=s,e.style.cssText=t?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}c.forEach(o=>{let s;try{s=W(o.dataset.formInline)}catch{return}o.querySelector(":scope > .cms-form-embed-summary")?.remove();const t=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,d=e.layout||"grid",p=t.some(h=>h.type==="page-break"),i=new Map;Se(o,t);const r=async h=>{if(o.dataset.triggerBlocked==="true"){const f=o._formLogicRuntime,w=f&&f.submitBlock?f.submitBlock().reasons:[];n(o,w.join(" ")||"This form cannot be submitted yet.",!0);const A=new Error("Blocked by a form trigger");throw A.formSubmitFailed=!0,A}try{const f=o.querySelector(".fb-form-honeypot"),w=f?.querySelector('input[type="text"]')||null,A=f?.querySelector('[name="_t"]')||null,I=Object.assign({},h);delete I._t,f?.dataset.hpName&&delete I[f.dataset.hpName],w!==null&&(I._hp=w.value),A!==null&&(I._t=A.value);const N=new Map(i);oe(o,N);const O=we(I,t,N),B=await H.post(`/api/forms/submit/${s.slug}`,O.files.size?Ce(O.values,O.files).body:O.values);if(B&&B.error){n(o,B.error,!0);const M=new Error(B.error);throw M.formSubmitFailed=!0,M}if(B?.redirect){window.location.href=B.redirect;return}for(ye(o);o.firstChild;)o.removeChild(o.firstChild);n(o,B?.message||e.successMessage||"Thank you for your submission.",!1)}catch(f){throw f.formSubmitFailed||n(o,f.message||"Submission failed. Please try again.",!0),f}},g=Date.now();function b(h){const f=h.querySelector("form");if(!f||f.querySelector(".fb-form-honeypot"))return;const w=document.createElement("div");w.className="fb-form-honeypot",w.setAttribute("aria-hidden","true");const A=document.createElement("input");A.name=f.querySelector('[name="website"]')?"_website":"website",w.dataset.hpName=A.name,A.type="text",A.tabIndex=-1,A.autocomplete="url",A.placeholder="https://",w.appendChild(A);const I=document.createElement("input");I.name="_t",I.type="hidden",I.value=g,w.appendChild(I),f.appendChild(w)}function u(h){if(!window.FormLogicEngine||!t.some(w=>w.logic||Array.isArray(w.triggers)&&w.triggers.length))return null;const f=new window.FormLogicEngine.FormLogicRuntime(s,o,h?{baseValues:h}:void 0);if(f.init(),o._formLogicRuntime=f,o._formLogicObserved)return f;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const w=new MutationObserver(function(A){for(const I of A)for(const N of I.removedNodes)if(N===o||N.nodeType===1&&N.contains&&N.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),w.disconnect();return}});w.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=w}return f}let y=!1;if(o.addEventListener("dm-form-end",h=>{const f=h.detail?.validation;if(f&&!f.valid){Z(o,f.missing),ee(f);return}y||(y=!0,r(h.detail?.values||{}).catch(()=>{}).finally(()=>{y=!1}))}),p&&F.wizard){const h=fe(t,s.title),f=h.map(M=>({title:M.title,description:M.description,fields:v(M.fields,a)})),w=h.map(M=>M.fields);let A=0,I=null,N=null;const O=M=>{const z=window.FormLogicEngine;if(!z||!z.validateFields)return;const l=z.validateFields(o,w[A]||[]);l.valid||(M.preventDefault(),M.stopImmediatePropagation(),Z(o,l.missing),ee(l))},B=()=>{const M=w[A]||t;ge(o,e),te(o,e,M),C(o,M),Q(o,M),be(o,i),e.honeypot!==!1&&b(o),I&&I.destroy(),I=u(N?.getStepData?N.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(z=>z.addEventListener("click",O,!0))};N=F.wizard(o,{schema:{steps:f},layout:d,columns:a,finishText:e.submitText||"Finish",onSubmit:r,onStepChange:M=>{oe(o,i),A=M,setTimeout(B,0)}}),o.addEventListener("dm-form-trigger",M=>{if(M.detail?.type!=="goto-step"||!N?.goToStep)return;const z=Number(M.detail.action.step);!Number.isFinite(z)||z<0||z>=f.length||z!==A&&N.goToStep(z)}),Promise.resolve(N).then(B)}else if(F.render){const h=F.render(o,v(t,a),k(t),{submitText:e.submitText||"Submit",layout:d,columns:a,onSubmit:r});Promise.resolve(h).then(function(){he(o,e,d),te(o,e,t),C(o,t),Q(o,t),e.honeypot!==!1&&b(o),u()})}})})();function xe(m,c){const x=document.body,v=x.classList,k=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),C=()=>window.matchMedia("(min-width: 993px)").matches;m.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const d=a.textContent.trim();d&&(a.setAttribute("title",d),a.querySelector("svg, [data-icon]")||(a.dataset.initial=d.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const o=()=>{const a=v.contains("dm-vnav-collapsed");n.setAttribute("aria-pressed",String(a)),n.setAttribute("aria-label",a?"Expand menu":"Collapse menu"),n.title=n.getAttribute("aria-label");const d=c?"chevrons-left":"chevrons-right",p=c?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?d:p}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{v.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!v.contains("dm-vnav-collapsed"))),o();const t=m.querySelector(".navbar-container")||m;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{v.toggle("dm-vnav-autohide",a),v.toggle("dm-vnav-collapsible",!a&&k.has("collapsible")),a?v.remove("dm-vnav-collapsed"):k.has("collapsible")&&S.get("nav_collapsed")===!0&&v.add("dm-vnav-collapsed"),m.classList.remove("dm-nav-revealed"),o()};m.addEventListener("pointerdown",a=>{v.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!m.classList.contains("dm-nav-revealed")&&(m.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{m.contains(a.target)||m.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const a=v.contains("dm-vnav-autohide"),d=v.contains("dm-vnav-collapsed"),p=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:d?"Expand menu":"Collapse to icons",icon:d?"maximize":"minimize",disabled:!C()||a||!k.has("collapsible"),action:()=>s(!d)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!C(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!p,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(k.has("autohide")),v.toggle("dm-vnav-collapsed",!k.has("autohide")&&k.has("start-collapsed")),o()}}]}})}function ke(m){m.classList.add("dm-nav-scroll-hide");let c=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const v=window.scrollY,k=m.matches(":focus-within")||m.querySelector(".navbar-dropdown.open, .navbar-collapse.show");v>c&&v>m.offsetHeight&&!k?m.classList.add("dm-nav-scrolled-away"):v<c&&m.classList.remove("dm-nav-scrolled-away"),c=v}))},{passive:!0})}function Le(m){let c={};try{c=JSON.parse(m.dataset.contents||"{}")}catch{}const x=m.closest(".site-main"),v=document.querySelector(".site-content"),k=new Set(String(c.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),C=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(v?[...v.querySelectorAll(i)]:[]).filter(C).filter(r=>!k.has(r.textContent.trim().toLowerCase())),o=c.headings==="h2"?n("h2"):c.headings==="h3"?n("h3"):c.headings==="h2h3"?n("h2, h3"):n("h2").length?n("h2"):n("h3");if(!o.length){x?.classList.add("no-sidebar-items");return}const s=new Set([...document.querySelectorAll("[id]")].map(i=>i.id)),t=document.createElement("nav");t.className="dm-contents",t.setAttribute("aria-label",c.title||"Page contents"),c.hover&&(t.dataset.menuHover=c.hover),c.selected&&(t.dataset.menuActive=c.selected),c.accent&&t.style.setProperty("--dm-menu-highlight",c.accent.startsWith("#")?c.accent:`var(--dm-${c.accent})`),c.font&&(t.style.fontFamily=c.font),c.fontSize&&(t.style.fontSize=c.fontSize),c.fontWeight&&(t.style.fontWeight=c.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=c.title||"",e.appendChild(a),t.appendChild(e);const d=document.createElement("ol");d.className="dm-contents-list"+(c.numbered?" is-numbered":"");const p=[];for(const i of o){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let u=b;for(let y=2;s.has(u);y++)u=`${b}-${y}`;s.add(u),i.id=u}const r=document.createElement("li");r.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${i.id}`,g.textContent=i.textContent.trim(),r.appendChild(g),d.appendChild(r),p.push([i,g])}if(t.appendChild(d),m.replaceChildren(t),c.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,r=()=>{const b=p.find(([u])=>i.get(u))||null;p.forEach(([,u])=>{const y=b&&u===b[1];u.classList.toggle("is-active",!!y),y?u.setAttribute("aria-current","location"):u.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(u=>i.set(u.target,u.isIntersecting)),r()},{rootMargin:"-80px 0px -60% 0px"});p.forEach(([b])=>g.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const r=()=>{const g=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!g)),i.setAttribute("aria-label",g?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${g?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),r()}),r(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&c.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(m.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",r=>{if(r.target.closest("button"))return;const g=m.getBoundingClientRect(),b=r.clientX-g.left,u=r.clientY-g.top,y=f=>{const w=Math.max(0,Math.min(window.innerWidth-g.width,f.clientX-b)),A=Math.max(0,Math.min(window.innerHeight-40,f.clientY-u));Object.assign(m.style,{left:`${w}px`,top:`${A}px`,right:"auto",bottom:"auto"})},h=()=>{document.removeEventListener("pointermove",y),S.set("contents_position",{x:parseInt(m.style.left,10),y:parseInt(m.style.top,10)})};document.addEventListener("pointermove",y),document.addEventListener("pointerup",h,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const i=x?.classList.contains("contents-collapsible"),r=x?.classList.contains("contents-collapsed"),g=p.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:r?"Show the contents":"Collapse to a button",icon:r?"list":"minimize",action:()=>m.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!g,action:async()=>{const b=`${location.origin}${location.pathname}#${g[0].id}`;try{await _.copyToClipboard(b),E.toast("Link copied.",{type:"success"})}catch{E.toast("Could not copy the link.",{type:"error"})}}},{label:"Back to the top",icon:"arrow-up",action:()=>window.scrollTo({top:0,behavior:"smooth"})}]}}),x?.classList.contains("contents-autohide")&&(m.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!m.classList.contains("is-revealed")&&(m.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{m.contains(i.target)||m.classList.remove("is-revealed")}))}
1
+ import{applyMobileMenu as se,initCarouselPopup as ie}from"/public/js/mobile-ui.mjs?v=20261016-mobile-options";import{installSmoothCarets as re}from"/public/js/smooth-caret.js";import{decodeBase64Json as W}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as U,applyColour as G,applyPill as ce,applyFloat as le,applyHighlight as V,applyItemHighlight as de,itemHighlightAttrs as me,isCurrentItem as K,menuAlign as ue,applyClasses as P,cssClasses as Y,applyEntrance as J}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as pe}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as Q,markInvalidFields as Z,showValidationPopup as ee}from"/public/js/forms.js?v=20261025-forms-files-total";import{buildWizardSteps as fe}from"/public/js/form-steps.mjs";import{applyFormClasses as te,applySubmitButtons as he,applyWizardButtons as ge}from"/public/js/form-submit.mjs";import{captureFileFields as oe,destroyDropzones as ye,fileBlueprint as ve,restoreFileFields as be,setFileLimits as we,splitFiles as Ce,submitBody as Se,totalTooLarge as xe,wireFileFields as ke}from"/public/js/form-files.mjs";document.addEventListener("click",m=>{const c=m.target?.closest?.(".dm-banner__dismiss");c&&(m.preventDefault(),c.closest(".dm-banner")?.remove())}),$(()=>{re(document);const m=window.__CMS_NAV__||{},c=window.__CMS_SITE__||{};if(c.autoTheme?.enabled){let o=function(e){const a=(e||"07:00").split(":");return+a[0]*60+(+a[1]||0)},s=function(){const e=new Date,a=e.getHours()*60+e.getMinutes();return a>=o(n.dayStart)&&a<o(n.nightStart)?n.dayTheme:n.nightTheme},t=function(e){const a=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const d of[...document.body.classList])d.startsWith("dm-custom-")&&document.body.classList.remove(d);a.custom&&document.body.classList.add(a.custom)};const n=c.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&m.brand){let s=function(u,y=0){return Array.isArray(u)?u.filter(h=>!(h&&(h.type==="spacer"||h.type==="theme-switcher"))).filter(h=>!(h&&h.type==="separator"&&y===0)).map(h=>h&&h.type==="separator"?{divider:!0}:Array.isArray(h?.items)?{...h,items:s(h.items,y+1)}:h):u};const n={...m.brand},o=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let u="";n.logo?u+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(u+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(u+=`<span class="navbar-brand-text${o}">${n.text}</span>`),n.tagline&&(u+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=u}else o&&n.text&&(n.html=`<span class="navbar-brand-text${o}">${n.text}</span>`);const t=s(m.items||[]),e=m.position==="floating",a=m.orientation==="vertical";x.get(0).querySelector(":scope > .cms-nojs-links")?.remove(),Domma.elements.navbar("#site-navbar",{brand:n,items:t,variant:m.variant||"dark",position:e?"static":m.position||"sticky",collapsible:!0,appearOnHover:!!m.appearOnHover}),Domma.icons.scan("#site-navbar");const d=x.get(0);se(d,m.mobile);const p=()=>{if(!a)return;const y=d.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");y&&!y.classList.contains("open")&&y.querySelector(":scope > .navbar-dropdown-toggle")?.click()};d.classList.add(`dm-nav-align-${ue(m)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",m.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const i=m.dock||{};a&&!e&&Le(d,m.side==="right"),!a&&i.hideOnScroll&&$e(d),V(d,m),J(d,m),P(d,m.class),e&&(le(x.get(0),m),pe(x.get(0).parentNode||document));const r=d.querySelector(".navbar-toggle"),g=d.querySelector(".navbar-collapse");if(r&&g){let u=null;const y=()=>{g.classList.contains("show")&&r.click()},h=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},f=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",y),document.body.appendChild(u),document.body.classList.add("dm-drawer-open"))};r.addEventListener("click",()=>{requestAnimationFrame(()=>{g.classList.contains("show")?(p(),f()):h()})}),g.addEventListener("click",w=>{w.target.closest("a")&&requestAnimationFrame(h)}),document.addEventListener("keydown",w=>{w.key==="Escape"&&g.classList.contains("show")&&y()})}const b=d.querySelector(".navbar-nav");if(b){const u=Array.from(b.children);let y=0;for(const h of m.items||[])if(!(h&&h.type==="separator")){if(h&&h.type==="spacer"){const f=document.createElement("li"),w=["xs","sm","md","lg","xl","flex"].includes(h.size)?h.size:"md";f.className=`dm-menu-spacer dm-menu-spacer--${w}`,f.setAttribute("aria-hidden","true"),b.insertBefore(f,u[y]||null);continue}if(h&&h.type==="theme-switcher"){const f=document.createElement("li");f.className="dm-menu-theme-switcher";const w=document.createElement("span");w.dataset.themeSwitcher="menu",w.dataset.position="inline",f.appendChild(w),b.insertBefore(f,u[y]||null);continue}y++}window.CMSThemeSwitcher?.scan(b)}d.querySelectorAll("[data-path]").forEach(u=>{const y=u.dataset.path.split(".").map(Number),h=y.length>1,f=y.reduce((q,I)=>q?.items?.[I],{items:t});if(!f)return;f.colour&&G(u,f.colour),f.pill&&!h&&ce(u,f.pill),de(u,f),P(u.closest("li"),f.class);const w=U(f.badge);w&&u.appendChild(w),K(f.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),p()}const v=$("#site-footer");if(v.length){let e=function(l){return String(l??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},a=function(l){return e(l).replace(/'/g,"&#39;")},d=function(l){if(l&&l.type==="separator")return'<li class="footer-sep"><hr></li>';if(l&&l.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(l.size)?l.size:"md"}" aria-hidden="true"></li>`;if(l&&l.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const D=a(l.url||"#"),A=e(l.text||""),M=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",j=me(l),ne=[j.hover&&` data-menu-item-hover="${a(j.hover)}"`,j.active&&` data-menu-item-active="${a(j.active)}"`].filter(Boolean).join(""),ae=j.accent?` style="--dm-menu-highlight:${a(j.accent)}"`:"",X=Y(l.class).join(" ");return`<li${X?` class="${a(X)}"`:""}><a href="${D}"${ne}${ae}>${A}</a>${M}</li>`},i=function(){if(!c.footer)return"";const l=Object.keys(o).filter(A=>n[A]);if(!l.length)return"";const D=Y(c.footer.socialClass).join(" ");return`<div class="footer-social">${l.map(A=>{const{label:M,svg:j}=o[A];return`<a href="${n[A]}" target="_blank" rel="noopener noreferrer" aria-label="${M}" class="footer-social-link${D?` ${a(D)}`:""}">${j}</a>`}).join("")}</div>`},u=function(l){if(!l||l.type==="separator"||l.type==="spacer")return"";if(l.type==="theme-switcher")return`<li class="cms-footer-col">${d(l).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const D=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",A=b(l)?`<span class="cms-footer-col-title">${e(l.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(l.url||"#")}">${e(l.text||"")}</a>`,M=["cms-footer-col",...Y(l.class)].join(" ");return`<li class="${a(M)}">${A}${D}</li>`},f=function(l,D,A=!1){return(l||[]).forEach(M=>{M&&(M.type==="separator"||M.type==="spacer"||M.type==="theme-switcher")||(A&&b(M)||D.push(M),Array.isArray(M.items)&&M.items.length&&f(M.items,D))}),D};const n=c.social||{},o={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let s='<div class="footer-inner container">';const t=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""},p=(()=>{const l=c.cookieConsent;if(!l||l.showSettingsLink===!1)return"";const D=A=>(A||[]).some(M=>M&&(M.url==="#cookie-settings"||D(M.items)));return D(t.primary)||D(t.legal)?"":`<li class="cms-cookie-settings"><a href="#cookie-settings">${e(l.settingsLinkText||"Cookie settings")}</a></li>`})(),r=["columns","row"].includes(c.footer?.layout)?c.footer.layout:"auto",g=r==="columns"||r==="auto"&&t.primary.some(l=>Array.isArray(l?.items)&&l.items.length),b=l=>g&&!l.url&&Array.isArray(l.items)&&l.items.length>0;if(g){const l=m.brand||{},D=l.text||c.title||"",A=l.logo?`<img src="${a(l.logo)}" class="cms-footer-brand-logo" alt="">`:l.icon?'<span class="cms-footer-brand-icon"></span>':"",M=String(c.footer?.blurb||"").trim();s='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',s+=`<div class="cms-footer-brand">${D||A?`<a class="cms-footer-brand-name" href="${a(l.url||"/")}">${A}<span>${e(D)}</span></a>`:""}${M?`<p class="cms-footer-blurb">${e(M)}</p>`:""}${i()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(u).join("")}</ul></nav>`),s+='</div><div class="cms-footer-bottom">',t.copyright&&(s+=`<p class="cms-footer-copyright">${e(t.copyright)}</p>`),(t.legal.length||p)&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(d).join("")}${p}</ul></nav>`),s+="</div>"}else t.copyright&&(s+=`<p>${e(t.copyright)}</p>`),t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${t.primary.map(d).join("")}</ul></nav>`),(t.legal.length||p)&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(d).join("")}${p}</ul></nav>`),s+=i();s+="</div>",v.get(0).querySelector(":scope > .cms-nojs-links")?.remove();const y=v.get(0).querySelector(":scope > .cms-footer-blocks");y?.remove(),v.html(s),y&&v.get(0).prepend(y),window.CMSThemeSwitcher?.scan(v.get(0));const h=v.get(0).querySelector(".cms-footer-brand-icon");h&&m.brand?.icon&&h.setAttribute("data-icon",m.brand.icon),P(v.get(0),c.footer?.class);const w=[...f(t.primary,[],!0),...f(t.legal,[])];v.get(0).querySelectorAll(".footer-nav a").forEach((l,D)=>{const A=w[D];if(!A)return;A.colour&&G(l,A.colour);const M=U(A.badge);M&&l.appendChild(M),K(A.url,location.pathname)&&(l.classList.add("is-active"),l.setAttribute("aria-current","page"))}),V(v.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),V(v.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),J(v.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),J(v.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),P(v.get(0).querySelector(".footer-nav--primary"),t.primaryClass),P(v.get(0).querySelector(".footer-nav--legal"),t.legalClass);const I=c.footer?.showMotionToggle!==!1,z=S.get("reduced_motion"),B=c.footer?.motionDefault,O=z!=null?!!z:B==="on"?!0:B==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),k=c.footer?.motionControl==="checkbox",N=v.get(0).querySelector(".cms-footer-bottom")||v.get(0).querySelector(".footer-inner");if(I&&N){const l=document.createElement("input");l.type="checkbox",l.className=k?"footer-motion-checkbox":"form-switch-input",l.id="dm-motion-switch",l.checked=O,l.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const D=document.createElement("span");D.className=k?"footer-motion-label":"form-switch-label",D.textContent="Reduce motion";const A=document.createElement("label");A.className=k?"footer-motion-check":"form-switch footer-motion-switch",A.title="Reduce motion",A.appendChild(l),A.appendChild(D),N.appendChild(A)}}const L=document.getElementById("site-sidebar");L&&Ee(L),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const C=$(".page-body");if(C.length){C.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),C.find(".tabs").each(function(){Domma.elements.tabs(this)}),C.find(".carousel").each(function(){const n={primary:"--dm-primary",secondary:"--dm-secondary",accent:"--dm-primary",text:"--dm-text",surface:"--dm-surface",background:"--dm-background",success:"--dm-success",warning:"--dm-warning",danger:"--dm-danger",info:"--dm-info"},o=p=>{const i=String(p||"").trim();return i?n[i.toLowerCase()]?`var(${n[i.toLowerCase()]})`:CSS.supports("color",i)?i:"":""},s=o(this.dataset.background),t=o(this.dataset.arrowBackground),e=o(this.dataset.arrowColor);s&&(this.style.setProperty("--dm-carousel-background",s),this.classList.add("dm-carousel-custom-background")),t&&this.style.setProperty("--dm-carousel-arrow-background",t),e&&this.style.setProperty("--dm-carousel-arrow-color",e);const a=["top","middle","bottom"].includes(this.dataset.arrowPosition)?this.dataset.arrowPosition:"middle";this.classList.add(`dm-carousel-arrows-${a}`);const d={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide",heightMode:this.dataset.heightMode==="average"?"average":"crop"};if(this.dataset.height){const p=Number(this.dataset.height);Number.isFinite(p)&&p>0&&(d.height=p)}if(this.dataset.animationDuration){const p=parseInt(this.dataset.animationDuration,10);Number.isFinite(p)&&(d.animationDuration=p)}this.dataset.animationEasing&&(d.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(p=>p.cloneNode(!0)),this._dmCarouselOpts=d),this._dmCarousel=Domma.elements.carousel(this,d),ie(this)}),C.find(".dm-countdown").each(function(){const n={autoStart:!0},o=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),t=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=t?new Date(+t[1],+t[2]-1,+t[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?n.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(n.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const a=/^dd:/i.test(o);n.format=a?"hh:mm:ss":o||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const d=this,p=r=>String(r).padStart(2,"0"),i=r=>{const g=Math.max(0,Math.floor(r/1e3)),b=d.querySelector(".dm-timer-time");b&&(b.textContent=`${p(Math.floor(g/86400))}:${p(Math.floor(g%86400/3600))}:${p(Math.floor(g%3600/60))}:${p(g%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),C.find("[data-tooltip]:not(.dm-range-slider)").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),C.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(t=>{const e={title:t.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:t.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return t.dataset.date&&(e.date=t.dataset.date),t.dataset.status&&(e.status=t.dataset.status),t.dataset.eventIcon&&(e.icon=t.dataset.eventIcon),t.dataset.entryId&&(e._entryId=t.dataset.entryId),e}),o=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:o,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});if((this.dataset.mode||"timeline")==="timeline"){const t={completed:{label:"Completed",icon:"check-circle",tone:"success"},planned:{label:"Planned",icon:"circle",tone:"secondary"},"in-progress":{label:"In progress",icon:"clock",tone:"primary"},blocked:{label:"Blocked",icon:"alert-circle",tone:"danger"}};this.querySelectorAll(".dm-progression-item").forEach((e,a)=>{const d=n[a];if(!d)return;const p=e.querySelector(".dm-progression-title");if(d.icon&&p){const r=document.createElement("span");r.dataset.icon=d.icon,r.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",r.setAttribute("aria-hidden","true"),p.prepend(r)}const i=t[d.status];if(i&&p){const r=document.createElement("span");r.className=`badge badge-${i.tone} cms-timeline-status`,r.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const g=document.createElement("span");g.dataset.icon=i.icon,g.dataset.iconSize="14",g.setAttribute("aria-hidden","true"),r.append(g,document.createTextNode(i.label)),p.after(r)}})}Domma.icons.scan(this),n.length&&(this._dmCtx={instance:s,items:n})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(n=>{const o=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",t=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,a=parseInt(n.dataset.revealDelay,10)||0,d=n.dataset.revealDirection==="rtl",p=Array.from(n.children),i={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};p.forEach((r,g)=>{r.style.opacity="0",r.style.transform=i[s]||"",r.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=d?p.length-1-g:g;r.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const r=new IntersectionObserver(g=>{g.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",r.unobserve(b.target))})},{threshold:.1});p.forEach(g=>r.observe(g))}))}),C.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),C.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,o=document.getElementById(n);if(!o)return;const s=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",s.setContent(o),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const s=W(o.dataset.config);Object.assign(n,s)}catch{}}),Object.keys(n).length>0){const o={};for(const[s,t]of Object.entries(n)){const e=t?.events?.click,{confirm:a,toast:d,alert:p,prompt:i,...r}=e||{};a||d||p||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let u=null;if(!(i&&(u=await E.prompt(i,{inputPlaceholder:r.promptPlaceholder||"",inputValue:r.promptDefault||""}),u===null))){if(r.target){const y=$(r.target);r.toggleClass&&y.toggleClass(r.toggleClass),r.addClass&&y.addClass(r.addClass),r.removeClass&&y.removeClass(r.removeClass),u!==null&&(r.setText&&y.text(u),r.setVal&&y.val(u),r.setAttr&&y.attr(r.setAttr,u))}r.href&&(window.location.href=r.href),d&&E.toast(d,{type:r.toastType||"success"}),p&&E.alert(p)}}),Object.keys(r).length&&(o[s]={...t,events:{...t.events,click:r}})):o[s]=t}$.setup(o)}}C.length&&R(C.get(0))}),window.wireCTAButtons=R;function R(m){m.querySelectorAll(".dm-cta-trigger").forEach(c=>{c.__ctaWired||(c.__ctaWired=!0,c.addEventListener("click",async()=>{const x=c.dataset.action,v=c.dataset.entry,L=c.dataset.confirm;let C=S.get("auth_token");if(!C){E.toast("Please log in to perform this action.",{type:"warning"});return}if(L&&!await E.confirm(L))return;const n=Array.from(c.childNodes).map(s=>s.cloneNode(!0));c.disabled=!0,c.textContent="Running\u2026";const o=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(c.dataset.scope?{entryId:v,scope:c.dataset.scope}:{entryId:v})});try{let s=await o(C);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const a=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(a.ok){const{token:d}=await a.json();S.set("auth_token",d),C=d,s=await o(C)}}}const t=await s.json().catch(()=>({}));if(!s.ok||t.success===!1)throw new Error(t.error||t.message||`Error ${s.status}`);E.toast(t.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{c.disabled=!1,c.textContent="",n.forEach(s=>c.appendChild(s)),Domma.icons.scan(c)}}))})}(function(){const c=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!c.length)return;const x=S.get("auth_token");function v(C,n){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){s.appendChild(document.createTextNode("Please "));const t=document.createElement("a");t.href="/login",t.textContent="sign in",s.appendChild(t),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=C;return o.appendChild(s),o}function L(C,n){const o=document.createElement("template");o.innerHTML=n||"",C.replaceWith(o.content)}c.forEach(async C=>{const n=C.dataset.collectionAttrs;if(!n)return;if(!x){C.replaceChildren(v("",!0));return}const o=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=x,t=await o(s);if(t.status===401){const a=S.get("auth_refresh_token");if(a){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(d.ok){const{token:p}=await d.json();S.set("auth_token",p),s=p,t=await o(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));C.replaceChildren(v(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();L(C,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{C.replaceChildren(v("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),R(document.body)},100)})(),(function(){const c=document.querySelectorAll("[data-collection-table]");!c.length||typeof T>"u"||!T.create||c.forEach(x=>{let v;try{v=W(x.dataset.payload)}catch{return}const{columns:L,rows:C,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:d,exportCfg:p}=v;if(!L?.length)return;if(a){const g=L.findIndex(b=>b.key==="_cta");g!==-1&&(L[g]={key:"_cta",title:"",render:(b,u)=>{const y=document.createElement("button");if(y.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,y.dataset.action=a.action||"",a.scope&&(y.dataset.scope=a.scope),y.dataset.entry=u._entryId||"",a.confirm&&(y.dataset.confirm=a.confirm),a.icon){const h=document.createElement("span");h.dataset.icon=a.icon,y.appendChild(h),y.appendChild(document.createTextNode(" "))}return y.appendChild(document.createTextNode(a.label||"Run")),y}})}const i="col-table-"+Math.random().toString(36).slice(2,7),r=document.createElement("div");r.id=i,x.replaceChildren(r),T.create("#"+i,{data:C,columns:L,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r),x._dmTable={columns:L,rows:C,slug:d,exportCfg:p,render(g){T.create("#"+i,{data:g,columns:L,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r)}}})})(),(function(){const c=document.querySelectorAll("[data-form-inline]");if(!c.length||typeof F>"u")return;function x(o){const s=o&&o.logic;return!!(s&&(s.visibility||s.requirement))}function v(o,s){const t={};return(o||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const a=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),d=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,p={...e.formConfig||{}};p.span==="full"&&s&&(p.span=s);const i={type:d,required:x(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...p}};e.minLength!==void 0&&(i.minLength=e.minLength),e.maxLength!==void 0&&(i.maxLength=e.maxLength),e.min!==void 0&&(i.min=e.min),e.max!==void 0&&(i.max=e.max),a&&(e.variant&&(i.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(i.multiple=!0),e.density&&(i.density=e.density),e.columns&&(i.columns=e.columns),e.accent&&(i.accent=e.accent),e.accentStyle&&(i.accentStyle=e.accentStyle),e.glow&&(i.glow=!0),e.glowColour&&(i.glowColour=e.glowColour),e.shadow&&(i.shadow=e.shadow),e.shadowColour&&(i.shadowColour=e.shadowColour)),t[e.name]=e.type==="file"?ve(e,i):i}),t}function L(o){const s={};return(o||[]).forEach(t=>{if(!(!t.name||t.type==="page-break"||t.type==="spacer")&&(t.type==="select"||t.type==="multiselect")&&t.required){const e=(t.options||[])[0];e&&(s[t.name]=typeof e=="object"?e.value:e)}}),s}function C(o,s){(s||[]).forEach(t=>{if(t.type!=="date"||!t.name)return;const e=o.querySelector(`[name="${t.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(o,s,t){let e=o.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",o.appendChild(e)),e.textContent=s,e.style.cssText=t?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}c.forEach(o=>{let s;try{s=W(o.dataset.formInline)}catch{return}o.querySelector(":scope > .cms-form-embed-summary")?.remove();const t=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,d=e.layout||"grid",p=t.some(h=>h.type==="page-break"),i=new Map;we(o,s.fileLimits,i),ke(o,t);const r=async h=>{if(o.dataset.triggerBlocked==="true"){const f=o._formLogicRuntime,w=f&&f.submitBlock?f.submitBlock().reasons:[];n(o,w.join(" ")||"This form cannot be submitted yet.",!0);const q=new Error("Blocked by a form trigger");throw q.formSubmitFailed=!0,q}try{const f=o.querySelector(".fb-form-honeypot"),w=f?.querySelector('input[type="text"]')||null,q=f?.querySelector('[name="_t"]')||null,I=Object.assign({},h);delete I._t,f?.dataset.hpName&&delete I[f.dataset.hpName],w!==null&&(I._hp=w.value),q!==null&&(I._t=q.value);const z=new Map(i);oe(o,z);const B=Ce(I,t,z),O=xe(o,B.files);if(O){n(o,O,!0);const N=new Error(O);throw N.formSubmitFailed=!0,N}const k=await H.post(`/api/forms/submit/${s.slug}`,B.files.size?Se(B.values,B.files).body:B.values);if(k&&k.error){n(o,k.error,!0);const N=new Error(k.error);throw N.formSubmitFailed=!0,N}if(k?.redirect){window.location.href=k.redirect;return}for(ye(o);o.firstChild;)o.removeChild(o.firstChild);n(o,k?.message||e.successMessage||"Thank you for your submission.",!1)}catch(f){throw f.formSubmitFailed||n(o,f.message||"Submission failed. Please try again.",!0),f}},g=Date.now();function b(h){const f=h.querySelector("form");if(!f||f.querySelector(".fb-form-honeypot"))return;const w=document.createElement("div");w.className="fb-form-honeypot",w.setAttribute("aria-hidden","true");const q=document.createElement("input");q.name=f.querySelector('[name="website"]')?"_website":"website",w.dataset.hpName=q.name,q.type="text",q.tabIndex=-1,q.autocomplete="url",q.placeholder="https://",w.appendChild(q);const I=document.createElement("input");I.name="_t",I.type="hidden",I.value=g,w.appendChild(I),f.appendChild(w)}function u(h){if(!window.FormLogicEngine||!t.some(w=>w.logic||Array.isArray(w.triggers)&&w.triggers.length))return null;const f=new window.FormLogicEngine.FormLogicRuntime(s,o,h?{baseValues:h}:void 0);if(f.init(),o._formLogicRuntime=f,o._formLogicObserved)return f;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const w=new MutationObserver(function(q){for(const I of q)for(const z of I.removedNodes)if(z===o||z.nodeType===1&&z.contains&&z.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),w.disconnect();return}});w.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=w}return f}let y=!1;if(o.addEventListener("dm-form-end",h=>{const f=h.detail?.validation;if(f&&!f.valid){Z(o,f.missing),ee(f);return}y||(y=!0,r(h.detail?.values||{}).catch(()=>{}).finally(()=>{y=!1}))}),p&&F.wizard){const h=fe(t,s.title),f=h.map(k=>({title:k.title,description:k.description,fields:v(k.fields,a)})),w=h.map(k=>k.fields);let q=0,I=null,z=null;const B=k=>{const N=window.FormLogicEngine;if(!N||!N.validateFields)return;const l=N.validateFields(o,w[q]||[]);l.valid||(k.preventDefault(),k.stopImmediatePropagation(),Z(o,l.missing),ee(l))},O=()=>{const k=w[q]||t;ge(o,e),te(o,e,k),C(o,k),Q(o,k),be(o,i),e.honeypot!==!1&&b(o),I&&I.destroy(),I=u(z?.getStepData?z.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(N=>N.addEventListener("click",B,!0))};z=F.wizard(o,{schema:{steps:f},layout:d,columns:a,finishText:e.submitText||"Finish",onSubmit:r,onStepChange:k=>{oe(o,i),q=k,setTimeout(O,0)}}),o.addEventListener("dm-form-trigger",k=>{if(k.detail?.type!=="goto-step"||!z?.goToStep)return;const N=Number(k.detail.action.step);!Number.isFinite(N)||N<0||N>=f.length||N!==q&&z.goToStep(N)}),Promise.resolve(z).then(O)}else if(F.render){const h=F.render(o,v(t,a),L(t),{submitText:e.submitText||"Submit",layout:d,columns:a,onSubmit:r});Promise.resolve(h).then(function(){he(o,e,d),te(o,e,t),C(o,t),Q(o,t),e.honeypot!==!1&&b(o),u()})}})})();function Le(m,c){const x=document.body,v=x.classList,L=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),C=()=>window.matchMedia("(min-width: 993px)").matches;m.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const d=a.textContent.trim();d&&(a.setAttribute("title",d),a.querySelector("svg, [data-icon]")||(a.dataset.initial=d.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const o=()=>{const a=v.contains("dm-vnav-collapsed");n.setAttribute("aria-pressed",String(a)),n.setAttribute("aria-label",a?"Expand menu":"Collapse menu"),n.title=n.getAttribute("aria-label");const d=c?"chevrons-left":"chevrons-right",p=c?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?d:p}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{v.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!v.contains("dm-vnav-collapsed"))),o();const t=m.querySelector(".navbar-container")||m;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{v.toggle("dm-vnav-autohide",a),v.toggle("dm-vnav-collapsible",!a&&L.has("collapsible")),a?v.remove("dm-vnav-collapsed"):L.has("collapsible")&&S.get("nav_collapsed")===!0&&v.add("dm-vnav-collapsed"),m.classList.remove("dm-nav-revealed"),o()};m.addEventListener("pointerdown",a=>{v.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!m.classList.contains("dm-nav-revealed")&&(m.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{m.contains(a.target)||m.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const a=v.contains("dm-vnav-autohide"),d=v.contains("dm-vnav-collapsed"),p=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:d?"Expand menu":"Collapse to icons",icon:d?"maximize":"minimize",disabled:!C()||a||!L.has("collapsible"),action:()=>s(!d)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!C(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!p,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(L.has("autohide")),v.toggle("dm-vnav-collapsed",!L.has("autohide")&&L.has("start-collapsed")),o()}}]}})}function $e(m){m.classList.add("dm-nav-scroll-hide");let c=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const v=window.scrollY,L=m.matches(":focus-within")||m.querySelector(".navbar-dropdown.open, .navbar-collapse.show");v>c&&v>m.offsetHeight&&!L?m.classList.add("dm-nav-scrolled-away"):v<c&&m.classList.remove("dm-nav-scrolled-away"),c=v}))},{passive:!0})}function Ee(m){let c={};try{c=JSON.parse(m.dataset.contents||"{}")}catch{}const x=m.closest(".site-main"),v=document.querySelector(".site-content"),L=new Set(String(c.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),C=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(v?[...v.querySelectorAll(i)]:[]).filter(C).filter(r=>!L.has(r.textContent.trim().toLowerCase())),o=c.headings==="h2"?n("h2"):c.headings==="h3"?n("h3"):c.headings==="h2h3"?n("h2, h3"):n("h2").length?n("h2"):n("h3");if(!o.length){x?.classList.add("no-sidebar-items");return}const s=new Set([...document.querySelectorAll("[id]")].map(i=>i.id)),t=document.createElement("nav");t.className="dm-contents",t.setAttribute("aria-label",c.title||"Page contents"),c.hover&&(t.dataset.menuHover=c.hover),c.selected&&(t.dataset.menuActive=c.selected),c.accent&&t.style.setProperty("--dm-menu-highlight",c.accent.startsWith("#")?c.accent:`var(--dm-${c.accent})`),c.font&&(t.style.fontFamily=c.font),c.fontSize&&(t.style.fontSize=c.fontSize),c.fontWeight&&(t.style.fontWeight=c.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=c.title||"",e.appendChild(a),t.appendChild(e);const d=document.createElement("ol");d.className="dm-contents-list"+(c.numbered?" is-numbered":"");const p=[];for(const i of o){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let u=b;for(let y=2;s.has(u);y++)u=`${b}-${y}`;s.add(u),i.id=u}const r=document.createElement("li");r.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${i.id}`,g.textContent=i.textContent.trim(),r.appendChild(g),d.appendChild(r),p.push([i,g])}if(t.appendChild(d),m.replaceChildren(t),c.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,r=()=>{const b=p.find(([u])=>i.get(u))||null;p.forEach(([,u])=>{const y=b&&u===b[1];u.classList.toggle("is-active",!!y),y?u.setAttribute("aria-current","location"):u.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(u=>i.set(u.target,u.isIntersecting)),r()},{rootMargin:"-80px 0px -60% 0px"});p.forEach(([b])=>g.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const r=()=>{const g=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!g)),i.setAttribute("aria-label",g?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${g?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),r()}),r(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&c.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(m.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",r=>{if(r.target.closest("button"))return;const g=m.getBoundingClientRect(),b=r.clientX-g.left,u=r.clientY-g.top,y=f=>{const w=Math.max(0,Math.min(window.innerWidth-g.width,f.clientX-b)),q=Math.max(0,Math.min(window.innerHeight-40,f.clientY-u));Object.assign(m.style,{left:`${w}px`,top:`${q}px`,right:"auto",bottom:"auto"})},h=()=>{document.removeEventListener("pointermove",y),S.set("contents_position",{x:parseInt(m.style.left,10),y:parseInt(m.style.top,10)})};document.addEventListener("pointermove",y),document.addEventListener("pointerup",h,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const i=x?.classList.contains("contents-collapsible"),r=x?.classList.contains("contents-collapsed"),g=p.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:r?"Show the contents":"Collapse to a button",icon:r?"list":"minimize",action:()=>m.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!g,action:async()=>{const b=`${location.origin}${location.pathname}#${g[0].id}`;try{await _.copyToClipboard(b),E.toast("Link copied.",{type:"success"})}catch{E.toast("Could not copy the link.",{type:"error"})}}},{label:"Back to the top",icon:"arrow-up",action:()=>window.scrollTo({top:0,behavior:"smooth"})}]}}),x?.classList.contains("contents-autohide")&&(m.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!m.classList.contains("is-revealed")&&(m.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{m.contains(i.target)||m.classList.remove("is-revealed")}))}
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Write REFUSED_EXTENSIONS into public/js/form-files.mjs: the extensions /media/ would
4
+ * serve as a type no public form may collect (server/services/mediaTypes.js). The page
5
+ * refuses them as they are added; the server decides by the same rule. Run it when the
6
+ * rule or the `mime` copy @fastify/send uses changes - tests/forms/form-file-types.test.js
7
+ * fails until it is.
8
+ *
9
+ * node scripts/build-form-file-types.mjs
10
+ */
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import {fileURLToPath} from 'url';
14
+ import {refusedExtensions} from '../server/services/mediaTypes.js';
15
+
16
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
+ const FILE = path.join(ROOT, 'public', 'js', 'form-files.mjs');
18
+ export const BEGIN = '// BEGIN refused-extensions (scripts/build-form-file-types.mjs writes this block)';
19
+ export const END = '// END refused-extensions';
20
+
21
+ /** The block as the script writes it. */
22
+ export function refusedBlock(exts = refusedExtensions()) {
23
+ const lines = [];
24
+ let line = '';
25
+ for (const ext of exts) {
26
+ if (line && line.length + ext.length + 1 > 100) {
27
+ lines.push(line);
28
+ line = '';
29
+ }
30
+ line += (line ? ' ' : '') + ext;
31
+ }
32
+ if (line) lines.push(line);
33
+ return [BEGIN, 'const REFUSED_EXTENSIONS = new Set((', ...lines.map(l => ` '${l} ' +`), " '').trim().split(' '));", END].join('\n');
34
+ }
35
+
36
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
37
+ const src = fs.readFileSync(FILE, 'utf8');
38
+ const a = src.indexOf(BEGIN);
39
+ const b = src.indexOf(END);
40
+ if (a < 0 || b < a) throw new Error(`${FILE} has no refused-extensions block`);
41
+ fs.writeFileSync(FILE, src.slice(0, a) + refusedBlock() + src.slice(b + END.length));
42
+ console.log(`REFUSED_EXTENSIONS written (${refusedExtensions().length} extensions).`);
43
+ }
@@ -23,11 +23,11 @@ import {keepSubmissionsOut} from '../../services/siteGitignore.js';
23
23
  import {createRequire} from 'module';
24
24
  import {fileURLToPath} from 'url';
25
25
  import path from 'path';
26
- import {deleteForm, ensureCollectionForForm, ensureFormsDir, listForms, readForm, slugify, writeForm} from '../../services/forms.js';
26
+ import {deleteForm, ensureCollectionForForm, ensureFormsDir, listForms, maxSubmissionBytes, publicFileLimits, readForm, slugify, writeForm} from '../../services/forms.js';
27
27
  import {executeAction} from '../../services/actions.js';
28
28
  // The same rules the browser draws the buttons by (a Cancel link must be safe).
29
29
  import {buttonSettingsProblems, formClassProblems, publicButtonSettings} from '../../../public/js/form-submit.mjs';
30
- import {cleanUploadName, countRefusal, fileRefusal, fileRules, fileValueText, isFileValue} from '../../../public/js/form-files.mjs';
30
+ import {cleanUploadName, countRefusal, fileRefusal, submissionTooLarge, fileRules, fileValueText, isFileValue} from '../../../public/js/form-files.mjs';
31
31
  import {sanitiseCsv} from '../../../public/js/collection-export.mjs';
32
32
  import {originOf, trustedOrigin} from '../../services/passwordReset.js';
33
33
  import {createTransport, mailFailure, sendFormEmail, TEST_EMAIL_FAILED} from '../../services/email.js';
@@ -47,7 +47,7 @@ import {authenticate, requireAdmin, requirePermission} from '../../middleware/au
47
47
  import {hooks} from '../../services/hooks.js';
48
48
  import {pluginDenialFor} from '../../services/collectionAccess.js';
49
49
  import {deleteMedia, saveMedia} from '../../services/content.js';
50
- import {servedType} from '../../services/mediaTypes.js';
50
+ import {formMayCollect, servedType} from '../../services/mediaTypes.js';
51
51
  import {v4 as uuidv4} from 'uuid';
52
52
  import {ago, clip, plural} from '../../services/sidebarBadges.js';
53
53
  import {safeFetch} from '../../services/outbound.js';
@@ -117,18 +117,10 @@ function safeFilename(raw) {
117
117
  return `${uuidv4().slice(0, 8)}-${cleanUploadName(raw)}`;
118
118
  }
119
119
 
120
- /**
121
- * The kinds of file a form may collect, by the type /media/ will serve the stored name as:
122
- * images (not SVG), audio, video, PDF, Office and OpenDocument, plain text and CSV,
123
- * archives. A name with no known type is served as a download. Anything else - above all
124
- * anything a browser would render as a page on this site's address - is refused.
125
- */
126
- const SERVABLE = /^(image\/(?!svg)[\w.+-]+|audio\/[\w.+-]+|video\/[\w.+-]+|text\/(plain|csv)|application\/(pdf|rtf|zip|x-zip-compressed|gzip|x-gzip|x-tar|x-7z-compressed|x-rar-compressed|vnd\.rar|msword|vnd\.ms-[\w.+-]+|vnd\.openxmlformats-officedocument\.[\w.+-]+|vnd\.oasis\.opendocument\.[\w.+-]+|epub\+zip|octet-stream))$/i;
127
-
128
120
  /** Why a stored name would be served as something a form may not collect, or null. */
129
121
  function servedTypeRefusal(fieldCfg, filename) {
130
122
  const served = servedType(cleanUploadName(filename));
131
- if (!served || SERVABLE.test(served)) return null;
123
+ if (!served || formMayCollect(served)) return null;
132
124
  const label = fieldCfg.label || fieldCfg.name;
133
125
  return `"${label}" file type "${served}" cannot be uploaded here.`;
134
126
  }
@@ -148,31 +140,9 @@ function validateUpload(fieldCfg, part, size) {
148
140
  || servedTypeRefusal(fieldCfg, part.filename || '');
149
141
  }
150
142
 
151
- /** What one submission's files may hold in memory together when server.json sets nothing: 50 MB. */
152
- export const DEFAULT_MAX_SUBMISSION_BYTES = 50 * 1024 * 1024;
153
-
154
- /**
155
- * The most bytes one submission's files may hold in memory, all fields together:
156
- * server.json `uploads.maxSubmissionBytes` (read fresh) when set; otherwise 50 MB or
157
- * `uploads.maxFileSize`, whichever is larger, so one file the site takes is never
158
- * refused for the total.
159
- *
160
- * @returns {number}
161
- */
162
- export function maxSubmissionBytes() {
163
- let uploads = {};
164
- try { uploads = getConfig('server')?.uploads || {}; } catch { /* no server.json */ }
165
- const set = Number(uploads.maxSubmissionBytes);
166
- if (set > 0) return set;
167
- return Math.max(DEFAULT_MAX_SUBMISSION_BYTES, Number(uploads.maxFileSize) || 0);
168
- }
169
-
170
- /** The refusal for a submission whose files together pass maxSubmissionBytes(). */
171
- export function submissionTooLarge(limit) {
172
- const mb = limit / (1024 * 1024);
173
- const size = mb >= 1 ? `${Math.round(mb * 10) / 10} MB` : `${Math.round(limit / 1024)} KB`;
174
- return `The files sent are too large together (at most ${size} in one submission). Send fewer or smaller files.`;
175
- }
143
+ // The per-submission cap lives in services/forms.js (the page is told it too), its words in form-files.mjs.
144
+ export {DEFAULT_MAX_SUBMISSION_BYTES, maxSubmissionBytes} from '../../services/forms.js';
145
+ export {submissionTooLarge};
176
146
 
177
147
  /**
178
148
  * Drain a multipart request into a JSON-shaped body. Text parts become string
@@ -297,7 +267,7 @@ async function saveFormFiles(pending, body, keep) {
297
267
  }
298
268
  const refs = Array.isArray(body[name]) ? body[name] : [body[name]];
299
269
  for (let i = 0; i < files.length; i++) {
300
- const saved = await saveMedia(safeFilename(files[i].filename), files[i].buffer);
270
+ const saved = await saveNewMedia(files[i]);
301
271
  written.push(saved.name);
302
272
  refs[i].url = saved.url;
303
273
  }
@@ -309,6 +279,20 @@ async function saveFormFiles(pending, body, keep) {
309
279
  return written;
310
280
  }
311
281
 
282
+ /**
283
+ * Write one visitor file under a new name. Never over another file: a clash of the random
284
+ * prefix picks another, and a write that fails part way removes what it wrote.
285
+ */
286
+ async function saveNewMedia(file) {
287
+ for (let tries = 0; ; tries++) {
288
+ try {
289
+ return await saveMedia(safeFilename(file.filename), file.buffer, {exclusive: true});
290
+ } catch (err) {
291
+ if (err.code !== 'EEXIST' || tries >= 4) throw err;
292
+ }
293
+ }
294
+ }
295
+
312
296
  /** Remove a submission's written files again (it was not stored after all). Never throws. */
313
297
  async function removeFormFiles(names) {
314
298
  for (const name of names || []) {
@@ -660,7 +644,7 @@ export async function formsRoutes(fastify) {
660
644
  // A Cancel link saved before the check (or written by hand) is
661
645
  // not handed to the page.
662
646
  if (safe.settings) safe.settings = publicButtonSettings(safe.settings);
663
- return safe;
647
+ return publicFileLimits(safe);
664
648
  } catch {
665
649
  return reply.status(404).send({ error: 'Form not found.' });
666
650
  }
@@ -414,12 +414,23 @@ export async function listMedia() {
414
414
  *
415
415
  * @param {string} filename
416
416
  * @param {Buffer} buffer
417
+ * @param {{exclusive?: boolean}} [opts] - exclusive: refuse (EEXIST) rather than replace a file
417
418
  * @returns {Promise<object>}
418
419
  */
419
- export async function saveMedia(filename, buffer) {
420
+ export async function saveMedia(filename, buffer, {exclusive = false} = {}) {
420
421
  await fs.mkdir(MEDIA_DIR, { recursive: true });
421
422
  const filePath = path.join(MEDIA_DIR, filename);
422
- await fs.writeFile(filePath, buffer);
423
+ if (exclusive) {
424
+ // Only ever a new file, so one that fails part way (a full disk) is ours to remove.
425
+ try {
426
+ await fs.writeFile(filePath, buffer, {flag: 'wx'});
427
+ } catch (err) {
428
+ if (err.code !== 'EEXIST') await fs.rm(filePath, {force: true}).catch(() => {});
429
+ throw err;
430
+ }
431
+ } else {
432
+ await fs.writeFile(filePath, buffer);
433
+ }
423
434
  hooks.emit('content:mediaUploaded', {filename});
424
435
  return { name: filename, url: `/media/${filename}` };
425
436
  }
@@ -4,11 +4,85 @@
4
4
  * Submissions are stored exclusively in Collections (slug matching form slug).
5
5
  */
6
6
  import fs from 'fs/promises';
7
+ import {watch as fsWatch} from 'fs';
7
8
  import path from 'path';
8
9
  import {fileURLToPath} from 'url';
9
10
  import * as cache from './cache/index.js';
10
11
  import {buttonSettingsProblems, formClassProblems} from '../../public/js/form-submit.mjs';
11
12
  import {fileIn} from './safePath.js';
13
+ import {getConfig} from '../config.js';
14
+
15
+ /** What one submission's files may hold in memory together when server.json sets nothing: 50 MB. */
16
+ export const DEFAULT_MAX_SUBMISSION_BYTES = 50 * 1024 * 1024;
17
+
18
+ /**
19
+ * The most bytes one submission's files may hold in memory, all fields together:
20
+ * server.json `uploads.maxSubmissionBytes` (read fresh) when set; otherwise 50 MB or
21
+ * `uploads.maxFileSize`, whichever is larger, so one file the site takes is never
22
+ * refused for the total.
23
+ *
24
+ * @returns {number}
25
+ */
26
+ export function maxSubmissionBytes() {
27
+ let uploads = {};
28
+ try { uploads = getConfig('server')?.uploads || {}; } catch { /* no server.json */ }
29
+ const set = Number(uploads.maxSubmissionBytes);
30
+ if (set > 0) return set;
31
+ return Math.max(DEFAULT_MAX_SUBMISSION_BYTES, Number(uploads.maxFileSize) || 0);
32
+ }
33
+
34
+ /**
35
+ * A form as a page draws it, told the per-submission file cap when it has a file field,
36
+ * so the page refuses a file that would pass it before anything is sent.
37
+ *
38
+ * @param {object} form
39
+ * @returns {object}
40
+ */
41
+ export function publicFileLimits(form) {
42
+ const hasFiles = (form?.fields || []).some(f => f && f.type === 'file');
43
+ if (!hasFiles) return form;
44
+ const cap = maxSubmissionBytes();
45
+ watchFileLimits(cap);
46
+ return {...form, fileLimits: {maxTotalBytes: cap}};
47
+ }
48
+
49
+ /** The cache tag on every page that drew a form's file cap (markdown.js adds it). */
50
+ export const FILE_LIMITS_TAG = 'forms:file-limits';
51
+
52
+ let limitsWatch = null; // {cap, watcher} once a page has drawn a cap; cap = the last one seen in server.json
53
+
54
+ /**
55
+ * Keep cached pages' file cap in step with server.json. Nothing in the admin writes that
56
+ * file, so a change there (by hand, or by the Manager) has no save to hang an
57
+ * invalidation on: watch the config folder, and when the cap changes, drop the pages that
58
+ * carry it. Only the watcher moves `cap` - a page drawn with the new cap before the event
59
+ * arrives must not hide the change from the pages cached with the old one. The folder,
60
+ * not the file: an editor's save replaces the file, which ends a watch on it. A watch that
61
+ * ends (the folder itself replaced) is armed again by the next page that draws a cap.
62
+ *
63
+ * @param {number} cap - the cap just drawn
64
+ */
65
+ function watchFileLimits(cap) {
66
+ if (limitsWatch) return;
67
+ limitsWatch = {cap, watcher: null};
68
+ try {
69
+ const watcher = fsWatch(path.resolve('config'), (_event, name) => {
70
+ if (name && String(name) !== 'server.json') return;
71
+ const now = maxSubmissionBytes();
72
+ if (now === limitsWatch?.cap) return;
73
+ if (limitsWatch) limitsWatch.cap = now;
74
+ cache.invalidateTags([FILE_LIMITS_TAG]).catch(() => {});
75
+ });
76
+ const disarm = () => {
77
+ if (limitsWatch?.watcher === watcher) limitsWatch = null;
78
+ try { watcher.close(); } catch { /* closed */ }
79
+ };
80
+ watcher.on('error', disarm);
81
+ watcher.on('close', () => { if (limitsWatch?.watcher === watcher) limitsWatch = null; });
82
+ watcher.unref?.();
83
+ limitsWatch.watcher = watcher;
84
+ } catch { /* no watching here: a restart or a form save still redraws */ }
85
+ }
12
86
 
13
87
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
88
  const ROOT = path.resolve(__dirname, '..', '..');
@@ -44,6 +118,11 @@ export async function readForm(slug) {
44
118
  */
45
119
  export async function writeForm(slug, data) {
46
120
  await ensureFormsDir();
121
+ // fileLimits is the server's config, added to the copy a page is given - never stored
122
+ if (data && 'fileLimits' in data) {
123
+ const {fileLimits: _limits, ...rest} = data;
124
+ data = rest;
125
+ }
47
126
  const file = fileIn(FORMS_DIR, slug, '.json');
48
127
  await fs.writeFile(file, JSON.stringify(data, null, 4) + '\n', 'utf8');
49
128
  await cache.invalidateTags([`form:${slug}`]);
@@ -23,6 +23,7 @@ import {buildTextStyle} from '../../public/js/text-style.mjs';
23
23
  import {buildScribeStyle} from '../../public/js/scribe-style.mjs';
24
24
  import {CHARACTERS, resolveCharacterSize} from '../../public/js/celebrations/character-catalog.mjs';
25
25
  import {publicButtonSettings} from '../../public/js/form-submit.mjs';
26
+ import {FILE_LIMITS_TAG, publicFileLimits} from './forms.js';
26
27
  import {applyShortcodeExpiry} from './shortcodeExpiry.js';
27
28
 
28
29
  const __dirname_md = path.dirname(fileURLToPath(import.meta.url));
@@ -4461,7 +4462,9 @@ async function processFormBlocks(markdown, tagSet) {
4461
4462
  const {actions: _actions, ...publicForm} = form;
4462
4463
  // A Cancel link that is not safe to draw stays out of the page source.
4463
4464
  if (publicForm.settings) publicForm.settings = publicButtonSettings(publicForm.settings);
4464
- const encoded = Buffer.from(JSON.stringify(publicForm)).toString('base64');
4465
+ const drawn = publicFileLimits(publicForm);
4466
+ if (drawn.fileLimits) tagSet?.add(FILE_LIMITS_TAG);
4467
+ const encoded = Buffer.from(JSON.stringify(drawn)).toString('base64');
4465
4468
  const extraClass = attrs.class ? ` ${escapeAttr(attrs.class)}` : '';
4466
4469
  const idAttr = attrs.id ? ` id="${escapeAttr(attrs.id)}"` : '';
4467
4470
  replacement = `<div class="cms-form-embed${extraClass}" data-form-inline="${escapeAttr(encoded)}"${idAttr}>${formSummaryHtml(form)}</div>`;
@@ -20,3 +20,25 @@ export function servedType(name) {
20
20
  export function isActiveType(type) {
21
21
  return /^(text\/html|text\/xml|application\/xml|image\/svg)|\+xml$|javascript|ecmascript/i.test(String(type || ''));
22
22
  }
23
+
24
+ /**
25
+ * The kinds of file a form may collect, by the type /media/ will serve the stored name as:
26
+ * images (not SVG), audio, video, PDF, Office and OpenDocument, plain text and CSV,
27
+ * archives. A name with no known type is served as a download. Anything else - above all
28
+ * anything a browser would render as a page on this site's address - is refused.
29
+ */
30
+ const FORM_SERVABLE = /^(image\/(?!svg)[\w.+-]+|audio\/[\w.+-]+|video\/[\w.+-]+|text\/(plain|csv)|application\/(pdf|rtf|zip|x-zip-compressed|gzip|x-gzip|x-tar|x-7z-compressed|x-rar-compressed|vnd\.rar|msword|vnd\.ms-[\w.+-]+|vnd\.openxmlformats-officedocument\.[\w.+-]+|vnd\.oasis\.opendocument\.[\w.+-]+|epub\+zip|octet-stream))$/i;
31
+
32
+ /** Whether a public form may collect a file /media/ would serve as this type. */
33
+ export function formMayCollect(type) {
34
+ return FORM_SERVABLE.test(String(type || ''));
35
+ }
36
+
37
+ /**
38
+ * The extensions /media/ would serve as a type no form may collect, sorted - what
39
+ * public/js/form-files.mjs carries as REFUSED_EXTENSIONS so the page refuses them as they
40
+ * are added (scripts/build-form-file-types.mjs writes it; a test checks it).
41
+ */
42
+ export function refusedExtensions() {
43
+ return Object.entries(mime._types).filter(([, type]) => !formMayCollect(type)).map(([ext]) => ext).sort();
44
+ }
@@ -8,6 +8,7 @@ import {fileURLToPath} from 'url';
8
8
  import {getConfig} from '../config.js';
9
9
  import {getInjectionSnippets} from './plugins.js';
10
10
  import {applyTransforms} from './hooks.js';
11
+ import {FILE_LIMITS_TAG} from './forms.js';
11
12
  import {contentsPaint, readContentsOverride, resolveContents} from './pageContents.js';
12
13
  import {applyMenuOverride, hasThemeSwitcherItem, resolveLocation, resolveMenuDecorations, resolveOverlaysForPage} from './menus.js';
13
14
  import {buildMenuNav} from './menuRender.js';
@@ -172,7 +173,13 @@ export function footerBlockNames(site) {
172
173
  * and expiring shortcodes are never run - those stages come before [block] - so
173
174
  * the block names are the whole of what a footer depends on beyond `site`.)
174
175
  */
175
- export const footerBlockCacheTags = (site) => footerBlockNames(site).map(n => `block:${n}`);
176
+ // A footer block's own shortcode tags are not carried out of its render, and a [form] in
177
+ // one draws the file cap: every page with footer blocks also carries the cap's tag (it is
178
+ // invalidated only when server.json changes the cap).
179
+ export const footerBlockCacheTags = (site) => {
180
+ const names = footerBlockNames(site);
181
+ return names.length ? [...names.map(n => `block:${n}`), FILE_LIMITS_TAG] : [];
182
+ };
176
183
 
177
184
  /**
178
185
  * The blocks in the footer (site.json footer.blocks - Site Settings > Footer,
@@ -185,7 +185,7 @@
185
185
  <script src="/public/js/form-logic-engine.js?v=20261025-dropzone-disable" defer></script>
186
186
 
187
187
  <!-- Site initialisation -->
188
- <script src="/public/js/site.js?v=20261025-dropzone-range" type="module"></script>
188
+ <script src="/public/js/site.js?v=20261025-files-total" type="module"></script>
189
189
  <script src="/public/js/collection-context.js?v=20261010-utf8-payloads" type="module"></script>
190
190
  {{ctxMenusModule}}
191
191
 
@@ -195,7 +195,7 @@
195
195
  <script src="/public/js/menu-float.js?v=20260927-float" type="module"></script>
196
196
 
197
197
  <!-- Core Forms renderer -->
198
- <script src="/public/js/forms.js?v=20261025-form-dropzone" type="module"></script>
198
+ <script src="/public/js/forms.js?v=20261025-forms-files-total" type="module"></script>
199
199
 
200
200
  <!-- Core effects runtime -->
201
201
  <script src="/public/js/effects.js?v=20261015-occasions" defer></script>