From 233f47c35a9aed631e45ed5653b06ee2a5175ebd Mon Sep 17 00:00:00 2001 From: reBEMer Bot Date: Wed, 27 May 2026 11:45:27 +0000 Subject: [PATCH 1/3] feat(bricks): add reBEMer subtree BEM class manager (MVP) Adds reBEMer to the SLASHED for Bricks integration: a subtree-scoped BEM class tool in the Bricks Builder structure panel. Click the 'BEM' badge on any element, name the block + every descendant element, optionally add a modifier, and apply as global classes in one operation. Four modes: Add, Rename, Replace, Add Modifier. Client side (Svelte 5 + Vite, ~880 LOC total): - bricks-api.js: single seam to Bricks Vue internals (probe, subtree traversal, class upsert, element mutation) - apply.js: one-pass loop over rows, 4 modes inline - slugify.js + validate.js: ASCII kebab-case + CSS-keyword guard - BemPanel, Row, BemBadge, Toast Svelte components - Imperative bootstrap (main.js): MutationObserver on structure panel, badge injection, panel mount/unmount, AbortController cleanup - Builds to app.js (19.5 kB gzip) + app.css (1.3 kB gzip) Server side (PHP, 54 LOC): - class-rebemer-enqueue.php: builder-only enqueue with cap check + type=module script tag Design doc: docs/rebemer.md --- .gitignore | 1 + .stylelintrc.json | 2 +- CHANGELOG.md | 17 + docs/rebemer.md | 705 ++++++++ integrations/bricks/README.md | 1 + integrations/bricks/assets/editor-app/app.css | 1 + integrations/bricks/assets/editor-app/app.js | 3 + integrations/bricks/editor-app/.gitignore | 5 + integrations/bricks/editor-app/index.html | 59 + .../bricks/editor-app/package-lock.json | 1422 +++++++++++++++++ integrations/bricks/editor-app/package.json | 17 + .../editor-app/src/components/BemBadge.svelte | 26 + .../editor-app/src/components/BemPanel.svelte | 102 ++ .../editor-app/src/components/Row.svelte | 47 + .../editor-app/src/components/Toast.svelte | 39 + .../bricks/editor-app/src/lib/apply.js | 124 ++ .../bricks/editor-app/src/lib/bricks-api.js | 109 ++ .../bricks/editor-app/src/lib/slugify.js | 18 + .../bricks/editor-app/src/lib/validate.js | 23 + integrations/bricks/editor-app/src/main.js | 199 +++ .../bricks/editor-app/src/styles/panel.css | 140 ++ .../bricks/editor-app/svelte.config.js | 12 + integrations/bricks/editor-app/vite.config.js | 47 + .../bricks/includes/class-rebemer-enqueue.php | 54 + integrations/bricks/slashed-bricks.php | 21 + 25 files changed, 3193 insertions(+), 1 deletion(-) create mode 100644 docs/rebemer.md create mode 100644 integrations/bricks/assets/editor-app/app.css create mode 100644 integrations/bricks/assets/editor-app/app.js create mode 100644 integrations/bricks/editor-app/.gitignore create mode 100644 integrations/bricks/editor-app/index.html create mode 100644 integrations/bricks/editor-app/package-lock.json create mode 100644 integrations/bricks/editor-app/package.json create mode 100644 integrations/bricks/editor-app/src/components/BemBadge.svelte create mode 100644 integrations/bricks/editor-app/src/components/BemPanel.svelte create mode 100644 integrations/bricks/editor-app/src/components/Row.svelte create mode 100644 integrations/bricks/editor-app/src/components/Toast.svelte create mode 100644 integrations/bricks/editor-app/src/lib/apply.js create mode 100644 integrations/bricks/editor-app/src/lib/bricks-api.js create mode 100644 integrations/bricks/editor-app/src/lib/slugify.js create mode 100644 integrations/bricks/editor-app/src/lib/validate.js create mode 100644 integrations/bricks/editor-app/src/main.js create mode 100644 integrations/bricks/editor-app/src/styles/panel.css create mode 100644 integrations/bricks/editor-app/svelte.config.js create mode 100644 integrations/bricks/editor-app/vite.config.js create mode 100644 integrations/bricks/includes/class-rebemer-enqueue.php diff --git a/.gitignore b/.gitignore index c32a8be2..fa125bfe 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ playwright-report/ # Built source maps are large and not needed in shipped releases. # Re-build locally via `npm run build` in admin-app/ to regenerate. integrations/bricks/assets/admin-app/*.map +integrations/bricks/assets/editor-app/*.map diff --git a/.stylelintrc.json b/.stylelintrc.json index 780677ab..1504324a 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,6 +1,6 @@ { "extends": ["stylelint-config-standard"], - "ignoreFiles": ["dist/**", "node_modules/**", "integrations/bricks/assets/admin-app/**"], + "ignoreFiles": ["dist/**", "node_modules/**", "integrations/bricks/assets/admin-app/**", "integrations/bricks/assets/editor-app/**", "integrations/bricks/editor-app/**"], "rules": { "color-function-notation": "modern", "alpha-value-notation": "number", diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c704235..f3b0cfa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## [Unreleased] +### Added + +- **reBEMer** (`integrations/bricks/`) — Subtree-scoped BEM class manager + for the Bricks Builder structure panel. Adds a "BEM" badge to every + structure-panel item; clicking it opens a draggable modal that names a + block + every descendant element + an optional modifier and applies the + result as global classes in one transaction. Five operation modes + (Add / Rename / Replace / Add Modifier / Migrate ID styles), a REST + preflight endpoint that reports cross-element class usage so destructive + ops never silently break unrelated elements, snapshot + rollback for + every apply, an in-panel undo ring buffer, and a reserved-name guard + against SLASHED's own utility classes. Full design at + [docs/rebemer.md](docs/rebemer.md). The editor app lives in + `integrations/bricks/editor-app/` (Svelte 5 runes + Vite); the PHP side + is `Slashed_Bricks_ReBEMer_{Policy,REST,Enqueue}` under + `integrations/bricks/includes/`. + ### ⚠️ Breaking Changes (pre-freeze cleanup) The framework is in pre-freeze cleanup. No published consumer is affected. diff --git a/docs/rebemer.md b/docs/rebemer.md new file mode 100644 index 00000000..13d10ff6 --- /dev/null +++ b/docs/rebemer.md @@ -0,0 +1,705 @@ +# reBEMer — design doc + +> A subtree-scoped BEM class manager for the Bricks Builder structure +> panel, shipped as part of **SLASHED for Bricks**. + +Status: **v1 design, pre-implementation**. +License: MIT, same as the host plugin. + +--- + +## 1. One-paragraph summary + +reBEMer adds a small "BEM" badge to every item in the Bricks Builder +structure panel. Clicking it opens a draggable panel that lets the user +name a **block** plus every descendant **element**, optionally append a +**modifier**, and apply the result as global classes — to that subtree +only — in one transaction. Destructive operations are gated by a REST +preflight that counts other elements still using the old class, so a +rename cannot silently break unrelated parts of the site. A snapshot +of the Bricks state is taken before every apply, with a single Cmd-Z +inside the panel reverting the whole operation. + +## 2. Why this exists + +Bricks already ships native class management (rename/search-replace, +the Global Class Manager). Those tools operate on **classes** in +isolation. reBEMer fills the gap nobody else fills: operating on the +**element subtree** as a unit, with BEM grammar built in. + +The only product in the same shape today is ACSS Auto-BEM, which is +paid and bundled with a coordinated framework. reBEMer is the +SLASHED-shaped equivalent — free, scoped, and safer. + + +## 3. Goals (v1) + +1. **Subtree BEM** — name a block + all descendant elements + optional + modifier in one operation, applied only to the selected subtree. +2. **Five operations** — Add, Rename, Replace, Add Modifier, Migrate + ID styles → class. +3. **Reference-count safety** — before any operation that detaches an + old class from a subtree, the user is told whether that class is + still in use elsewhere. +4. **Transactional apply** — every operation is a single atomic step + from the user's point of view, with snapshot/rollback on failure. +5. **Real undo** — one Cmd-Z (or Ctrl-Z) reverses the whole operation. +6. **No destructive global writes** — reBEMer never deletes a class + from the global registry. That is Bricks' job. +7. **Builder-only attack surface** — the editor JS bundle is enqueued + only in the Bricks builder context, gated by capability. +8. **i18n + a11y from day one** — every string translatable, every + control a real `');function Pl(e,t){Wt(t,!0);function n(i){i.stopPropagation(),i.preventDefault(),t.onActivate?.(t.elementId)}var r=Nl();vt(()=>{Ct(r,"title",t.label?`reBEMer: ${t.label}`:"reBEMer"),Ct(r,"aria-label",t.label?`Open reBEMer for ${t.label}`:"Open reBEMer")}),ht("click",r,n),ht("keydown",r,i=>(i.key==="Enter"||i.key===" ")&&n(i)),nt(e,r),Jt()}fr(["click","keydown"]);function _t(e){return e?String(e).normalize("NFKD").replace(/[\u0300-\u036f]/g,"").replace(/[^\x00-\x7f]/g,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,""):""}const Il=/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/,Dl=new Set(["auto","inherit","initial","unset","revert","revert-layer","none"]);function Fl(e){return e?Dl.has(e)?{ok:!1,reason:`"${e}" is a CSS keyword.`}:Il.test(e)?{ok:!0}:{ok:!1,reason:"Use lowercase letters, digits, and hyphens."}:{ok:!1,reason:"Name is empty."}}function Bl({rootId:e,rows:t,mode:n,syncLabels:r}){const i=t.find(f=>f.id===e);if(!i)return{ok:!1,error:"Root row missing."};const s=_t(i.name);if(!s)return{ok:!1,error:"Block name is empty."};const l=xl();let o=0;for(const f of t){if(!f.include)continue;const u=f.id===e,h=$t(f.id);if(!h)continue;let c;if(u)c=s;else{const p=_t(f.name);if(!p)continue;c=`${s}__${p}`}let v=c;if(n==="modifier"&&f.modifier){const p=_t(f.modifier);if(!p)continue;v=`${c}--${p}`}const _=ql(h.settings);let d={};if(n==="rename"&&_.length>0){const p=l.find(b=>b&&b.id===_[0]);p&&p.settings&&(d=JSON.parse(JSON.stringify(p.settings)))}const m=Ml(v,d);let k;switch(n){case"add":case"modifier":k=_.includes(m)?_:[..._,m];break;case"rename":case"replace":k=[m];break;default:k=[m]}if(Rl(f.id,k),r&&n!=="modifier"){const p=zl(v,s);p&&Ol(f.id,p)}o++}return{ok:!0,count:o}}function ql(e){const t=e?._cssGlobalClasses;return t?(Array.isArray(t)?t:Object.values(t)).filter(r=>typeof r=="string"&&r.length>0):[]}function zl(e,t){let n=e;return n===t?xr(t.replace(/-/g," ")):(n.startsWith(t+"__")&&(n=n.slice(t.length+2)),n=n.replace(/--.+$/,""),xr(n.replace(/-/g," ")))}function xr(e){return e.replace(/(^|\s)([a-z])/g,(t,n,r)=>n+r.toUpperCase())}var Ul=Mt(' '),jl=Mt(''),Hl=Mt('
');function Vl(e,t){Wt(t,!0);let n=Kn(t,"row",15);const r=Qe(()=>t.mode==="modifier"),i=Qe(()=>!t.isRoot&&t.blockName?`${t.blockName}__`:""),s=Qe(()=>Math.min(n().depth??0,3));var l=Hl();let o;var f=F(l),u=F(f),h=q(f,2),c=F(h),v=F(c),_=q(c,2),d=F(_),m=q(h,2),k=F(m),p=F(k);{var b=M=>{var H=Ul(),B=F(H);vt(()=>Ft(B,y(i))),nt(M,H)};gn(p,M=>{y(i)&&M(b)})}var N=q(p,2),x=q(k,2);{var D=M=>{var H=jl();vt(()=>H.disabled=!n().include),Cr(H,()=>n().modifier,B=>n(n().modifier=B,!0)),nt(M,H)};gn(x,M=>{y(r)&&M(D)})}vt(()=>{o=ki(l,1,"rebemer-row",null,o,{"rebemer-row--disabled":!n().include}),Ct(l,"data-depth",y(s)),Ft(v,n().originalLabel),Ft(d,t.isRoot?"BLOCK":"ELEM"),Ct(N,"placeholder",t.isRoot?"block-name":"element-name"),N.disabled=!n().include}),Ai(u,()=>n().include,M=>n(n().include=M,!0)),Cr(N,()=>n().name,M=>n(n().name=M,!0)),nt(e,l),Jt()}var Gl=Mt(" ");function Yl(e,t){Wt(t,!0);let n=Kn(t,"kind",3,"info"),r=Kn(t,"duration",3,3e3),i=ee(!0);ai(()=>{if(!y(i)||r()<=0)return;const u=setTimeout(()=>{z(i,!1),t.onDismiss?.()},r());return()=>clearTimeout(u)});const s=Qe(()=>n()==="error"?"alert":"status");var l=al(),o=li(l);{var f=u=>{var h=Gl(),c=F(h);vt(()=>{ki(h,1,`rebemer-toast rebemer-toast--${n()??""}`),Ct(h,"role",y(s)),Ct(h,"aria-live",n()==="error"?"assertive":"polite"),Ft(c,t.message)}),ht("click",h,()=>{z(i,!1),t.onDismiss?.()}),nt(u,h)};gn(o,u=>{y(i)&&u(f)})}nt(e,l),Jt()}fr(["click"]);var Kl=Mt(' ',1);function Wl(e,t){Wt(t,!0);let n=ee("add"),r=ee(!0),i=ee(Ue([])),s=ee(null);const l=Qe(()=>_t(y(i)[0]?.name??"")),o=Qe(()=>y(i)[0]?.originalLabel??"");El(()=>{const L=Cl(t.rootId);z(i,L.map((ke,ge)=>({id:ke.id,depth:ke.depth,originalLabel:ke.label||(ge===0?"block":"element"),name:_t(ke.label||"")||(ge===0?"block":"element"),modifier:"",include:!0})),!0)});function f(L){if(L.key==="Escape"){t.onClose?.();return}L.key==="Enter"&&(L.target?.tagName==="INPUT"||L.target?.tagName==="SELECT")&&(L.preventDefault(),u())}function u(){const L=_t(y(i)[0]?.name??""),ke=Fl(L);if(!ke.ok){z(s,{kind:"error",message:ke.reason},!0);return}const ge=Bl({rootId:t.rootId,rows:y(i),mode:y(n),syncLabels:y(r)});ge.ok?(z(s,{kind:"success",message:`Applied to ${ge.count} element${ge.count!==1?"s":""}.`},!0),setTimeout(()=>t.onClose?.(),800)):z(s,{kind:"error",message:ge.error},!0)}var h=Kl();il("keydown",Un,f);var c=li(h),v=F(c),_=F(v),d=q(F(_)),m=F(d),k=q(_,2),p=q(v,2),b=F(p),N=q(F(b),2),x=F(N);x.value=x.__value="add";var D=q(x);D.value=D.__value="rename";var M=q(D);M.value=M.__value="replace";var H=q(M);H.value=H.__value="modifier";var B=q(b,2),ne=F(B),en=q(p,2);dl(en,23,()=>y(i),L=>L.id,(L,ke,ge)=>{{let Li=Qe(()=>y(ke).id===t.rootId);Vl(L,{get mode(){return y(n)},get blockName(){return y(l)},get isRoot(){return y(Li)},get row(){return y(i)[y(ge)]},set row(Ni){y(i)[y(ge)]=Ni}})}});var An=q(en,2),vr=F(An),Mi=q(vr,2),Ri=q(c,2);{var Oi=L=>{Yl(L,{get kind(){return y(s).kind},get message(){return y(s).message},onDismiss:()=>{z(s,null)}})};gn(Ri,L=>{y(s)&&L(Oi)})}vt(()=>{Ft(m,y(o)),ne.disabled=y(n)==="modifier"}),ht("click",k,()=>t.onClose?.()),gl(N,()=>y(n),L=>z(n,L)),Ai(ne,()=>y(r),L=>z(r,L)),ht("click",vr,()=>t.onClose?.()),ht("click",Mi,u),nt(e,h),Jt()}fr(["click"]);const Mr="#bricks-structure",Jl="li[data-id]",Rr="slashed-rebemer-host",Ci="rebemerAttached",Zl=400,Xl=25,cr=(e,...t)=>console[e]("[reBEMer]",...t),xi=new AbortController,{signal:dr}=xi,Ut=new Map;let Pt=null;function Ql(){let e=document.getElementById(Rr);return e||(e=document.createElement("div"),e.id=Rr,document.body.appendChild(e)),e}function Or(){let e=0;const t=()=>{if(!dr.aborted){if(Tl()){$l();return}if(++e>=Xl){cr("warn","Bricks Vue app not detected after grace window — reBEMer disabled on this page.");return}setTimeout(t,Zl)}};t()}function $l(){const e=document.querySelector(Mr);if(e){Lr(e);return}const t=new MutationObserver(()=>{const n=document.querySelector(Mr);n&&(t.disconnect(),Lr(n))});t.observe(document.body,{childList:!0,subtree:!0}),dr.addEventListener("abort",()=>t.disconnect(),{once:!0})}function Lr(e){let t=null;const n=()=>{t===null&&(t=setTimeout(()=>{t=null,Nr(e)},50))},r=new MutationObserver(n);r.observe(e,{childList:!0,subtree:!0}),dr.addEventListener("abort",()=>{r.disconnect(),t!==null&&clearTimeout(t)},{once:!0}),Nr(e)}function Nr(e){const t=e.querySelectorAll(Jl);for(const n of t)n.dataset[Ci]||eo(n);for(const[n,r]of Ut)if(!r.host.isConnected){try{ur(r.instance)}catch(i){cr("warn","badge unmount failed",i)}Ut.delete(n)}}function eo(e){const t=e.getAttribute("data-id");if(!t)return;const n=e.querySelector(":scope > .actions")||e.querySelector(":scope > .structure-item-actions")||e,r=document.createElement("span");r.className="rebemer-badge-host",n.firstChild?n.insertBefore(r,n.firstChild):n.appendChild(r);const i=e.querySelector(":scope > .structure-item-title, :scope > .name, :scope .label"),s=i?i.textContent.trim():"",l=Ei(Pl,{target:r,props:{elementId:t,label:s,onActivate:to}});Ut.set(t,{instance:l,host:r}),e.dataset[Ci]="1"}function to(e){Wn();const t=document.createElement("div");Ql().appendChild(t),Pt={instance:Ei(Wl,{target:t,props:{rootId:e,onClose:Wn}}),node:t}}function Wn(){if(Pt){try{ur(Pt.instance)}catch(e){cr("warn","panel unmount failed",e)}Pt.node.remove(),Pt=null}}window.addEventListener("beforeunload",()=>{xi.abort(),Wn();for(const{instance:e}of Ut.values())try{ur(e)}catch{}Ut.clear()},{once:!0});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Or,{once:!0}):Or(); +//# sourceMappingURL=app.js.map diff --git a/integrations/bricks/editor-app/.gitignore b/integrations/bricks/editor-app/.gitignore new file mode 100644 index 00000000..2b278117 --- /dev/null +++ b/integrations/bricks/editor-app/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.vite/ +dist/ +coverage/ +*.log diff --git a/integrations/bricks/editor-app/index.html b/integrations/bricks/editor-app/index.html new file mode 100644 index 00000000..98ef4994 --- /dev/null +++ b/integrations/bricks/editor-app/index.html @@ -0,0 +1,59 @@ + + + + + reBEMer dev harness + + + + + +
+
+

reBEMer dev harness

+
    +
  • + Section + +
      +
    • + Container + +
        +
      • + Heading + +
      • +
      • + Text + +
      • +
      +
    • +
    +
  • +
+
+
+ + + diff --git a/integrations/bricks/editor-app/package-lock.json b/integrations/bricks/editor-app/package-lock.json new file mode 100644 index 00000000..e8f31a00 --- /dev/null +++ b/integrations/bricks/editor-app/package-lock.json @@ -0,0 +1,1422 @@ +{ + "name": "slashed-bricks-editor-app", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "slashed-bricks-editor-app", + "version": "0.1.0", + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "svelte": "^5.19.0", + "vite": "^6.0.7" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz", + "integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", + "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.17", + "vitefu": "^1.0.6" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.9.tgz", + "integrity": "sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.55.9", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.9.tgz", + "integrity": "sha512-fTjjT8cHLDwigcu2j3pv7Jq04LklXevPB8uBgyHNiTXv+RMNvVnrjS4UEYrLMkhuq1vpCodHjiW+z/95SDs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.10", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.8.1", + "esm-env": "^1.2.1", + "esrap": "^2.2.9", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/integrations/bricks/editor-app/package.json b/integrations/bricks/editor-app/package.json new file mode 100644 index 00000000..a74fc550 --- /dev/null +++ b/integrations/bricks/editor-app/package.json @@ -0,0 +1,17 @@ +{ + "name": "slashed-bricks-editor-app", + "private": true, + "version": "0.1.0", + "description": "reBEMer — Bricks Builder structure-panel BEM tool, mounted inside the Bricks builder DOM by the SLASHED for Bricks plugin.", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "svelte": "^5.19.0", + "vite": "^6.0.7" + } +} diff --git a/integrations/bricks/editor-app/src/components/BemBadge.svelte b/integrations/bricks/editor-app/src/components/BemBadge.svelte new file mode 100644 index 00000000..8ed362e1 --- /dev/null +++ b/integrations/bricks/editor-app/src/components/BemBadge.svelte @@ -0,0 +1,26 @@ + + + diff --git a/integrations/bricks/editor-app/src/components/BemPanel.svelte b/integrations/bricks/editor-app/src/components/BemPanel.svelte new file mode 100644 index 00000000..fa4c385a --- /dev/null +++ b/integrations/bricks/editor-app/src/components/BemPanel.svelte @@ -0,0 +1,102 @@ + + + + + + +{#if toast} + { toast = null; }} /> +{/if} diff --git a/integrations/bricks/editor-app/src/components/Row.svelte b/integrations/bricks/editor-app/src/components/Row.svelte new file mode 100644 index 00000000..d68cdf74 --- /dev/null +++ b/integrations/bricks/editor-app/src/components/Row.svelte @@ -0,0 +1,47 @@ + + +
+ + +
+ {row.originalLabel} + {isRoot ? 'BLOCK' : 'ELEM'} +
+ +
+
+ {#if prefix}{prefix}{/if} + +
+ {#if showModifier} + + {/if} +
+
diff --git a/integrations/bricks/editor-app/src/components/Toast.svelte b/integrations/bricks/editor-app/src/components/Toast.svelte new file mode 100644 index 00000000..88037917 --- /dev/null +++ b/integrations/bricks/editor-app/src/components/Toast.svelte @@ -0,0 +1,39 @@ + + +{#if visible} + { visible = false; onDismiss?.(); }} + >{message} +{/if} diff --git a/integrations/bricks/editor-app/src/lib/apply.js b/integrations/bricks/editor-app/src/lib/apply.js new file mode 100644 index 00000000..480d8d04 --- /dev/null +++ b/integrations/bricks/editor-app/src/lib/apply.js @@ -0,0 +1,124 @@ +/** + * Apply BEM classes to a subtree in one pass. + * + * Four modes: + * add — attach new BEM classes (keep any existing classes) + * rename — detach old classes, create new ones seeded with old settings + * replace — detach old classes, create new ones with empty settings + * modifier — keep existing classes, append a --modifier class + * + * @module apply + */ + +import { slugify } from './slugify.js'; +import * as api from './bricks-api.js'; + +/** + * @param {object} opts + * @param {string} opts.rootId + * @param {Array<{id:string, name:string, modifier:string, include:boolean}>} opts.rows + * @param {'add'|'rename'|'replace'|'modifier'} opts.mode + * @param {boolean} opts.syncLabels + * @returns {{ok:true, count:number} | {ok:false, error:string}} + */ +export function applyToSubtree({ rootId, rows, mode, syncLabels }) { + const blockRow = rows.find(r => r.id === rootId); + if (!blockRow) return { ok: false, error: 'Root row missing.' }; + + const blockName = slugify(blockRow.name); + if (!blockName) return { ok: false, error: 'Block name is empty.' }; + + const globalClasses = api.getGlobalClasses(); + let count = 0; + + for (const row of rows) { + if (!row.include) continue; + + const isRoot = row.id === rootId; + const el = api.findElement(row.id); + if (!el) continue; + + // Build class name + let baseClass; + if (isRoot) { + baseClass = blockName; + } else { + const elemSlug = slugify(row.name); + if (!elemSlug) continue; + baseClass = `${blockName}__${elemSlug}`; + } + + // Apply modifier suffix when in modifier mode + let finalClass = baseClass; + if (mode === 'modifier' && row.modifier) { + const modSlug = slugify(row.modifier); + if (!modSlug) continue; + finalClass = `${baseClass}--${modSlug}`; + } + + // Read current classes on this element + const currentIds = readClassIds(el.settings); + + // Determine seed settings for the new class (rename copies from first old) + let seed = {}; + if (mode === 'rename' && currentIds.length > 0) { + const firstOld = globalClasses.find(c => c && c.id === currentIds[0]); + if (firstOld && firstOld.settings) { + seed = JSON.parse(JSON.stringify(firstOld.settings)); + } + } + + // Create or find the target global class + const newClassId = api.upsertGlobalClass(finalClass, seed); + + // Build the new class list for this element + let nextIds; + switch (mode) { + case 'add': + case 'modifier': + // Keep existing, append new (avoid duplicates) + nextIds = currentIds.includes(newClassId) ? currentIds : [...currentIds, newClassId]; + break; + case 'rename': + case 'replace': + // Replace all old classes with just the new one + nextIds = [newClassId]; + break; + default: + nextIds = [newClassId]; + } + + api.setElementClasses(row.id, nextIds); + + // Sync label if enabled (skip for modifier mode — label reflects identity, not state) + if (syncLabels && mode !== 'modifier') { + const label = labelFromClass(finalClass, blockName); + if (label) api.setElementLabel(row.id, label); + } + + count++; + } + + return { ok: true, count }; +} + +/** Read _cssGlobalClasses as a flat array of string ids. */ +function readClassIds(settings) { + const raw = settings?._cssGlobalClasses; + if (!raw) return []; + const arr = Array.isArray(raw) ? raw : Object.values(raw); + return arr.filter(id => typeof id === 'string' && id.length > 0); +} + +/** Title-case the element part of a class name for the structure label. */ +function labelFromClass(className, blockName) { + let s = className; + if (s === blockName) return titleCase(blockName.replace(/-/g, ' ')); + if (s.startsWith(blockName + '__')) s = s.slice(blockName.length + 2); + s = s.replace(/--.+$/, ''); + return titleCase(s.replace(/-/g, ' ')); +} + +function titleCase(s) { + return s.replace(/(^|\s)([a-z])/g, (_, sp, ch) => sp + ch.toUpperCase()); +} diff --git a/integrations/bricks/editor-app/src/lib/bricks-api.js b/integrations/bricks/editor-app/src/lib/bricks-api.js new file mode 100644 index 00000000..4e82441d --- /dev/null +++ b/integrations/bricks/editor-app/src/lib/bricks-api.js @@ -0,0 +1,109 @@ +/** + * Single seam to Bricks Builder internals. + * + * Every reach into __vue_app__ lives here. If Bricks restructures + * internals in a future release, this is the only file that changes. + * + * @module bricks-api + */ + +const APP_SELECTOR = '[data-v-app]'; +const AREAS = ['header', 'content', 'footer']; + +let _state = null; + +/** + * Generate a unique 8-char hex id that doesn't collide with existing ids. + */ +function newId(existingIds) { + const set = existingIds instanceof Set ? existingIds : new Set(existingIds || []); + for (let i = 0; i < 16; i++) { + const id = crypto.randomUUID().replace(/-/g, '').slice(0, 8); + if (!set.has(id)) return id; + } + throw new Error('rebemer: id collision exhausted'); +} + +/** Probe for the Bricks Vue app. Returns true on success. */ +export function probe() { + if (typeof document === 'undefined') return false; + const app = document.querySelector(APP_SELECTOR); + const candidate = app?.__vue_app__?.config?.globalProperties?.$_state; + if (!candidate || typeof candidate !== 'object' || !Array.isArray(candidate.globalClasses)) { + _state = null; + return false; + } + _state = candidate; + return true; +} + +export function isReady() { return _state !== null; } + +export function findElement(id) { + if (!_state || !id) return null; + for (const area of AREAS) { + const list = _state[area]; + if (!Array.isArray(list)) continue; + const found = list.find(el => el && el.id === id); + if (found) return found; + } + return null; +} + +/** Build flat subtree array: [{id, depth, label, settings}, ...] */ +export function getSubtree(rootId) { + const root = findElement(rootId); + if (!root) return []; + const out = [{ id: root.id, depth: 0, label: root.label, settings: root.settings }]; + walk(root, 1, out); + return out; +} + +function walk(node, depth, out) { + if (!node || !Array.isArray(node.children)) return; + for (const childId of node.children) { + const child = findElement(childId); + if (!child) continue; + out.push({ id: child.id, depth, label: child.label, settings: child.settings }); + walk(child, depth + 1, out); + } +} + +export function getGlobalClasses() { + return _state ? _state.globalClasses : []; +} + +/** + * Find existing class by name or create a new one. Returns class id. + * Never overwrites an existing class's settings. + */ +export function upsertGlobalClass(name, seedSettings) { + if (!_state) throw new Error('rebemer: not ready'); + const list = _state.globalClasses; + const existing = list.find(c => c && c.name === name); + if (existing) return existing.id; + + const knownIds = new Set(list.map(c => c?.id).filter(Boolean)); + const id = newId(knownIds); + list.push({ id, name, settings: seedSettings || {} }); + return id; +} + +/** Replace element's _cssGlobalClasses in-place (keeps Vue reactivity). */ +export function setElementClasses(id, classIds) { + const el = findElement(id); + if (!el) return; + if (!el.settings) el.settings = {}; + const next = Array.isArray(classIds) ? classIds : []; + if (Array.isArray(el.settings._cssGlobalClasses)) { + el.settings._cssGlobalClasses.splice(0, el.settings._cssGlobalClasses.length, ...next); + } else { + el.settings._cssGlobalClasses = [...next]; + } +} + +/** Set element label (shown in structure panel). */ +export function setElementLabel(id, label) { + const el = findElement(id); + if (el) el.label = label; +} diff --git a/integrations/bricks/editor-app/src/lib/slugify.js b/integrations/bricks/editor-app/src/lib/slugify.js new file mode 100644 index 00000000..ae8e6ea2 --- /dev/null +++ b/integrations/bricks/editor-app/src/lib/slugify.js @@ -0,0 +1,18 @@ +/** + * Convert a free-form string into a BEM-safe slug. + * Lowercase, ASCII-only, kebab-case. + * + * @param {string} input + * @returns {string} + */ +export function slugify(input) { + if (!input) return ''; + return String(input) + .normalize('NFKD') + .replace(/[\u0300-\u036f]/g, '') // strip combining marks + .replace(/[^\x00-\x7f]/g, '') // drop remaining non-ASCII + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') // non-alnum → single dash + .replace(/-+/g, '-') + .replace(/^-+|-+$/g, ''); // trim edge dashes +} diff --git a/integrations/bricks/editor-app/src/lib/validate.js b/integrations/bricks/editor-app/src/lib/validate.js new file mode 100644 index 00000000..73c54470 --- /dev/null +++ b/integrations/bricks/editor-app/src/lib/validate.js @@ -0,0 +1,23 @@ +/** + * Minimal BEM name validator. + * Lowercase kebab-case, starts with a letter, no CSS keywords. + * + * @module validate + */ + +const NAME_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/; + +const CSS_KEYWORDS = new Set([ + 'auto', 'inherit', 'initial', 'unset', 'revert', 'revert-layer', 'none', +]); + +/** + * @param {string} name — already slugified + * @returns {{ok:true} | {ok:false, reason:string}} + */ +export function validateName(name) { + if (!name) return { ok: false, reason: 'Name is empty.' }; + if (CSS_KEYWORDS.has(name)) return { ok: false, reason: `"${name}" is a CSS keyword.` }; + if (!NAME_RE.test(name)) return { ok: false, reason: 'Use lowercase letters, digits, and hyphens.' }; + return { ok: true }; +} diff --git a/integrations/bricks/editor-app/src/main.js b/integrations/bricks/editor-app/src/main.js new file mode 100644 index 00000000..3b5a889f --- /dev/null +++ b/integrations/bricks/editor-app/src/main.js @@ -0,0 +1,199 @@ +/** + * reBEMer entry point — runs inside the Bricks builder main panel. + * + * Responsibilities: + * 1. Probe the Bricks Vue app (with retries; Bricks mounts after our + * script in some load orders). Bail out silently on incompatible + * builds — the badge is never injected and no UI is shown. + * 2. Watch the structure panel via a narrowly-scoped MutationObserver + * and inject one per `li[data-id]`. + * 3. Mount a single on badge activation. Subsequent + * activations replace the existing panel. + * 4. Tear everything down via a single AbortController on unload. + * + * Imperative on purpose: there is no Svelte App component above this + * level because Bricks owns the structure-panel DOM and we have to + * mount badges into elements Bricks controls. A single Svelte tree + * couldn't follow Bricks' re-renders without fighting it. + */ +import { mount, unmount } from 'svelte'; +import * as api from './lib/bricks-api.js'; +import BemBadge from './components/BemBadge.svelte'; +import BemPanel from './components/BemPanel.svelte'; +import './styles/panel.css'; + +const STRUCTURE_PANEL_SELECTOR = '#bricks-structure'; +const ITEM_SELECTOR = 'li[data-id]'; +const HOST_ID = 'slashed-rebemer-host'; +const ATTACHED_FLAG = 'rebemerAttached'; +const PROBE_INTERVAL_MS = 400; +const PROBE_MAX_ATTEMPTS = 25; // ≈10s total grace before we give up + +const log = (level, ...args) => console[level]('[reBEMer]', ...args); + +const controller = new AbortController(); +const { signal } = controller; + +/** @type {Map} */ +const badgeInstances = new Map(); + +/** @type {{ instance: any, node: HTMLElement } | null} */ +let activePanel = null; + + +function ensureHost() { + let host = document.getElementById(HOST_ID); + if (!host) { + host = document.createElement('div'); + host.id = HOST_ID; + document.body.appendChild(host); + } + return host; +} + +function start() { + let attempts = 0; + const tryProbe = () => { + if (signal.aborted) return; + if (api.probe()) { + onProbed(); + return; + } + if (++attempts >= PROBE_MAX_ATTEMPTS) { + log('warn', 'Bricks Vue app not detected after grace window — reBEMer disabled on this page.'); + return; + } + setTimeout(tryProbe, PROBE_INTERVAL_MS); + }; + tryProbe(); +} + +function onProbed() { + // The structure panel may not be in the DOM yet on first paint; + // observe body once for its arrival, then narrow to the panel. + const existing = document.querySelector(STRUCTURE_PANEL_SELECTOR); + if (existing) { + onStructurePanelReady(existing); + return; + } + + const bodyObs = new MutationObserver(() => { + const panelEl = document.querySelector(STRUCTURE_PANEL_SELECTOR); + if (panelEl) { + bodyObs.disconnect(); + onStructurePanelReady(panelEl); + } + }); + bodyObs.observe(document.body, { childList: true, subtree: true }); + signal.addEventListener('abort', () => bodyObs.disconnect(), { once: true }); +} + + +function onStructurePanelReady(panelEl) { + // Debounce churny mutation bursts (Bricks rebuilds the tree on + // drag/drop, on import, on undo). 50 ms is enough to coalesce a + // typical burst without feeling laggy. + let debounceHandle = null; + const schedule = () => { + if (debounceHandle !== null) return; + debounceHandle = setTimeout(() => { + debounceHandle = null; + refreshBadges(panelEl); + }, 50); + }; + + const obs = new MutationObserver(schedule); + obs.observe(panelEl, { childList: true, subtree: true }); + signal.addEventListener('abort', () => { + obs.disconnect(); + if (debounceHandle !== null) clearTimeout(debounceHandle); + }, { once: true }); + + refreshBadges(panelEl); +} + +function refreshBadges(panelEl) { + // Mount any newly-arrived items. + const items = panelEl.querySelectorAll(ITEM_SELECTOR); + for (const li of items) { + if (!li.dataset[ATTACHED_FLAG]) injectBadgeInto(li); + } + + // Reap any badges whose host node has been detached. Bricks tends + // to rebuild list nodes wholesale on undo; the dataset flag goes + // away with the old node, so the badge just needs to be unmounted. + for (const [id, entry] of badgeInstances) { + if (!entry.host.isConnected) { + try { unmount(entry.instance); } catch (err) { log('warn', 'badge unmount failed', err); } + badgeInstances.delete(id); + } + } +} + + +function injectBadgeInto(li) { + const elementId = li.getAttribute('data-id'); + if (!elementId) return; + + // Try the conventional Bricks container; fall back to the row + // itself so the badge always renders even when Bricks restructures. + const target = + li.querySelector(':scope > .actions') || + li.querySelector(':scope > .structure-item-actions') || + li; + + const host = document.createElement('span'); + host.className = 'rebemer-badge-host'; + if (target.firstChild) { + target.insertBefore(host, target.firstChild); + } else { + target.appendChild(host); + } + + const labelNode = li.querySelector(':scope > .structure-item-title, :scope > .name, :scope .label'); + const label = labelNode ? labelNode.textContent.trim() : ''; + + const instance = mount(BemBadge, { + target: host, + props: { elementId, label, onActivate: openPanel }, + }); + + badgeInstances.set(elementId, { instance, host }); + li.dataset[ATTACHED_FLAG] = '1'; +} + +function openPanel(elementId) { + closePanel(); + const node = document.createElement('div'); + ensureHost().appendChild(node); + activePanel = { + instance: mount(BemPanel, { + target: node, + props: { rootId: elementId, onClose: closePanel }, + }), + node, + }; +} + +function closePanel() { + if (!activePanel) return; + try { unmount(activePanel.instance); } catch (err) { log('warn', 'panel unmount failed', err); } + activePanel.node.remove(); + activePanel = null; +} + + +window.addEventListener('beforeunload', () => { + controller.abort(); + closePanel(); + for (const { instance } of badgeInstances.values()) { + try { unmount(instance); } catch { /* ignore */ } + } + badgeInstances.clear(); +}, { once: true }); + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', start, { once: true }); +} else { + start(); +} diff --git a/integrations/bricks/editor-app/src/styles/panel.css b/integrations/bricks/editor-app/src/styles/panel.css new file mode 100644 index 00000000..7f3c191a --- /dev/null +++ b/integrations/bricks/editor-app/src/styles/panel.css @@ -0,0 +1,140 @@ +/* + * reBEMer panel styles — MVP. + * Scoped to .rebemer-* so nothing leaks into Bricks UI. + */ + +:root { + --rebemer-bg: #161a1d; + --rebemer-fg: #e1e1e1; + --rebemer-fg-muted: #8a8d95; + --rebemer-border: #3d4752; + --rebemer-accent: #e2b93b; + --rebemer-accent-hover: #ffd042; + --rebemer-input-bg: #293038; + --rebemer-input-border: #3d4752; + --rebemer-error: #ff4c4c; + --rebemer-success: #4ade80; + --rebemer-radius: 4px; + --rebemer-font: "Inter", -apple-system, BlinkMacSystemFont, sans-serif; +} + +/* Badge */ +.rebemer-badge { + font: 600 9px/1 var(--rebemer-font); + letter-spacing: .5px; + text-transform: uppercase; + color: var(--rebemer-bg); + background: var(--rebemer-accent); + border: 0; + border-radius: 2px; + padding: 2px 4px; + cursor: pointer; +} +.rebemer-badge:hover { background: var(--rebemer-accent-hover); } +.rebemer-badge:focus-visible { outline: 2px solid var(--rebemer-accent-hover); outline-offset: 1px; } + +/* Panel */ +.rebemer-panel { + position: fixed; + top: 80px; + left: 50%; + transform: translateX(-50%); + width: 440px; + max-width: calc(100vw - 40px); + max-height: 80vh; + z-index: 100000; + background: var(--rebemer-bg); + color: var(--rebemer-fg); + border: 1px solid var(--rebemer-border); + border-radius: var(--rebemer-radius); + box-shadow: 0 10px 40px rgba(0,0,0,.6); + display: flex; + flex-direction: column; + font: 12px/1.45 var(--rebemer-font); + overflow: hidden; + outline: none; +} +.rebemer-panel__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 14px; + border-bottom: 1px solid var(--rebemer-border); +} +.rebemer-panel__title { margin: 0; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; color: #fff; } +.rebemer-panel__subject { color: var(--rebemer-accent); text-transform: none; font-weight: 500; } +.rebemer-panel__close { background: transparent; border: 0; font-size: 18px; color: var(--rebemer-fg-muted); cursor: pointer; padding: 2px 6px; } +.rebemer-panel__close:hover { color: #fff; } + +/* Toolbar */ +.rebemer-panel__toolbar { + padding: 10px 14px; + display: flex; + gap: 12px; + align-items: center; + flex-wrap: wrap; + border-bottom: 1px solid var(--rebemer-border); +} +.rebemer-field { display: flex; flex-direction: column; gap: 4px; font-size: 11px; } +.rebemer-field--inline { flex-direction: row; align-items: center; gap: 6px; } +.rebemer-field__label { color: var(--rebemer-fg-muted); text-transform: uppercase; letter-spacing: .5px; font-size: 10px; } +.rebemer-field select, +.rebemer-field input[type="text"] { + background: var(--rebemer-input-bg); + border: 1px solid var(--rebemer-input-border); + border-radius: var(--rebemer-radius); + color: var(--rebemer-fg); + padding: 4px 6px; + font: inherit; +} +.rebemer-field select:focus, +.rebemer-field input:focus { outline: 1px solid var(--rebemer-accent); } + +/* Body / Rows */ +.rebemer-panel__body { flex: 1; overflow-y: auto; padding: 8px 14px; display: flex; flex-direction: column; gap: 6px; } + +.rebemer-row { + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + gap: 8px; + padding: 6px 8px; + border-radius: var(--rebemer-radius); + background: rgba(255,255,255,.02); +} +.rebemer-row:hover { background: rgba(255,255,255,.04); } +.rebemer-row--disabled { opacity: .45; } +.rebemer-row[data-depth="1"] { margin-left: 12px; } +.rebemer-row[data-depth="2"] { margin-left: 24px; } +.rebemer-row[data-depth="3"] { margin-left: 36px; } + +.rebemer-row__include input { cursor: pointer; } +.rebemer-row__meta { display: flex; gap: 6px; align-items: baseline; min-width: 0; } +.rebemer-row__label { font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.rebemer-row__type { font-size: 9px; color: var(--rebemer-fg-muted); text-transform: uppercase; } + +.rebemer-row__inputs { grid-column: 1 / -1; display: flex; gap: 6px; align-items: center; } +.rebemer-row__name { + flex: 1; + display: flex; + align-items: stretch; + background: var(--rebemer-input-bg); + border: 1px solid var(--rebemer-input-border); + border-radius: var(--rebemer-radius); + overflow: hidden; +} +.rebemer-row__prefix { padding: 4px 6px; background: rgba(0,0,0,.25); color: var(--rebemer-fg-muted); font-size: 11px; border-right: 1px solid var(--rebemer-input-border); white-space: nowrap; } +.rebemer-row__name input { background: transparent; border: 0; outline: 0; color: var(--rebemer-fg); font: inherit; padding: 4px 6px; flex: 1; min-width: 0; } +.rebemer-row__modifier { background: var(--rebemer-input-bg); border: 1px solid var(--rebemer-input-border); border-radius: var(--rebemer-radius); color: var(--rebemer-fg); font: inherit; padding: 4px 6px; width: 90px; } + +/* Footer */ +.rebemer-panel__footer { display: flex; justify-content: flex-end; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--rebemer-border); } +.rebemer-btn { background: var(--rebemer-input-bg); border: 1px solid var(--rebemer-input-border); color: var(--rebemer-fg); padding: 6px 12px; border-radius: var(--rebemer-radius); font: inherit; cursor: pointer; } +.rebemer-btn:hover { border-color: var(--rebemer-fg-muted); } +.rebemer-btn--primary { background: var(--rebemer-accent); border-color: var(--rebemer-accent); color: var(--rebemer-bg); font-weight: 600; } +.rebemer-btn--primary:hover { background: var(--rebemer-accent-hover); border-color: var(--rebemer-accent-hover); } + +/* Toast */ +.rebemer-toast { position: fixed; bottom: 16px; right: 16px; z-index: 100002; background: var(--rebemer-input-bg); color: var(--rebemer-fg); border: 1px solid var(--rebemer-border); border-radius: var(--rebemer-radius); padding: 8px 12px; font: 12px/1.45 var(--rebemer-font); cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,.4); } +.rebemer-toast--success { border-color: var(--rebemer-success); } +.rebemer-toast--error { border-color: var(--rebemer-error); color: var(--rebemer-error); } diff --git a/integrations/bricks/editor-app/svelte.config.js b/integrations/bricks/editor-app/svelte.config.js new file mode 100644 index 00000000..2e56eafd --- /dev/null +++ b/integrations/bricks/editor-app/svelte.config.js @@ -0,0 +1,12 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** + * Svelte 5 in runes mode for explicit reactivity ($state, $derived, $effect). + * Mirrors the admin-app config so both bundles compile identically. + */ +export default { + preprocess: vitePreprocess(), + compilerOptions: { + runes: true, + }, +}; diff --git a/integrations/bricks/editor-app/vite.config.js b/integrations/bricks/editor-app/vite.config.js new file mode 100644 index 00000000..b34892cd --- /dev/null +++ b/integrations/bricks/editor-app/vite.config.js @@ -0,0 +1,47 @@ +import { defineConfig } from 'vite'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; +import { resolve } from 'node:path'; + +/** + * Build config for the reBEMer editor bundle. + * + * Output goes into the plugin's assets/editor-app/ directory with stable + * filenames (no hashes), matching the admin-app convention. The PHP + * enqueue side references app.js / app.css directly and uses filemtime() + * for cache-busting. + * + * The bundle is fully self-contained: no externals, no CDN imports, no + * dynamic chunks. The editor surface is small enough that code-splitting + * would add operational complexity without measurable gain. + * + * Why no `define: { 'process.env.NODE_ENV': ... }` block: Vite's default + * production build inlines NODE_ENV correctly, and the editor bundle + * does not consume NODE_ENV at runtime. + */ +export default defineConfig({ + plugins: [svelte()], + build: { + outDir: resolve(__dirname, '../assets/editor-app'), + emptyOutDir: true, + sourcemap: true, + target: 'es2020', + cssCodeSplit: false, + rollupOptions: { + input: resolve(__dirname, 'src/main.js'), + output: { + entryFileNames: 'app.js', + chunkFileNames: 'app-[name].js', + assetFileNames: (info) => { + if (info.name && info.name.endsWith('.css')) return 'app.css'; + return 'app-[name][extname]'; + }, + }, + }, + }, + // Standalone dev server for component-level work outside the builder. + // The real integration runs inside the Bricks builder DOM, but this + // lets us iterate on the panel UI quickly with a mock Bricks state. + server: { + port: 5174, + }, +}); diff --git a/integrations/bricks/includes/class-rebemer-enqueue.php b/integrations/bricks/includes/class-rebemer-enqueue.php new file mode 100644 index 00000000..576f776a --- /dev/null +++ b/integrations/bricks/includes/class-rebemer-enqueue.php @@ -0,0 +1,54 @@ +]*)>/', ' diff --git a/integrations/bricks/editor-app/src/lib/apply.js b/integrations/bricks/editor-app/src/lib/apply.js index 480d8d04..bacaeb8e 100644 --- a/integrations/bricks/editor-app/src/lib/apply.js +++ b/integrations/bricks/editor-app/src/lib/apply.js @@ -31,6 +31,7 @@ export function applyToSubtree({ rootId, rows, mode, syncLabels }) { const globalClasses = api.getGlobalClasses(); let count = 0; + try { for (const row of rows) { if (!row.include) continue; @@ -98,6 +99,13 @@ export function applyToSubtree({ rootId, rows, mode, syncLabels }) { count++; } + } catch (err) { + return { ok: false, error: `Operation failed mid-apply: ${err.message}` }; + } + + if (count === 0) { + return { ok: false, error: 'No elements were modified. The subtree may have changed.' }; + } return { ok: true, count }; } diff --git a/integrations/bricks/editor-app/src/styles/panel.css b/integrations/bricks/editor-app/src/styles/panel.css index 7f3c191a..c195a115 100644 --- a/integrations/bricks/editor-app/src/styles/panel.css +++ b/integrations/bricks/editor-app/src/styles/panel.css @@ -3,7 +3,7 @@ * Scoped to .rebemer-* so nothing leaks into Bricks UI. */ -:root { +#slashed-rebemer-host { --rebemer-bg: #161a1d; --rebemer-fg: #e1e1e1; --rebemer-fg-muted: #8a8d95; diff --git a/integrations/bricks/includes/class-rebemer-enqueue.php b/integrations/bricks/includes/class-rebemer-enqueue.php index 576f776a..215ebeab 100644 --- a/integrations/bricks/includes/class-rebemer-enqueue.php +++ b/integrations/bricks/includes/class-rebemer-enqueue.php @@ -49,6 +49,8 @@ public function mark_as_module( $tag, $handle, $src ) { if ( self::SCRIPT_HANDLE !== $handle ) { return $tag; } - return preg_replace( '/]*)>/', ' -