');function q(m,a){let r=l(a,"scale",3,1),h=l(a,"class",3,"bg-foreground hover:bg-primary");const i=100;let d=c(()=>`${i*r()}px`),p=c(()=>`${i*r()}px`);var e=y(),t=n(e);u(e),f(()=>{_(e,1,b(h())),x(e,`mask:url(${a.path??""}) no-repeat center`),s(t,"src",a.path),s(t,"height",o(d)),s(t,"width",o(p)),s(t,"alt",`logo for ${a.path??""}`)}),v(m,e)}export{q as C};
diff --git a/_app/immutable/chunks/B1wPNbHL.js b/_app/immutable/chunks/B1wPNbHL.js
new file mode 100644
index 0000000..1103cef
--- /dev/null
+++ b/_app/immutable/chunks/B1wPNbHL.js
@@ -0,0 +1 @@
+import{p as t,b as f}from"./CPdWO9vA.js";import{N as d,p as h,d as _,s as b,t as p,a as u,b as k,e as v,r as x}from"./BbMFilaF.js";import{s as C,d as E}from"./DZYZrkxG.js";var N=_("");function j(i,e){h(e,!0);let c=t(e,"class",3,""),o=t(e,"baseClass",3,"flex flex-col"),n=t(e,"onclick",3,()=>{}),r=t(e,"role",3,""),l=t(e,"thisElement",15);var s=N();s.__click=function(...a){n()?.apply(this,a)};var m=v(s);b(m,()=>e.children),x(s),f(s,a=>l(a),()=>l()),p(()=>{C(s,1,`${c()} ${o()}`),E(s,"role",r())}),u(i,s),k()}d(["click"]);export{j as S};
diff --git a/_app/immutable/chunks/B6-ZdAj4.js b/_app/immutable/chunks/B6-ZdAj4.js
new file mode 100644
index 0000000..b1be79e
--- /dev/null
+++ b/_app/immutable/chunks/B6-ZdAj4.js
@@ -0,0 +1,3 @@
+import{o as Ve,s as At}from"./BP4hYQWv.js";import{n as be,bs as Rt,P as C,h as N,R as O,H as ne,B as Ge,x as Tt,G as It}from"./BbMFilaF.js";const q=[];function Le(e,t=be){let n=null;const r=new Set;function a(o){if(Rt(e,o)&&(e=o,n)){const c=!q.length;for(const l of r)l[1](),q.push(l,e);if(c){for(let l=0;l{r.delete(l),r.size===0&&n&&(n(),n=null)}}return{set:a,update:s,subscribe:i}}class ue{constructor(t,n){this.status=t,typeof n=="string"?this.body={message:n}:n?this.body=n:this.body={message:`Error: ${t}`}}toString(){return JSON.stringify(this.body)}}class Pe{constructor(t,n){this.status=t,this.location=n}}class xe extends Error{constructor(t,n,r){super(r),this.status=t,this.text=n}}new URL("sveltekit-internal://");function Ut(e,t){return e==="/"||t==="ignore"?e:t==="never"?e.endsWith("/")?e.slice(0,-1):e:t==="always"&&!e.endsWith("/")?e+"/":e}function Lt(e){return e.split("%25").map(decodeURI).join("%25")}function Pt(e){for(const t in e)e[t]=decodeURIComponent(e[t]);return e}function ke({href:e}){return e.split("#")[0]}function xt(e,t,n,r=!1){const a=new URL(e);Object.defineProperty(a,"searchParams",{value:new Proxy(a.searchParams,{get(i,o){if(o==="get"||o==="getAll"||o==="has")return l=>(n(l),i[o](l));t();const c=Reflect.get(i,o);return typeof c=="function"?c.bind(i):c}}),enumerable:!0,configurable:!0});const s=["href","pathname","search","toString","toJSON"];r&&s.push("hash");for(const i of s)Object.defineProperty(a,i,{get(){return t(),e[i]},enumerable:!0,configurable:!0});return a}function Ct(...e){let t=5381;for(const n of e)if(typeof n=="string"){let r=n.length;for(;r;)t=t*33^n.charCodeAt(--r)}else if(ArrayBuffer.isView(n)){const r=new Uint8Array(n.buffer,n.byteOffset,n.byteLength);let a=r.length;for(;a;)t=t*33^r[--a]}else throw new TypeError("value must be a string or TypedArray");return(t>>>0).toString(36)}new TextEncoder;const Nt=new TextDecoder;function Ot(e){const t=atob(e),n=new Uint8Array(t.length);for(let r=0;r((e instanceof Request?e.method:t?.method||"GET")!=="GET"&&J.delete(Ce(e)),$t(e,t));const J=new Map;function jt(e,t){const n=Ce(e,t),r=document.querySelector(n);if(r?.textContent){r.remove();let{body:a,...s}=JSON.parse(r.textContent);const i=r.getAttribute("data-ttl");return i&&J.set(n,{body:a,init:s,ttl:1e3*Number(i)}),r.getAttribute("data-b64")!==null&&(a=Ot(a)),Promise.resolve(new Response(a,s))}return window.fetch(e,t)}function Dt(e,t,n){if(J.size>0){const r=Ce(e,n),a=J.get(r);if(a){if(performance.now(){const a=/^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(r);if(a)return t.push({name:a[1],matcher:a[2],optional:!1,rest:!0,chained:!0}),"(?:/([^]*))?";const s=/^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(r);if(s)return t.push({name:s[1],matcher:s[2],optional:!0,rest:!1,chained:!0}),"(?:/([^/]+))?";if(!r)return;const i=r.split(/\[(.+?)\](?!\])/);return"/"+i.map((c,l)=>{if(l%2){if(c.startsWith("x+"))return Ee(String.fromCharCode(parseInt(c.slice(2),16)));if(c.startsWith("u+"))return Ee(String.fromCharCode(...c.slice(2).split("-").map(y=>parseInt(y,16))));const d=Bt.exec(c),[,p,u,f,h]=d;return t.push({name:f,matcher:h,optional:!!p,rest:!!u,chained:u?l===1&&i[0]==="":!1}),u?"([^]*?)":p?"([^/]*)?":"([^/]+?)"}return Ee(c)}).join("")}).join("")}/?$`),params:t}}function Mt(e){return e!==""&&!/^\([^)]+\)$/.test(e)}function tt(e){return e.slice(1).split("/").filter(Mt)}function qt(e,t,n){const r={},a=e.slice(1),s=a.filter(o=>o!==void 0);let i=0;for(let o=0;od).join("/"),i=0),l===void 0){c.rest&&(r[c.name]="");continue}if(!c.matcher||n[c.matcher](l)){r[c.name]=l;const d=t[o+1],p=a[o+1];d&&!d.rest&&d.optional&&p&&c.chained&&(i=0),!d&&!p&&Object.keys(r).length===s.length&&(i=0);continue}if(c.optional&&c.chained){i++;continue}return}if(!i)return r}function Ee(e){return e.normalize().replace(/[[\]]/g,"\\$&").replace(/%/g,"%25").replace(/\//g,"%2[Ff]").replace(/\?/g,"%3[Ff]").replace(/#/g,"%23").replace(/[.*+?^${}()|\\]/g,"\\$&")}const Vt=/\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;function On(e,t){const n=tt(e),r=e!="/"&&e.endsWith("/");return"/"+n.map(a=>a.replace(Vt,(s,i,o,c)=>{const l=t[c];if(!l){if(i||o&&l!==void 0)return"";throw new Error(`Missing parameter '${c}' in route ${e}`)}if(l.startsWith("/")||l.endsWith("/"))throw new Error(`Parameter '${c}' in route ${e} cannot start or end with a slash -- this would cause an invalid route like foo//bar`);return l})).filter(Boolean).join("/")+(r?"/":"")}function Gt({nodes:e,server_loads:t,dictionary:n,matchers:r}){const a=new Set(t);return Object.entries(n).map(([o,[c,l,d]])=>{const{pattern:p,params:u}=Ft(o),f={id:o,exec:h=>{const y=p.exec(h);if(y)return qt(y,u,r)},errors:[1,...d||[]].map(h=>e[h]),layouts:[0,...l||[]].map(i),leaf:s(c)};return f.errors.length=f.layouts.length=Math.max(f.errors.length,f.layouts.length),f});function s(o){const c=o<0;return c&&(o=~o),[c,e[o]]}function i(o){return o===void 0?o:[a.has(o),e[o]]}}function nt(e,t=JSON.parse){try{return t(sessionStorage[e])}catch{}}function ze(e,t,n=JSON.stringify){const r=n(t);try{sessionStorage[e]=r}catch{}}const L=globalThis.__sveltekit_1bzcbkr?.base??"",zt=globalThis.__sveltekit_1bzcbkr?.assets??L??"",Ht="1775561167945",rt="sveltekit:snapshot",at="sveltekit:scroll",Ne="sveltekit:states",ot="sveltekit:pageurl",F="sveltekit:history",W="sveltekit:navigation",D={tap:1,hover:2,viewport:3,eager:4,off:-1,false:-1},de=location.origin;function Oe(e){if(e instanceof URL)return e;let t=document.baseURI;if(!t){const n=document.getElementsByTagName("base");t=n.length?n[0].href:document.URL}return new URL(e,t)}function he(){return{x:pageXOffset,y:pageYOffset}}function V(e,t){return e.getAttribute(`data-sveltekit-${t}`)}const He={...D,"":D.hover};function st(e){let t=e.assignedSlot??e.parentNode;return t?.nodeType===11&&(t=t.host),t}function it(e,t){for(;e&&e!==t;){if(e.nodeName.toUpperCase()==="A"&&e.hasAttribute("href"))return e;e=st(e)}}function Re(e,t,n){let r;try{if(r=new URL(e instanceof SVGAElement?e.href.baseVal:e.href,document.baseURI),n&&r.hash.match(/^#[^/]/)){const o=location.hash.split("#")[1]||"/";r.hash=`#${o}${r.hash}`}}catch{}const a=e instanceof SVGAElement?e.target.baseVal:e.target,s=!r||!!a||pe(r,t,n)||(e.getAttribute("rel")||"").split(/\s+/).includes("external"),i=r?.origin===de&&e.hasAttribute("download");return{url:r,external:s,target:a,download:i}}function re(e){let t=null,n=null,r=null,a=null,s=null,i=null,o=e;for(;o&&o!==document.documentElement;)r===null&&(r=V(o,"preload-code")),a===null&&(a=V(o,"preload-data")),t===null&&(t=V(o,"keepfocus")),n===null&&(n=V(o,"noscroll")),s===null&&(s=V(o,"reload")),i===null&&(i=V(o,"replacestate")),o=st(o);function c(l){switch(l){case"":case"true":return!0;case"off":case"false":return!1;default:return}}return{preload_code:He[r??"off"],preload_data:He[a??"off"],keepfocus:c(t),noscroll:c(n),reload:c(s),replace_state:c(i)}}function We(e){const t=Le(e);let n=!0;function r(){n=!0,t.update(i=>i)}function a(i){n=!1,t.set(i)}function s(i){let o;return t.subscribe(c=>{(o===void 0||n&&c!==o)&&i(o=c)})}return{notify:r,set:a,subscribe:s}}const ct={v:()=>{}};function Wt(){const{set:e,subscribe:t}=Le(!1);let n;async function r(){clearTimeout(n);try{const a=await fetch(`${zt}/_app/version.json`,{headers:{pragma:"no-cache","cache-control":"no-cache"}});if(!a.ok)return!1;const i=(await a.json()).version!==Ht;return i&&(e(!0),ct.v(),clearTimeout(n)),i}catch{return!1}}return{subscribe:t,check:r}}function pe(e,t,n){return e.origin!==de||!e.pathname.startsWith(t)?!0:n?!(e.pathname===t+"/"||e.pathname===t+"/index.html"||e.protocol==="file:"&&e.pathname.replace(/\/[^/]+\.html?$/,"")===t):!1}function $n(e){}function Yt(e){const t=Jt(e),n=new ArrayBuffer(t.length),r=new DataView(n);for(let a=0;a>16),t+=String.fromCharCode((n&65280)>>8),t+=String.fromCharCode(n&255),n=r=0);return r===12?(n>>=4,t+=String.fromCharCode(n)):r===18&&(n>>=2,t+=String.fromCharCode((n&65280)>>8),t+=String.fromCharCode(n&255)),t}const Xt=-1,Zt=-2,Qt=-3,en=-4,tn=-5,nn=-6;function rn(e,t){if(typeof e=="number")return a(e,!0);if(!Array.isArray(e)||e.length===0)throw new Error("Invalid input");const n=e,r=Array(n.length);function a(s,i=!1){if(s===Xt)return;if(s===Qt)return NaN;if(s===en)return 1/0;if(s===tn)return-1/0;if(s===nn)return-0;if(i||typeof s!="number")throw new Error("Invalid input");if(s in r)return r[s];const o=n[s];if(!o||typeof o!="object")r[s]=o;else if(Array.isArray(o))if(typeof o[0]=="string"){const c=o[0],l=t?.[c];if(l)return r[s]=l(a(o[1]));switch(c){case"Date":r[s]=new Date(o[1]);break;case"Set":const d=new Set;r[s]=d;for(let f=1;ft!=null)}const sn="x-sveltekit-invalidated",cn="x-sveltekit-trailing-slash";function ae(e){return e instanceof ue||e instanceof xe?e.status:500}function ln(e){return e instanceof xe?e.text:"Internal Error"}let R,Z,Se;const fn=Ve.toString().includes("$$")||/function \w+\(\) \{\}/.test(Ve.toString());fn?(R={data:{},form:null,error:null,params:{},route:{id:null},state:{},status:-1,url:new URL("https://example.com")},Z={current:null},Se={current:!1}):(R=new class{#e=C({});get data(){return N(this.#e)}set data(t){O(this.#e,t)}#t=C(null);get form(){return N(this.#t)}set form(t){O(this.#t,t)}#n=C(null);get error(){return N(this.#n)}set error(t){O(this.#n,t)}#r=C({});get params(){return N(this.#r)}set params(t){O(this.#r,t)}#a=C({id:null});get route(){return N(this.#a)}set route(t){O(this.#a,t)}#o=C({});get state(){return N(this.#o)}set state(t){O(this.#o,t)}#s=C(-1);get status(){return N(this.#s)}set status(t){O(this.#s,t)}#i=C(new URL("https://example.com"));get url(){return N(this.#i)}set url(t){O(this.#i,t)}},Z=new class{#e=C(null);get current(){return N(this.#e)}set current(t){O(this.#e,t)}},Se=new class{#e=C(!1);get current(){return N(this.#e)}set current(t){O(this.#e,t)}},ct.v=()=>Se.current=!0);function ft(e){Object.assign(R,e)}const un="/__data.json",dn=".html__data.json";function hn(e){return e.endsWith(".html")?e.replace(/\.html$/,dn):e.replace(/\/$/,"")+un}const Ye={spanContext(){return pn},setAttribute(){return this},setAttributes(){return this},addEvent(){return this},setStatus(){return this},updateName(){return this},end(){return this},isRecording(){return!1},recordException(){return this},addLink(){return this},addLinks(){return this}},pn={traceId:"",spanId:"",traceFlags:0},{onMount:gn}=At,_n=Tt??(e=>e()),mn=new Set(["icon","shortcut icon","apple-touch-icon"]),M=nt(at)??{},Q=nt(rt)??{},j={url:We({}),page:We({}),navigating:Le(null),updated:Wt()};function ge(e){M[e]=he()}function ut(e,t){let n=e+1;for(;M[n];)delete M[n],n+=1;for(n=t+1;Q[n];)delete Q[n],n+=1}function Y(e,t=!1){return t?location.replace(e.href):location.href=e.href,new Promise(()=>{})}async function dt(){if("serviceWorker"in navigator){const e=await navigator.serviceWorker.getRegistration(L||"/");e&&await e.update()}}function Ke(){}let $e,Te,oe,$,Ie,b;const se=[],ie=[];let U=null;function ht(){U?.fork?.then(e=>e?.discard()),U=null}const te=new Map,pt=new Set,yn=new Set,G=new Set;let m={branch:[],error:null,url:null},je=!1,ce=!1,Je=!0,ee=!1,K=!1,De=!1,Be=!1,_e,E,T,B;const X=new Set,Xe=new Map;async function Fn(e,t,n){globalThis.__sveltekit_1bzcbkr?.data&&globalThis.__sveltekit_1bzcbkr.data,document.URL!==location.href&&(location.href=location.href),b=e,await e.hooks.init?.(),$e=Gt(e),$=document.documentElement,Ie=t,Te=e.nodes[0],oe=e.nodes[1],Te(),oe(),E=history.state?.[F],T=history.state?.[W],E||(E=T=Date.now(),history.replaceState({...history.state,[F]:E,[W]:T},""));const r=M[E];function a(){r&&(history.scrollRestoration="manual",scrollTo(r.x,r.y))}n?(a(),await Ln(Ie,n)):(await z({type:"enter",url:Oe(b.hash?xn(new URL(location.href)):location.href),replace_state:!0}),a()),Un()}function wn(){se.length=0,Be=!1}function gt(e){ie.some(t=>t?.snapshot)&&(Q[e]=ie.map(t=>t?.snapshot?.capture()))}function _t(e){Q[e]?.forEach((t,n)=>{ie[n]?.snapshot?.restore(t)})}function Ze(){ge(E),ze(at,M),gt(T),ze(rt,Q)}async function vn(e,t,n,r){let a;t.invalidateAll&&ht(),await z({type:"goto",url:Oe(e),keepfocus:t.keepFocus,noscroll:t.noScroll,replace_state:t.replaceState,state:t.state,redirect_count:n,nav_token:r,accept:()=>{t.invalidateAll&&(Be=!0,a=[...Xe.keys()]),t.invalidate&&t.invalidate.forEach(In)}}),t.invalidateAll&&ne().then(ne).then(()=>{Xe.forEach(({resource:s},i)=>{a?.includes(i)&&s.refresh?.()})})}async function bn(e){if(e.id!==U?.id){const t={};if(X.add(t),U={id:e.id,token:t,promise:wt({...e,preload:t}).then(n=>(X.delete(t),n.type==="loaded"&&n.state.error&&ht(),n)),fork:null},Ge){const n=U;n.fork=n.promise.then(r=>{if(n===U&&r.type==="loaded")try{return Ge(()=>{_e.$set(r.props),ft(r.props.page)})}catch{}return null})}}return U.promise}async function Ae(e){const t=(await ye(e,!1))?.route;t&&await Promise.all([...t.layouts,t.leaf].map(n=>n?.[1]()))}function mt(e,t,n){m=e.state;const r=document.querySelector("style[data-sveltekit]");if(r&&r.remove(),Object.assign(R,e.props.page),_e=new b.root({target:t,props:{...e.props,stores:j,components:ie},hydrate:n,sync:!1}),_t(T),n){const a={from:null,to:{params:m.params,route:{id:m.route?.id??null},url:new URL(location.href)},willUnload:!1,type:"enter",complete:Promise.resolve()};G.forEach(s=>s(a))}ce=!0}function le({url:e,params:t,branch:n,status:r,error:a,route:s,form:i}){let o="never";if(L&&(e.pathname===L||e.pathname===L+"/"))o="always";else for(const f of n)f?.slash!==void 0&&(o=f.slash);e.pathname=Ut(e.pathname,o),e.search=e.search;const c={type:"loaded",state:{url:e,params:t,branch:n,error:a,route:s},props:{constructors:on(n).map(f=>f.node.component),page:we(R)}};i!==void 0&&(c.props.form=i);let l={},d=!R,p=0;for(let f=0;f(o&&(c.route=!0),u[f])}),params:new Proxy(r,{get:(u,f)=>(o&&c.params.add(f),u[f])}),data:s?.data??null,url:xt(n,()=>{o&&(c.url=!0)},u=>{o&&c.search_params.add(u)},b.hash),async fetch(u,f){u instanceof Request&&(f={body:u.method==="GET"||u.method==="HEAD"?void 0:await u.blob(),cache:u.cache,credentials:u.credentials,headers:[...u.headers].length>0?u?.headers:void 0,integrity:u.integrity,keepalive:u.keepalive,method:u.method,mode:u.mode,redirect:u.redirect,referrer:u.referrer,referrerPolicy:u.referrerPolicy,signal:u.signal,...f});const{resolved:h,promise:y}=yt(u,f,n);return o&&d(h.href),y},setHeaders:()=>{},depends:d,parent(){return o&&(c.parent=!0),t()},untrack(u){o=!1;try{return u()}finally{o=!0}}};i=await l.universal.load.call(null,p)??null}return{node:l,loader:e,server:s,universal:l.universal?.load?{type:"data",data:i,uses:c}:null,data:i??s?.data??null,slash:l.universal?.trailingSlash??s?.slash}}function yt(e,t,n){let r=e instanceof Request?e.url:e;const a=new URL(r,n);a.origin===n.origin&&(r=a.href.slice(n.origin.length));const s=ce?Dt(r,a.href,t):jt(r,t);return{resolved:a,promise:s}}function Qe(e,t,n,r,a,s){if(Be)return!0;if(!a)return!1;if(a.parent&&e||a.route&&t||a.url&&n)return!0;for(const i of a.search_params)if(r.has(i))return!0;for(const i of a.params)if(s[i]!==m.params[i])return!0;for(const i of a.dependencies)if(se.some(o=>o(new URL(i))))return!0;return!1}function Me(e,t){return e?.type==="data"?e:e?.type==="skip"?t??null:null}function kn(e,t){if(!e)return new Set(t.searchParams.keys());const n=new Set([...e.searchParams.keys(),...t.searchParams.keys()]);for(const r of n){const a=e.searchParams.getAll(r),s=t.searchParams.getAll(r);a.every(i=>s.includes(i))&&s.every(i=>a.includes(i))&&n.delete(r)}return n}function et({error:e,url:t,route:n,params:r}){return{type:"loaded",state:{error:e,url:t,route:n,params:r,branch:[]},props:{page:we(R),constructors:[]}}}async function wt({id:e,invalidating:t,url:n,params:r,route:a,preload:s}){if(U?.id===e)return X.delete(U.token),U.promise;const{errors:i,layouts:o,leaf:c}=a,l=[...o,c];i.forEach(_=>_?.().catch(()=>{})),l.forEach(_=>_?.[1]().catch(()=>{}));let d=null;const p=m.url?e!==fe(m.url):!1,u=m.route?a.id!==m.route.id:!1,f=kn(m.url,n);let h=!1;{const _=l.map((w,k)=>{const A=m.branch[k],v=!!w?.[0]&&(A?.loader!==w[1]||Qe(h,u,p,f,A.server?.uses,r));return v&&(h=!0),v});if(_.some(Boolean)){try{d=await kt(n,_)}catch(w){const k=await H(w,{url:n,params:r,route:{id:e}});return X.has(s)?et({error:k,url:n,params:r,route:a}):me({status:ae(w),error:k,url:n,route:a})}if(d.type==="redirect")return d}}const y=d?.nodes;let S=!1;const g=l.map(async(_,w)=>{if(!_)return;const k=m.branch[w],A=y?.[w];if((!A||A.type==="skip")&&_[1]===k?.loader&&!Qe(S,u,p,f,k.universal?.uses,r))return k;if(S=!0,A?.type==="error")throw A;return Fe({loader:_[1],url:n,params:r,route:a,parent:async()=>{const x={};for(let I=0;I{});const P=[];for(let _=0;_Promise.resolve({}),server_data_node:Me(s)}),o={node:await oe(),loader:oe,universal:null,server:null,data:null};return le({url:n,params:a,branch:[i,o],status:e,error:t,route:null})}catch(i){if(i instanceof Pe)return vn(new URL(i.location,location.href),{},0);throw i}}async function Sn(e){const t=e.href;if(te.has(t))return te.get(t);let n;try{const r=(async()=>{let a=await b.hooks.reroute({url:new URL(e),fetch:async(s,i)=>yt(s,i,e).promise})??e;if(typeof a=="string"){const s=new URL(e);b.hash?s.hash=a:s.pathname=a,a=s}return a})();te.set(t,r),n=await r}catch{te.delete(t);return}return n}async function ye(e,t){if(e&&!pe(e,L,b.hash)){const n=await Sn(e);if(!n)return;const r=An(n);for(const a of $e){const s=a.exec(r);if(s)return{id:fe(e),invalidating:t,route:a,params:Pt(s),url:e}}}}function An(e){return Lt(b.hash?e.hash.replace(/^#/,"").replace(/[?#].+/,""):e.pathname.slice(L.length))||"/"}function fe(e){return(b.hash?e.hash.replace(/^#/,""):e.pathname)+e.search}function vt({url:e,type:t,intent:n,delta:r,event:a}){let s=!1;const i=qe(m,n,e,t);r!==void 0&&(i.navigation.delta=r),a!==void 0&&(i.navigation.event=a);const o={...i.navigation,cancel:()=>{s=!0,i.reject(new Error("navigation cancelled"))}};return ee||pt.forEach(c=>c(o)),s?null:i}async function z({type:e,url:t,popped:n,keepfocus:r,noscroll:a,replace_state:s,state:i={},redirect_count:o=0,nav_token:c={},accept:l=Ke,block:d=Ke,event:p}){const u=B;B=c;const f=await ye(t,!1),h=e==="enter"?qe(m,f,t,e):vt({url:t,type:e,delta:n?.delta,intent:f,event:p});if(!h){d(),B===c&&(B=u);return}const y=E,S=T;l(),ee=!0,ce&&h.navigation.type!=="enter"&&j.navigating.set(Z.current=h.navigation);let g=f&&await wt(f);if(!g){if(pe(t,L,b.hash))return await Y(t,s);g=await bt(t,{id:null},await H(new xe(404,"Not Found",`Not found: ${t.pathname}`),{url:t,params:{},route:{id:null}}),404,s)}if(t=f?.url||t,B!==c)return h.reject(new Error("navigation aborted")),!1;if(g.type==="redirect"){if(o<20){await z({type:e,url:new URL(g.location,t),popped:n,keepfocus:r,noscroll:a,replace_state:s,state:i,redirect_count:o+1,nav_token:c}),h.fulfil(void 0);return}g=await me({status:500,error:await H(new Error("Redirect loop"),{url:t,params:{},route:{id:null}}),url:t,route:{id:null}})}else g.props.page.status>=400&&await j.updated.check()&&(await dt(),await Y(t,s));if(wn(),ge(y),gt(S),g.props.page.url.pathname!==t.pathname&&(t.pathname=g.props.page.url.pathname),i=n?n.state:i,!n){const v=s?0:1,x={[F]:E+=v,[W]:T+=v,[Ne]:i};(s?history.replaceState:history.pushState).call(history,x,"",t),s||ut(E,T)}const P=U?.fork;U=null,g.props.page.state=i;let _;if(ce){const v=(await Promise.all(Array.from(yn,I=>I(h.navigation)))).filter(I=>typeof I=="function");if(v.length>0){let I=function(){v.forEach(ve=>{G.delete(ve)})};v.push(I),v.forEach(ve=>{G.add(ve)})}m=g.state,g.props.page&&(g.props.page.url=t);const x=P&&await P;x?_=x.commit():(_e.$set(g.props),ft(g.props.page),_=It?.()),De=!0}else mt(g,Ie,!1);const{activeElement:w}=document;await _,await ne(),await ne();let k=n?n.scroll:a?he():null;if(Je){const v=t.hash&&document.getElementById(St(t));if(k)scrollTo(k.x,k.y);else if(v){v.scrollIntoView();const{top:x,left:I}=v.getBoundingClientRect();k={x:pageXOffset+I,y:pageYOffset+x}}else scrollTo(0,0)}const A=document.activeElement!==w&&document.activeElement!==document.body;!r&&!A&&Pn(t,k),Je=!0,g.props.page&&Object.assign(R,g.props.page),ee=!1,e==="popstate"&&_t(T),h.fulfil(void 0),G.forEach(v=>v(h.navigation)),j.navigating.set(Z.current=null)}async function bt(e,t,n,r,a){return e.origin===de&&e.pathname===location.pathname&&!je?await me({status:r,error:n,url:e,route:t}):await Y(e,a)}function Rn(){let e,t,n;$.addEventListener("mousemove",o=>{const c=o.target;clearTimeout(e),e=setTimeout(()=>{s(c,D.hover)},20)});function r(o){o.defaultPrevented||s(o.composedPath()[0],D.tap)}$.addEventListener("mousedown",r),$.addEventListener("touchstart",r,{passive:!0});const a=new IntersectionObserver(o=>{for(const c of o)c.isIntersecting&&(Ae(new URL(c.target.href)),a.unobserve(c.target))},{threshold:0});async function s(o,c){const l=it(o,$),d=l===t&&c>=n;if(!l||d)return;const{url:p,external:u,download:f}=Re(l,L,b.hash);if(u||f)return;const h=re(l),y=p&&fe(m.url)===fe(p);if(!(h.reload||y))if(c<=h.preload_data){t=l,n=D.tap;const S=await ye(p,!1);if(!S)return;bn(S)}else c<=h.preload_code&&(t=l,n=c,Ae(p))}function i(){a.disconnect();for(const o of $.querySelectorAll("a")){const{url:c,external:l,download:d}=Re(o,L,b.hash);if(l||d)continue;const p=re(o);p.reload||(p.preload_code===D.viewport&&a.observe(o),p.preload_code===D.eager&&Ae(c))}}G.add(i),i()}function H(e,t){if(e instanceof ue)return e.body;const n=ae(e),r=ln(e);return b.hooks.handleError({error:e,event:t,status:n,message:r})??{message:r}}function Tn(e,t){gn(()=>(e.add(t),()=>{e.delete(t)}))}function Mn(e){Tn(G,e)}function In(e){if(typeof e=="function")se.push(e);else{const{href:t}=new URL(e,location.href);se.push(n=>n.href===t)}}function qn(e,t){ge(E);const n={[F]:E+=1,[W]:T,[ot]:R.url.href,[Ne]:t};history.pushState(n,"",Oe(e)),De=!0,R.state=t,_e.$set({page:_n(()=>we(R))}),ut(E,T)}function Un(){history.scrollRestoration="manual",addEventListener("beforeunload",t=>{let n=!1;if(Ze(),!ee){const r=qe(m,void 0,null,"leave"),a={...r.navigation,cancel:()=>{n=!0,r.reject(new Error("navigation cancelled"))}};pt.forEach(s=>s(a))}n?(t.preventDefault(),t.returnValue=""):history.scrollRestoration="auto"}),addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&Ze()}),navigator.connection?.saveData||Rn(),$.addEventListener("click",async t=>{if(t.button||t.which!==1||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||t.defaultPrevented)return;const n=it(t.composedPath()[0],$);if(!n)return;const{url:r,external:a,target:s,download:i}=Re(n,L,b.hash);if(!r)return;if(s==="_parent"||s==="_top"){if(window.parent!==window)return}else if(s&&s!=="_self")return;const o=re(n);if(!(n instanceof SVGAElement)&&r.protocol!==location.protocol&&!(r.protocol==="https:"||r.protocol==="http:")||i)return;const[l,d]=(b.hash?r.hash.replace(/^#/,""):r.href).split("#"),p=l===ke(location);if(a||o.reload&&(!p||!d)){vt({url:r,type:"link",event:t})?ee=!0:t.preventDefault();return}if(d!==void 0&&p){const[,u]=m.url.href.split("#");if(u===d){if(t.preventDefault(),d===""||d==="top"&&n.ownerDocument.getElementById("top")===null)scrollTo({top:0});else{const f=n.ownerDocument.getElementById(decodeURIComponent(d));f&&(f.scrollIntoView(),f.focus())}return}if(K=!0,ge(E),e(r),!o.replace_state)return;K=!1}t.preventDefault(),await new Promise(u=>{requestAnimationFrame(()=>{setTimeout(u,0)}),setTimeout(u,100)}),await z({type:"link",url:r,keepfocus:o.keepfocus,noscroll:o.noscroll,replace_state:o.replace_state??r.href===location.href,event:t})}),$.addEventListener("submit",t=>{if(t.defaultPrevented)return;const n=HTMLFormElement.prototype.cloneNode.call(t.target),r=t.submitter;if((r?.formTarget||n.target)==="_blank"||(r?.formMethod||n.method)!=="get")return;const i=new URL(r?.hasAttribute("formaction")&&r?.formAction||n.action);if(pe(i,L,!1))return;const o=t.target,c=re(o);if(c.reload)return;t.preventDefault(),t.stopPropagation();const l=new FormData(o,r);i.search=new URLSearchParams(l).toString(),z({type:"form",url:i,keepfocus:c.keepfocus,noscroll:c.noscroll,replace_state:c.replace_state??i.href===location.href,event:t})}),addEventListener("popstate",async t=>{if(!Ue){if(t.state?.[F]){const n=t.state[F];if(B={},n===E)return;const r=M[n],a=t.state[Ne]??{},s=new URL(t.state[ot]??location.href),i=t.state[W],o=m.url?ke(location)===ke(m.url):!1;if(i===T&&(De||o)){a!==R.state&&(R.state=a),e(s),M[E]=he(),r&&scrollTo(r.x,r.y),E=n;return}const l=n-E;await z({type:"popstate",url:s,popped:{state:a,scroll:r,delta:l},accept:()=>{E=n,T=i},block:()=>{history.go(-l)},nav_token:B,event:t})}else if(!K){const n=new URL(location.href);e(n),b.hash&&location.reload()}}}),addEventListener("hashchange",()=>{K&&(K=!1,history.replaceState({...history.state,[F]:++E,[W]:T},"",location.href))});for(const t of document.querySelectorAll("link"))mn.has(t.rel)&&(t.href=t.href);addEventListener("pageshow",t=>{t.persisted&&j.navigating.set(Z.current=null)});function e(t){m.url=R.url=t,j.page.set(we(R)),j.page.notify()}}async function Ln(e,{status:t=200,error:n,node_ids:r,params:a,route:s,server_route:i,data:o,form:c}){je=!0;const l=new URL(location.href);let d;({params:a={},route:s={id:null}}=await ye(l,!1)||{}),d=$e.find(({id:f})=>f===s.id);let p,u=!0;try{const f=r.map(async(y,S)=>{const g=o[S];return g?.uses&&(g.uses=Et(g.uses)),Fe({loader:b.nodes[y],url:l,params:a,route:s,parent:async()=>{const P={};for(let _=0;_s?"1":"0").join(""));const r=window.fetch,a=await r(n.href,{});if(!a.ok){let s;throw a.headers.get("content-type")?.includes("application/json")?s=await a.json():a.status===404?s="Not Found":a.status===500&&(s="Internal Error"),new ue(a.status,s)}return new Promise(async s=>{const i=new Map,o=a.body.getReader();function c(d){return rn(d,{...b.decoders,Promise:p=>new Promise((u,f)=>{i.set(p,{fulfil:u,reject:f})})})}let l="";for(;;){const{done:d,value:p}=await o.read();if(d&&!l)break;for(l+=!p&&l?`
+`:Nt.decode(p,{stream:!0});;){const u=l.indexOf(`
+`);if(u===-1)break;const f=JSON.parse(l.slice(0,u));if(l=l.slice(u+1),f.type==="redirect")return s(f);if(f.type==="data")f.nodes?.forEach(h=>{h?.type==="data"&&(h.uses=Et(h.uses),h.data=c(h.data))}),s(f);else if(f.type==="chunk"){const{id:h,data:y,error:S}=f,g=i.get(h);i.delete(h),S?g.reject(c(S)):g.fulfil(c(y))}}}})}function Et(e){return{dependencies:new Set(e?.dependencies??[]),params:new Set(e?.params??[]),parent:!!e?.parent,route:!!e?.route,url:!!e?.url,search_params:new Set(e?.search_params??[])}}let Ue=!1;function Pn(e,t=null){const n=document.querySelector("[autofocus]");if(n)n.focus();else{const r=St(e);if(r&&document.getElementById(r)){const{x:s,y:i}=t??he();setTimeout(()=>{const o=history.state;Ue=!0,location.replace(`#${r}`),b.hash&&location.replace(e.hash),history.replaceState(o,"",e.hash),scrollTo(s,i),Ue=!1})}else{const s=document.body,i=s.getAttribute("tabindex");s.tabIndex=-1,s.focus({preventScroll:!0,focusVisible:!1}),i!==null?s.setAttribute("tabindex",i):s.removeAttribute("tabindex")}const a=getSelection();if(a&&a.type!=="None"){const s=[];for(let i=0;i{if(a.rangeCount===s.length){for(let i=0;i{a=c,s=l});return i.catch(()=>{}),{navigation:{from:{params:e.params,route:{id:e.route?.id??null},url:e.url},to:n&&{params:t?.params??null,route:{id:t?.route?.id??null},url:n},willUnload:!t,type:r,complete:i},fulfil:a,reject:s}}function we(e){return{data:e.data,error:e.error,form:e.form,params:e.params,route:e.route,state:e.state,status:e.status,url:e.url}}function xn(e){const t=new URL(e);return t.hash=decodeURIComponent(e.hash),t}function St(e){let t;if(b.hash){const[,,n]=e.hash.split("#",3);t=n??""}else t=e.hash.slice(1);return decodeURIComponent(t)}export{ue as H,Mn as a,L as b,qn as c,Fn as d,$n as l,R as p,On as r,j as s};
diff --git a/_app/immutable/chunks/B6MtVlrN.js b/_app/immutable/chunks/B6MtVlrN.js
new file mode 100644
index 0000000..3a2ca98
--- /dev/null
+++ b/_app/immutable/chunks/B6MtVlrN.js
@@ -0,0 +1,44 @@
+import{l as w,p as d}from"./CPdWO9vA.js";import"./ByzbuU5l.js";import{L as I,M as L,o as M,au as O,av as x,j as y,x as k,h as m,aw as P,ax as u,ay as q,p as D,az as E,a as W,b as F,e as G,g as H,r as J,c as K,f as Q,u as R,aA as T}from"./BbMFilaF.js";import{e as U,i as V,a as X}from"./BGFanmKd.js";import{a as z}from"./DZYZrkxG.js";function Y(o,e,t,n,a){I&&L();var s=e.$$slots?.[t],r=!1;s===!0&&(s=e.children,r=!0),s===void 0||s(o,r?()=>n:n)}function Z(o=!1){const e=M,t=e.l.u;if(!t)return;let n=()=>u(e.s);if(o){let a=0,s={};const r=q(()=>{let i=!1;const l=e.s;for(const f in l)l[f]!==s[f]&&(s[f]=l[f],i=!0);return i&&a++,a});n=()=>m(r)}t.b.length&&O(()=>{N(e,n),x(t.b)}),y(()=>{const a=k(()=>t.m.map(P));return()=>{for(const s of a)typeof s=="function"&&s()}}),t.a.length&&y(()=>{N(e,n),x(t.a)})}function N(o,e){if(o.l.s)for(const t of o.l.s)m(t);e()}/**
+ * @license lucide-svelte v0.545.0 - ISC
+ *
+ * ISC License
+ *
+ * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * ---
+ *
+ * The MIT License (MIT) (for portions derived from Feather)
+ *
+ * Copyright (c) 2013-2023 Cole Bemis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */const $={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"};var ee=E("");function ie(o,e){const t=w(e,["children","$$slots","$$events","$$legacy"]),n=w(t,["name","color","size","strokeWidth","absoluteStrokeWidth","iconNode"]);D(e,!1);let a=d(e,"name",8,void 0),s=d(e,"color",8,"currentColor"),r=d(e,"size",8,24),i=d(e,"strokeWidth",8,2),l=d(e,"absoluteStrokeWidth",8,!1),f=d(e,"iconNode",24,()=>[]);const j=(...h)=>h.filter((c,g,_)=>!!c&&_.indexOf(c)===g).join(" ");Z();var v=ee();z(v,(h,c)=>({...$,...n,width:r(),height:r(),stroke:s(),"stroke-width":h,class:c}),[()=>(u(l()),u(i()),u(r()),k(()=>l()?Number(i())*24/Number(r()):i())),()=>(u(a()),u(t),k(()=>j("lucide-icon","lucide",a()?`lucide-${a()}`:"",t.class)))]);var b=G(v);U(b,1,f,V,(h,c)=>{var g=R(()=>T(m(c),2));let _=()=>m(g)[0],S=()=>m(g)[1];var p=K(),A=Q(p);X(A,_,!0,(B,te)=>{z(B,()=>({...S()}))}),W(h,p)});var C=H(b);Y(C,e,"default",{}),J(v),W(o,v),F()}export{ie as I,Z as i,Y as s};
diff --git a/_app/immutable/chunks/BECF2Gun.js b/_app/immutable/chunks/BECF2Gun.js
new file mode 100644
index 0000000..5149e7a
--- /dev/null
+++ b/_app/immutable/chunks/BECF2Gun.js
@@ -0,0 +1 @@
+import{e as Ne}from"./DZYZrkxG.js";const de="-",Le=e=>{const r=Fe(e),{conflictingClassGroups:o,conflictingClassGroupModifiers:t}=e;return{getClassGroupId:c=>{const u=c.split(de);return u[0]===""&&u.length!==1&&u.shift(),ze(u,r)||Ve(c)},getConflictingClassGroupIds:(c,u)=>{const p=o[c]||[];return u&&t[c]?[...p,...t[c]]:p}}},ze=(e,r)=>{if(e.length===0)return r.classGroupId;const o=e[0],t=r.nextPart.get(o),i=t?ze(e.slice(1),t):void 0;if(i)return i;if(r.validators.length===0)return;const l=e.join(de);return r.validators.find(({validator:c})=>c(l))?.classGroupId},we=/^\[(.+)\]$/,Ve=e=>{if(we.test(e)){const r=we.exec(e)[1],o=r?.substring(0,r.indexOf(":"));if(o)return"arbitrary.."+o}},Fe=e=>{const{theme:r,classGroups:o}=e,t={nextPart:new Map,validators:[]};for(const i in o)ne(o[i],t,i,r);return t},ne=(e,r,o,t)=>{e.forEach(i=>{if(typeof i=="string"){const l=i===""?r:ke(r,i);l.classGroupId=o;return}if(typeof i=="function"){if(_e(i)){ne(i(t),r,o,t);return}r.validators.push({validator:i,classGroupId:o});return}Object.entries(i).forEach(([l,c])=>{ne(c,ke(r,l),o,t)})})},ke=(e,r)=>{let o=e;return r.split(de).forEach(t=>{o.nextPart.has(t)||o.nextPart.set(t,{nextPart:new Map,validators:[]}),o=o.nextPart.get(t)}),o},_e=e=>e.isThemeGetter,Be=e=>{if(e<1)return{get:()=>{},set:()=>{}};let r=0,o=new Map,t=new Map;const i=(l,c)=>{o.set(l,c),r++,r>e&&(r=0,t=o,o=new Map)};return{get(l){let c=o.get(l);if(c!==void 0)return c;if((c=t.get(l))!==void 0)return i(l,c),c},set(l,c){o.has(l)?o.set(l,c):i(l,c)}}},ae="!",ie=":",We=ie.length,$e=e=>{const{prefix:r,experimentalParseClassName:o}=e;let t=i=>{const l=[];let c=0,u=0,p=0,b;for(let k=0;kp?b-p:void 0;return{modifiers:l,hasImportantModifier:_,baseClassName:S,maybePostfixModifierPosition:B}};if(r){const i=r+ie,l=t;t=c=>c.startsWith(i)?l(c.substring(i.length)):{isExternal:!0,modifiers:[],hasImportantModifier:!1,baseClassName:c,maybePostfixModifierPosition:void 0}}if(o){const i=t;t=l=>o({className:l,parseClassName:i})}return t},je=e=>e.endsWith(ae)?e.substring(0,e.length-1):e.startsWith(ae)?e.substring(1):e,Oe=e=>{const r=Object.fromEntries(e.orderSensitiveModifiers.map(t=>[t,!0]));return t=>{if(t.length<=1)return t;const i=[];let l=[];return t.forEach(c=>{c[0]==="["||r[c]?(i.push(...l.sort(),c),l=[]):l.push(c)}),i.push(...l.sort()),i}},Ue=e=>({cache:Be(e.cacheSize),parseClassName:$e(e),sortModifiers:Oe(e),...Le(e)}),qe=/\s+/,He=(e,r)=>{const{parseClassName:o,getClassGroupId:t,getConflictingClassGroupIds:i,sortModifiers:l}=r,c=[],u=e.trim().split(qe);let p="";for(let b=u.length-1;b>=0;b-=1){const h=u[b],{isExternal:S,modifiers:_,hasImportantModifier:B,baseClassName:k,maybePostfixModifierPosition:y}=o(h);if(S){p=h+(p.length>0?" "+p:p);continue}let R=!!y,G=t(R?k.substring(0,y):k);if(!G){if(!R){p=h+(p.length>0?" "+p:p);continue}if(G=t(k),!G){p=h+(p.length>0?" "+p:p);continue}R=!1}const U=l(_).join(":"),W=B?U+ae:U,T=W+G;if(c.includes(T))continue;c.push(T);const E=i(G,R);for(let A=0;A0?" "+p:p)}return p};function Je(){let e=0,r,o,t="";for(;e{if(typeof e=="string")return e;let r,o="";for(let t=0;tS(h),e());return o=Ue(b),t=o.cache.get,i=o.cache.set,l=u,u(p)}function u(p){const b=t(p);if(b)return b;const h=He(p,o);return i(p,h),h}return function(){return l(Je.apply(null,arguments))}}const f=e=>{const r=o=>o[e]||[];return r.isThemeGetter=!0,r},Se=/^\[(?:(\w[\w-]*):)?(.+)\]$/i,Ce=/^\((?:(\w[\w-]*):)?(.+)\)$/i,Xe=/^\d+\/\d+$/,De=/^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/,Ke=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/,Qe=/^(rgba?|hsla?|hwb|(ok)?(lab|lch)|color-mix)\(.+\)$/,Ye=/^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/,Ze=/^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/,L=e=>Xe.test(e),m=e=>!!e&&!Number.isNaN(Number(e)),C=e=>!!e&&Number.isInteger(Number(e)),te=e=>e.endsWith("%")&&m(e.slice(0,-1)),M=e=>De.test(e),eo=()=>!0,oo=e=>Ke.test(e)&&!Qe.test(e),Ge=()=>!1,ro=e=>Ye.test(e),to=e=>Ze.test(e),so=e=>!s(e)&&!n(e),no=e=>V(e,Ie,Ge),s=e=>Se.test(e),I=e=>V(e,Re,oo),se=e=>V(e,mo,m),ye=e=>V(e,Ae,Ge),ao=e=>V(e,Pe,to),K=e=>V(e,Te,ro),n=e=>Ce.test(e),j=e=>F(e,Re),io=e=>F(e,po),ve=e=>F(e,Ae),lo=e=>F(e,Ie),co=e=>F(e,Pe),Q=e=>F(e,Te,!0),V=(e,r,o)=>{const t=Se.exec(e);return t?t[1]?r(t[1]):o(t[2]):!1},F=(e,r,o=!1)=>{const t=Ce.exec(e);return t?t[1]?r(t[1]):o:!1},Ae=e=>e==="position"||e==="percentage",Pe=e=>e==="image"||e==="url",Ie=e=>e==="length"||e==="size"||e==="bg-size",Re=e=>e==="length",mo=e=>e==="number",po=e=>e==="family-name",Te=e=>e==="shadow",ce=()=>{const e=f("color"),r=f("font"),o=f("text"),t=f("font-weight"),i=f("tracking"),l=f("leading"),c=f("breakpoint"),u=f("container"),p=f("spacing"),b=f("radius"),h=f("shadow"),S=f("inset-shadow"),_=f("text-shadow"),B=f("drop-shadow"),k=f("blur"),y=f("perspective"),R=f("aspect"),G=f("ease"),U=f("animate"),W=()=>["auto","avoid","all","avoid-page","page","left","right","column"],T=()=>["center","top","bottom","left","right","top-left","left-top","top-right","right-top","bottom-right","right-bottom","bottom-left","left-bottom"],E=()=>[...T(),n,s],A=()=>["auto","hidden","clip","visible","scroll"],$=()=>["auto","contain","none"],d=()=>[n,s,p],v=()=>[L,"full","auto",...d()],me=()=>[C,"none","subgrid",n,s],pe=()=>["auto",{span:["full",C,n,s]},C,n,s],q=()=>[C,"auto",n,s],ue=()=>["auto","min","max","fr",n,s],ee=()=>["start","end","center","between","around","evenly","stretch","baseline","center-safe","end-safe"],N=()=>["start","end","center","stretch","center-safe","end-safe"],z=()=>["auto",...d()],P=()=>[L,"auto","full","dvw","dvh","lvw","lvh","svw","svh","min","max","fit",...d()],a=()=>[e,n,s],fe=()=>[...T(),ve,ye,{position:[n,s]}],ge=()=>["no-repeat",{repeat:["","x","y","space","round"]}],be=()=>["auto","cover","contain",lo,no,{size:[n,s]}],oe=()=>[te,j,I],x=()=>["","none","full",b,n,s],w=()=>["",m,j,I],H=()=>["solid","dashed","dotted","double"],he=()=>["normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"],g=()=>[m,te,ve,ye],xe=()=>["","none",k,n,s],J=()=>["none",m,n,s],X=()=>["none",m,n,s],re=()=>[m,n,s],D=()=>[L,"full",...d()];return{cacheSize:500,theme:{animate:["spin","ping","pulse","bounce"],aspect:["video"],blur:[M],breakpoint:[M],color:[eo],container:[M],"drop-shadow":[M],ease:["in","out","in-out"],font:[so],"font-weight":["thin","extralight","light","normal","medium","semibold","bold","extrabold","black"],"inset-shadow":[M],leading:["none","tight","snug","normal","relaxed","loose"],perspective:["dramatic","near","normal","midrange","distant","none"],radius:[M],shadow:[M],spacing:["px",m],text:[M],"text-shadow":[M],tracking:["tighter","tight","normal","wide","wider","widest"]},classGroups:{aspect:[{aspect:["auto","square",L,s,n,R]}],container:["container"],columns:[{columns:[m,s,n,u]}],"break-after":[{"break-after":W()}],"break-before":[{"break-before":W()}],"break-inside":[{"break-inside":["auto","avoid","avoid-page","avoid-column"]}],"box-decoration":[{"box-decoration":["slice","clone"]}],box:[{box:["border","content"]}],display:["block","inline-block","inline","flex","inline-flex","table","inline-table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row-group","table-row","flow-root","grid","inline-grid","contents","list-item","hidden"],sr:["sr-only","not-sr-only"],float:[{float:["right","left","none","start","end"]}],clear:[{clear:["left","right","both","none","start","end"]}],isolation:["isolate","isolation-auto"],"object-fit":[{object:["contain","cover","fill","none","scale-down"]}],"object-position":[{object:E()}],overflow:[{overflow:A()}],"overflow-x":[{"overflow-x":A()}],"overflow-y":[{"overflow-y":A()}],overscroll:[{overscroll:$()}],"overscroll-x":[{"overscroll-x":$()}],"overscroll-y":[{"overscroll-y":$()}],position:["static","fixed","absolute","relative","sticky"],inset:[{inset:v()}],"inset-x":[{"inset-x":v()}],"inset-y":[{"inset-y":v()}],start:[{start:v()}],end:[{end:v()}],top:[{top:v()}],right:[{right:v()}],bottom:[{bottom:v()}],left:[{left:v()}],visibility:["visible","invisible","collapse"],z:[{z:[C,"auto",n,s]}],basis:[{basis:[L,"full","auto",u,...d()]}],"flex-direction":[{flex:["row","row-reverse","col","col-reverse"]}],"flex-wrap":[{flex:["nowrap","wrap","wrap-reverse"]}],flex:[{flex:[m,L,"auto","initial","none",s]}],grow:[{grow:["",m,n,s]}],shrink:[{shrink:["",m,n,s]}],order:[{order:[C,"first","last","none",n,s]}],"grid-cols":[{"grid-cols":me()}],"col-start-end":[{col:pe()}],"col-start":[{"col-start":q()}],"col-end":[{"col-end":q()}],"grid-rows":[{"grid-rows":me()}],"row-start-end":[{row:pe()}],"row-start":[{"row-start":q()}],"row-end":[{"row-end":q()}],"grid-flow":[{"grid-flow":["row","col","dense","row-dense","col-dense"]}],"auto-cols":[{"auto-cols":ue()}],"auto-rows":[{"auto-rows":ue()}],gap:[{gap:d()}],"gap-x":[{"gap-x":d()}],"gap-y":[{"gap-y":d()}],"justify-content":[{justify:[...ee(),"normal"]}],"justify-items":[{"justify-items":[...N(),"normal"]}],"justify-self":[{"justify-self":["auto",...N()]}],"align-content":[{content:["normal",...ee()]}],"align-items":[{items:[...N(),{baseline:["","last"]}]}],"align-self":[{self:["auto",...N(),{baseline:["","last"]}]}],"place-content":[{"place-content":ee()}],"place-items":[{"place-items":[...N(),"baseline"]}],"place-self":[{"place-self":["auto",...N()]}],p:[{p:d()}],px:[{px:d()}],py:[{py:d()}],ps:[{ps:d()}],pe:[{pe:d()}],pt:[{pt:d()}],pr:[{pr:d()}],pb:[{pb:d()}],pl:[{pl:d()}],m:[{m:z()}],mx:[{mx:z()}],my:[{my:z()}],ms:[{ms:z()}],me:[{me:z()}],mt:[{mt:z()}],mr:[{mr:z()}],mb:[{mb:z()}],ml:[{ml:z()}],"space-x":[{"space-x":d()}],"space-x-reverse":["space-x-reverse"],"space-y":[{"space-y":d()}],"space-y-reverse":["space-y-reverse"],size:[{size:P()}],w:[{w:[u,"screen",...P()]}],"min-w":[{"min-w":[u,"screen","none",...P()]}],"max-w":[{"max-w":[u,"screen","none","prose",{screen:[c]},...P()]}],h:[{h:["screen","lh",...P()]}],"min-h":[{"min-h":["screen","lh","none",...P()]}],"max-h":[{"max-h":["screen","lh",...P()]}],"font-size":[{text:["base",o,j,I]}],"font-smoothing":["antialiased","subpixel-antialiased"],"font-style":["italic","not-italic"],"font-weight":[{font:[t,n,se]}],"font-stretch":[{"font-stretch":["ultra-condensed","extra-condensed","condensed","semi-condensed","normal","semi-expanded","expanded","extra-expanded","ultra-expanded",te,s]}],"font-family":[{font:[io,s,r]}],"fvn-normal":["normal-nums"],"fvn-ordinal":["ordinal"],"fvn-slashed-zero":["slashed-zero"],"fvn-figure":["lining-nums","oldstyle-nums"],"fvn-spacing":["proportional-nums","tabular-nums"],"fvn-fraction":["diagonal-fractions","stacked-fractions"],tracking:[{tracking:[i,n,s]}],"line-clamp":[{"line-clamp":[m,"none",n,se]}],leading:[{leading:[l,...d()]}],"list-image":[{"list-image":["none",n,s]}],"list-style-position":[{list:["inside","outside"]}],"list-style-type":[{list:["disc","decimal","none",n,s]}],"text-alignment":[{text:["left","center","right","justify","start","end"]}],"placeholder-color":[{placeholder:a()}],"text-color":[{text:a()}],"text-decoration":["underline","overline","line-through","no-underline"],"text-decoration-style":[{decoration:[...H(),"wavy"]}],"text-decoration-thickness":[{decoration:[m,"from-font","auto",n,I]}],"text-decoration-color":[{decoration:a()}],"underline-offset":[{"underline-offset":[m,"auto",n,s]}],"text-transform":["uppercase","lowercase","capitalize","normal-case"],"text-overflow":["truncate","text-ellipsis","text-clip"],"text-wrap":[{text:["wrap","nowrap","balance","pretty"]}],indent:[{indent:d()}],"vertical-align":[{align:["baseline","top","middle","bottom","text-top","text-bottom","sub","super",n,s]}],whitespace:[{whitespace:["normal","nowrap","pre","pre-line","pre-wrap","break-spaces"]}],break:[{break:["normal","words","all","keep"]}],wrap:[{wrap:["break-word","anywhere","normal"]}],hyphens:[{hyphens:["none","manual","auto"]}],content:[{content:["none",n,s]}],"bg-attachment":[{bg:["fixed","local","scroll"]}],"bg-clip":[{"bg-clip":["border","padding","content","text"]}],"bg-origin":[{"bg-origin":["border","padding","content"]}],"bg-position":[{bg:fe()}],"bg-repeat":[{bg:ge()}],"bg-size":[{bg:be()}],"bg-image":[{bg:["none",{linear:[{to:["t","tr","r","br","b","bl","l","tl"]},C,n,s],radial:["",n,s],conic:[C,n,s]},co,ao]}],"bg-color":[{bg:a()}],"gradient-from-pos":[{from:oe()}],"gradient-via-pos":[{via:oe()}],"gradient-to-pos":[{to:oe()}],"gradient-from":[{from:a()}],"gradient-via":[{via:a()}],"gradient-to":[{to:a()}],rounded:[{rounded:x()}],"rounded-s":[{"rounded-s":x()}],"rounded-e":[{"rounded-e":x()}],"rounded-t":[{"rounded-t":x()}],"rounded-r":[{"rounded-r":x()}],"rounded-b":[{"rounded-b":x()}],"rounded-l":[{"rounded-l":x()}],"rounded-ss":[{"rounded-ss":x()}],"rounded-se":[{"rounded-se":x()}],"rounded-ee":[{"rounded-ee":x()}],"rounded-es":[{"rounded-es":x()}],"rounded-tl":[{"rounded-tl":x()}],"rounded-tr":[{"rounded-tr":x()}],"rounded-br":[{"rounded-br":x()}],"rounded-bl":[{"rounded-bl":x()}],"border-w":[{border:w()}],"border-w-x":[{"border-x":w()}],"border-w-y":[{"border-y":w()}],"border-w-s":[{"border-s":w()}],"border-w-e":[{"border-e":w()}],"border-w-t":[{"border-t":w()}],"border-w-r":[{"border-r":w()}],"border-w-b":[{"border-b":w()}],"border-w-l":[{"border-l":w()}],"divide-x":[{"divide-x":w()}],"divide-x-reverse":["divide-x-reverse"],"divide-y":[{"divide-y":w()}],"divide-y-reverse":["divide-y-reverse"],"border-style":[{border:[...H(),"hidden","none"]}],"divide-style":[{divide:[...H(),"hidden","none"]}],"border-color":[{border:a()}],"border-color-x":[{"border-x":a()}],"border-color-y":[{"border-y":a()}],"border-color-s":[{"border-s":a()}],"border-color-e":[{"border-e":a()}],"border-color-t":[{"border-t":a()}],"border-color-r":[{"border-r":a()}],"border-color-b":[{"border-b":a()}],"border-color-l":[{"border-l":a()}],"divide-color":[{divide:a()}],"outline-style":[{outline:[...H(),"none","hidden"]}],"outline-offset":[{"outline-offset":[m,n,s]}],"outline-w":[{outline:["",m,j,I]}],"outline-color":[{outline:a()}],shadow:[{shadow:["","none",h,Q,K]}],"shadow-color":[{shadow:a()}],"inset-shadow":[{"inset-shadow":["none",S,Q,K]}],"inset-shadow-color":[{"inset-shadow":a()}],"ring-w":[{ring:w()}],"ring-w-inset":["ring-inset"],"ring-color":[{ring:a()}],"ring-offset-w":[{"ring-offset":[m,I]}],"ring-offset-color":[{"ring-offset":a()}],"inset-ring-w":[{"inset-ring":w()}],"inset-ring-color":[{"inset-ring":a()}],"text-shadow":[{"text-shadow":["none",_,Q,K]}],"text-shadow-color":[{"text-shadow":a()}],opacity:[{opacity:[m,n,s]}],"mix-blend":[{"mix-blend":[...he(),"plus-darker","plus-lighter"]}],"bg-blend":[{"bg-blend":he()}],"mask-clip":[{"mask-clip":["border","padding","content","fill","stroke","view"]},"mask-no-clip"],"mask-composite":[{mask:["add","subtract","intersect","exclude"]}],"mask-image-linear-pos":[{"mask-linear":[m]}],"mask-image-linear-from-pos":[{"mask-linear-from":g()}],"mask-image-linear-to-pos":[{"mask-linear-to":g()}],"mask-image-linear-from-color":[{"mask-linear-from":a()}],"mask-image-linear-to-color":[{"mask-linear-to":a()}],"mask-image-t-from-pos":[{"mask-t-from":g()}],"mask-image-t-to-pos":[{"mask-t-to":g()}],"mask-image-t-from-color":[{"mask-t-from":a()}],"mask-image-t-to-color":[{"mask-t-to":a()}],"mask-image-r-from-pos":[{"mask-r-from":g()}],"mask-image-r-to-pos":[{"mask-r-to":g()}],"mask-image-r-from-color":[{"mask-r-from":a()}],"mask-image-r-to-color":[{"mask-r-to":a()}],"mask-image-b-from-pos":[{"mask-b-from":g()}],"mask-image-b-to-pos":[{"mask-b-to":g()}],"mask-image-b-from-color":[{"mask-b-from":a()}],"mask-image-b-to-color":[{"mask-b-to":a()}],"mask-image-l-from-pos":[{"mask-l-from":g()}],"mask-image-l-to-pos":[{"mask-l-to":g()}],"mask-image-l-from-color":[{"mask-l-from":a()}],"mask-image-l-to-color":[{"mask-l-to":a()}],"mask-image-x-from-pos":[{"mask-x-from":g()}],"mask-image-x-to-pos":[{"mask-x-to":g()}],"mask-image-x-from-color":[{"mask-x-from":a()}],"mask-image-x-to-color":[{"mask-x-to":a()}],"mask-image-y-from-pos":[{"mask-y-from":g()}],"mask-image-y-to-pos":[{"mask-y-to":g()}],"mask-image-y-from-color":[{"mask-y-from":a()}],"mask-image-y-to-color":[{"mask-y-to":a()}],"mask-image-radial":[{"mask-radial":[n,s]}],"mask-image-radial-from-pos":[{"mask-radial-from":g()}],"mask-image-radial-to-pos":[{"mask-radial-to":g()}],"mask-image-radial-from-color":[{"mask-radial-from":a()}],"mask-image-radial-to-color":[{"mask-radial-to":a()}],"mask-image-radial-shape":[{"mask-radial":["circle","ellipse"]}],"mask-image-radial-size":[{"mask-radial":[{closest:["side","corner"],farthest:["side","corner"]}]}],"mask-image-radial-pos":[{"mask-radial-at":T()}],"mask-image-conic-pos":[{"mask-conic":[m]}],"mask-image-conic-from-pos":[{"mask-conic-from":g()}],"mask-image-conic-to-pos":[{"mask-conic-to":g()}],"mask-image-conic-from-color":[{"mask-conic-from":a()}],"mask-image-conic-to-color":[{"mask-conic-to":a()}],"mask-mode":[{mask:["alpha","luminance","match"]}],"mask-origin":[{"mask-origin":["border","padding","content","fill","stroke","view"]}],"mask-position":[{mask:fe()}],"mask-repeat":[{mask:ge()}],"mask-size":[{mask:be()}],"mask-type":[{"mask-type":["alpha","luminance"]}],"mask-image":[{mask:["none",n,s]}],filter:[{filter:["","none",n,s]}],blur:[{blur:xe()}],brightness:[{brightness:[m,n,s]}],contrast:[{contrast:[m,n,s]}],"drop-shadow":[{"drop-shadow":["","none",B,Q,K]}],"drop-shadow-color":[{"drop-shadow":a()}],grayscale:[{grayscale:["",m,n,s]}],"hue-rotate":[{"hue-rotate":[m,n,s]}],invert:[{invert:["",m,n,s]}],saturate:[{saturate:[m,n,s]}],sepia:[{sepia:["",m,n,s]}],"backdrop-filter":[{"backdrop-filter":["","none",n,s]}],"backdrop-blur":[{"backdrop-blur":xe()}],"backdrop-brightness":[{"backdrop-brightness":[m,n,s]}],"backdrop-contrast":[{"backdrop-contrast":[m,n,s]}],"backdrop-grayscale":[{"backdrop-grayscale":["",m,n,s]}],"backdrop-hue-rotate":[{"backdrop-hue-rotate":[m,n,s]}],"backdrop-invert":[{"backdrop-invert":["",m,n,s]}],"backdrop-opacity":[{"backdrop-opacity":[m,n,s]}],"backdrop-saturate":[{"backdrop-saturate":[m,n,s]}],"backdrop-sepia":[{"backdrop-sepia":["",m,n,s]}],"border-collapse":[{border:["collapse","separate"]}],"border-spacing":[{"border-spacing":d()}],"border-spacing-x":[{"border-spacing-x":d()}],"border-spacing-y":[{"border-spacing-y":d()}],"table-layout":[{table:["auto","fixed"]}],caption:[{caption:["top","bottom"]}],transition:[{transition:["","all","colors","opacity","shadow","transform","none",n,s]}],"transition-behavior":[{transition:["normal","discrete"]}],duration:[{duration:[m,"initial",n,s]}],ease:[{ease:["linear","initial",G,n,s]}],delay:[{delay:[m,n,s]}],animate:[{animate:["none",U,n,s]}],backface:[{backface:["hidden","visible"]}],perspective:[{perspective:[y,n,s]}],"perspective-origin":[{"perspective-origin":E()}],rotate:[{rotate:J()}],"rotate-x":[{"rotate-x":J()}],"rotate-y":[{"rotate-y":J()}],"rotate-z":[{"rotate-z":J()}],scale:[{scale:X()}],"scale-x":[{"scale-x":X()}],"scale-y":[{"scale-y":X()}],"scale-z":[{"scale-z":X()}],"scale-3d":["scale-3d"],skew:[{skew:re()}],"skew-x":[{"skew-x":re()}],"skew-y":[{"skew-y":re()}],transform:[{transform:[n,s,"","none","gpu","cpu"]}],"transform-origin":[{origin:E()}],"transform-style":[{transform:["3d","flat"]}],translate:[{translate:D()}],"translate-x":[{"translate-x":D()}],"translate-y":[{"translate-y":D()}],"translate-z":[{"translate-z":D()}],"translate-none":["translate-none"],accent:[{accent:a()}],appearance:[{appearance:["none","auto"]}],"caret-color":[{caret:a()}],"color-scheme":[{scheme:["normal","dark","light","light-dark","only-dark","only-light"]}],cursor:[{cursor:["auto","default","pointer","wait","text","move","help","not-allowed","none","context-menu","progress","cell","crosshair","vertical-text","alias","copy","no-drop","grab","grabbing","all-scroll","col-resize","row-resize","n-resize","e-resize","s-resize","w-resize","ne-resize","nw-resize","se-resize","sw-resize","ew-resize","ns-resize","nesw-resize","nwse-resize","zoom-in","zoom-out",n,s]}],"field-sizing":[{"field-sizing":["fixed","content"]}],"pointer-events":[{"pointer-events":["auto","none"]}],resize:[{resize:["none","","y","x"]}],"scroll-behavior":[{scroll:["auto","smooth"]}],"scroll-m":[{"scroll-m":d()}],"scroll-mx":[{"scroll-mx":d()}],"scroll-my":[{"scroll-my":d()}],"scroll-ms":[{"scroll-ms":d()}],"scroll-me":[{"scroll-me":d()}],"scroll-mt":[{"scroll-mt":d()}],"scroll-mr":[{"scroll-mr":d()}],"scroll-mb":[{"scroll-mb":d()}],"scroll-ml":[{"scroll-ml":d()}],"scroll-p":[{"scroll-p":d()}],"scroll-px":[{"scroll-px":d()}],"scroll-py":[{"scroll-py":d()}],"scroll-ps":[{"scroll-ps":d()}],"scroll-pe":[{"scroll-pe":d()}],"scroll-pt":[{"scroll-pt":d()}],"scroll-pr":[{"scroll-pr":d()}],"scroll-pb":[{"scroll-pb":d()}],"scroll-pl":[{"scroll-pl":d()}],"snap-align":[{snap:["start","end","center","align-none"]}],"snap-stop":[{snap:["normal","always"]}],"snap-type":[{snap:["none","x","y","both"]}],"snap-strictness":[{snap:["mandatory","proximity"]}],touch:[{touch:["auto","none","manipulation"]}],"touch-x":[{"touch-pan":["x","left","right"]}],"touch-y":[{"touch-pan":["y","up","down"]}],"touch-pz":["touch-pinch-zoom"],select:[{select:["none","text","all","auto"]}],"will-change":[{"will-change":["auto","scroll","contents","transform",n,s]}],fill:[{fill:["none",...a()]}],"stroke-w":[{stroke:[m,j,I,se]}],stroke:[{stroke:["none",...a()]}],"forced-color-adjust":[{"forced-color-adjust":["auto","none"]}]},conflictingClassGroups:{overflow:["overflow-x","overflow-y"],overscroll:["overscroll-x","overscroll-y"],inset:["inset-x","inset-y","start","end","top","right","bottom","left"],"inset-x":["right","left"],"inset-y":["top","bottom"],flex:["basis","grow","shrink"],gap:["gap-x","gap-y"],p:["px","py","ps","pe","pt","pr","pb","pl"],px:["pr","pl"],py:["pt","pb"],m:["mx","my","ms","me","mt","mr","mb","ml"],mx:["mr","ml"],my:["mt","mb"],size:["w","h"],"font-size":["leading"],"fvn-normal":["fvn-ordinal","fvn-slashed-zero","fvn-figure","fvn-spacing","fvn-fraction"],"fvn-ordinal":["fvn-normal"],"fvn-slashed-zero":["fvn-normal"],"fvn-figure":["fvn-normal"],"fvn-spacing":["fvn-normal"],"fvn-fraction":["fvn-normal"],"line-clamp":["display","overflow"],rounded:["rounded-s","rounded-e","rounded-t","rounded-r","rounded-b","rounded-l","rounded-ss","rounded-se","rounded-ee","rounded-es","rounded-tl","rounded-tr","rounded-br","rounded-bl"],"rounded-s":["rounded-ss","rounded-es"],"rounded-e":["rounded-se","rounded-ee"],"rounded-t":["rounded-tl","rounded-tr"],"rounded-r":["rounded-tr","rounded-br"],"rounded-b":["rounded-br","rounded-bl"],"rounded-l":["rounded-tl","rounded-bl"],"border-spacing":["border-spacing-x","border-spacing-y"],"border-w":["border-w-x","border-w-y","border-w-s","border-w-e","border-w-t","border-w-r","border-w-b","border-w-l"],"border-w-x":["border-w-r","border-w-l"],"border-w-y":["border-w-t","border-w-b"],"border-color":["border-color-x","border-color-y","border-color-s","border-color-e","border-color-t","border-color-r","border-color-b","border-color-l"],"border-color-x":["border-color-r","border-color-l"],"border-color-y":["border-color-t","border-color-b"],translate:["translate-x","translate-y","translate-none"],"translate-none":["translate","translate-x","translate-y","translate-z"],"scroll-m":["scroll-mx","scroll-my","scroll-ms","scroll-me","scroll-mt","scroll-mr","scroll-mb","scroll-ml"],"scroll-mx":["scroll-mr","scroll-ml"],"scroll-my":["scroll-mt","scroll-mb"],"scroll-p":["scroll-px","scroll-py","scroll-ps","scroll-pe","scroll-pt","scroll-pr","scroll-pb","scroll-pl"],"scroll-px":["scroll-pr","scroll-pl"],"scroll-py":["scroll-pt","scroll-pb"],touch:["touch-x","touch-y","touch-pz"],"touch-x":["touch"],"touch-y":["touch"],"touch-pz":["touch"]},conflictingClassGroupModifiers:{"font-size":["leading"]},orderSensitiveModifiers:["*","**","after","backdrop","before","details-content","file","first-letter","first-line","marker","placeholder","selection"]}},uo=(e,{cacheSize:r,prefix:o,experimentalParseClassName:t,extend:i={},override:l={}})=>(O(e,"cacheSize",r),O(e,"prefix",o),O(e,"experimentalParseClassName",t),Y(e.theme,l.theme),Y(e.classGroups,l.classGroups),Y(e.conflictingClassGroups,l.conflictingClassGroups),Y(e.conflictingClassGroupModifiers,l.conflictingClassGroupModifiers),O(e,"orderSensitiveModifiers",l.orderSensitiveModifiers),Z(e.theme,i.theme),Z(e.classGroups,i.classGroups),Z(e.conflictingClassGroups,i.conflictingClassGroups),Z(e.conflictingClassGroupModifiers,i.conflictingClassGroupModifiers),Ee(e,i,"orderSensitiveModifiers"),e),O=(e,r,o)=>{o!==void 0&&(e[r]=o)},Y=(e,r)=>{if(r)for(const o in r)O(e,o,r[o])},Z=(e,r)=>{if(r)for(const o in r)Ee(e,r,o)},Ee=(e,r,o)=>{const t=r[o];t!==void 0&&(e[o]=e[o]?e[o].concat(t):t)},bo=(e,...r)=>typeof e=="function"?le(ce,e,...r):le(()=>uo(ce(),e),...r),fo=le(ce);function ho(...e){return fo(Ne(e))}export{ho as c,bo as e,fo as t};
diff --git a/_app/immutable/chunks/BGFanmKd.js b/_app/immutable/chunks/BGFanmKd.js
new file mode 100644
index 0000000..40e0c65
--- /dev/null
+++ b/_app/immutable/chunks/BGFanmKd.js
@@ -0,0 +1 @@
+import{S as K,I as z,T as Q,U as g,L as h,V as k,M as X,h as W,W as se,X as ie,Y as te,Z,_ as b,$ as D,a0 as ue,a1 as ve,a2 as G,a3 as de,a4 as V,a5 as F,a6 as oe,a7 as $,a8 as _e,a9 as y,aa as j,q as ce,ab as ee,ac as me,ad as Ee,ae as P,af as he,ag as ae,ah as pe,ai as Te,aj as Ae,ak as Ne,al as we,am as re,an as Ce,J as Ie,K as Me,ao as Se,ap as xe,aq as De,ar as Re}from"./BbMFilaF.js";function be(s,r){return r}function ge(s,r,e){for(var i=s.items,o=[],_=r.length,t=0;t<_;t++)Ae(r[t].e,o,!0);var f=_>0&&o.length===0&&e!==null;if(f){var A=e.parentNode;Ne(A),A.append(e),i.clear(),S(s,r[0].prev,r[_-1].next)}we(o,()=>{for(var m=0;m<_;m++){var v=r[m];f||(i.delete(v.k),S(s,v.prev,v.next)),ae(v.e,!f)}})}function Le(s,r,e,i,o,_=null){var t=s,f={flags:r,items:new Map,first:null},A=(r&Q)!==0;if(A){var m=s;t=h?g(k(m)):m.appendChild(K())}h&&X();var v=null,w=!1,N=new Map,C=se(()=>{var c=e();return ce(c)?c:c==null?[]:j(c)}),n,d;function u(){He(d,n,f,N,t,o,r,i,e),_!==null&&(n.length===0?v?ee(v):v=G(()=>_(t)):v!==null&&me(v,()=>{v=null}))}z(()=>{d??=re,n=W(C);var c=n.length;if(w&&c===0)return;w=c===0;let T=!1;if(h){var I=ie(t)===te;I!==(c===0)&&(t=Z(),g(t),b(!1),T=!0)}if(h){for(var x=null,E,a=0;a0&&g(Z())}if(h)c===0&&_&&(v=G(()=>_(t)));else if(de()){var H=new Set,L=oe;for(a=0;a0){var le=(t&Q)!==0&&w===0?o:null;if(m){for(l=0;l{if(c!==void 0)for(a of c)a.a?.apply()}),s.first=e.first&&e.first.e,s.last=u&&u.e;for(var fe of i.values())ae(fe.e);i.clear()}function ne(s,r,e,i){(i&V)!==0&&$(s.v,r),(i&F)!==0?$(s.i,e):s.i=e}function J(s,r,e,i,o,_,t,f,A,m,v){var w=(A&V)!==0,N=(A&Ee)===0,C=w?N?_e(o,!1,!1):y(o):o,n=(A&F)===0?t:y(t),d={i:n,v:C,k:_,a:null,e:null,prev:e,next:i};try{if(s===null){var u=document.createDocumentFragment();u.append(s=K())}return d.e=G(()=>f(s,C,n,m),h),d.e.prev=e&&e.e,d.e.next=i&&i.e,e===null?v||(r.first=d):(e.next=d,e.e.next=d.e),i!==null&&(i.prev=d,i.e.prev=d.e),d}finally{}}function U(s,r,e){for(var i=s.next?s.next.e.nodes_start:e,o=r?r.e.nodes_start:e,_=s.e.nodes_start;_!==null&&_!==i;){var t=Te(_);o.before(_),_=t}}function S(s,r,e){r===null?s.first=e:(r.next=e,r.e.next=e&&e.e),e!==null&&(e.prev=r,e.e.prev=r&&r.e)}function qe(s,r,e,i,o,_){let t=h;h&&X();var f=null;h&&D.nodeType===Ce&&(f=D,X());var A=h?D:s,m=new Me(A,!1);z(()=>{const v=r()||null;var w=e||v==="svg"?Se:null;if(v===null){m.ensure(null,null);return}return m.ensure(v,N=>{if(v){if(f=h?f:w?document.createElementNS(w,v):document.createElement(v),xe(f,f),i){h&&De(v)&&f.append(document.createComment(""));var C=h?k(f):f.appendChild(K());h&&(C===null?b(!1):g(C)),i(f,C)}re.nodes_end=f,N.before(f)}h&&g(N)}),()=>{}},Ie),Re(()=>{}),t&&(b(!0),g(A))}export{qe as a,Le as e,be as i};
diff --git a/_app/immutable/chunks/BP4hYQWv.js b/_app/immutable/chunks/BP4hYQWv.js
new file mode 100644
index 0000000..243b84f
--- /dev/null
+++ b/_app/immutable/chunks/BP4hYQWv.js
@@ -0,0 +1 @@
+import{l as o,j as v,k as i,m as d,o as e,q as g,v as f,w as y,x as c,y as b,z as x,A as h,B as C,C as k,D as S,E as w,F as A,G as j,H as D}from"./BbMFilaF.js";import{h as E,m as z,u as M}from"./DmP2n0k_.js";function O(){return i===null&&d(),(i.ac??=new AbortController).signal}function _(t){e===null&&o(),y&&e.l!==null?r(e).m.push(t):v(()=>{const n=c(t);if(typeof n=="function")return n})}function P(t){e===null&&o(),_(()=>()=>c(t))}function U(t,n,{bubbles:s=!1,cancelable:l=!1}={}){return new CustomEvent(t,{detail:n,bubbles:s,cancelable:l})}function $(){const t=e;return t===null&&o(),(n,s,l)=>{const a=t.s.$$events?.[n];if(a){const p=g(a)?a.slice():[a],u=U(n,s,l);for(const m of p)m.call(t.x,u);return!u.defaultPrevented}return!0}}function q(t){e===null&&o(),e.l===null&&f(),r(e).b.push(t)}function B(t){e===null&&o(),e.l===null&&f(),r(e).a.push(t)}function r(t){var n=t.l;return n.u??={a:[],b:[],m:[]}}const H=Object.freeze(Object.defineProperty({__proto__:null,afterUpdate:B,beforeUpdate:q,createContext:b,createEventDispatcher:$,createRawSnippet:x,flushSync:h,fork:C,getAbortSignal:O,getAllContexts:k,getContext:S,hasContext:w,hydrate:E,mount:z,onDestroy:P,onMount:_,setContext:A,settled:j,tick:D,unmount:M,untrack:c},Symbol.toStringTag,{value:"Module"}));export{_ as o,H as s};
diff --git a/_app/immutable/chunks/BaThn6Za.js b/_app/immutable/chunks/BaThn6Za.js
new file mode 100644
index 0000000..9ae6186
--- /dev/null
+++ b/_app/immutable/chunks/BaThn6Za.js
@@ -0,0 +1 @@
+import{I as t,J as c,K as h,L as i,M as m}from"./BbMFilaF.js";function f(n,r,e){i&&m();var o=new h(n);t(()=>{var a=r()??null;o.ensure(a,a&&(s=>e(s,a)))},c)}export{f as c};
diff --git a/_app/immutable/chunks/BbMFilaF.js b/_app/immutable/chunks/BbMFilaF.js
new file mode 100644
index 0000000..9d5ca5e
--- /dev/null
+++ b/_app/immutable/chunks/BbMFilaF.js
@@ -0,0 +1 @@
+var Kt=Array.isArray,zt=Array.prototype.indexOf,rr=Array.from,Xt=Object.defineProperty,he=Object.getOwnPropertyDescriptor,Zt=Object.getOwnPropertyDescriptors,Jt=Object.prototype,Qt=Array.prototype,_t=Object.getPrototypeOf,nt=Object.isExtensible;function sr(e){return typeof e=="function"}const ar=()=>{};function ir(e){return e()}function en(e){for(var t=0;t{e=r,t=s});return{promise:n,resolve:e,reject:t}}function fr(e,t){if(Array.isArray(e))return e;if(!(Symbol.iterator in e))return Array.from(e);const n=[];for(const r of e)if(n.push(r),n.length===t)break;return n}const g=2,Be=4,Re=8,V=16,B=32,ee=64,Ue=128,T=1024,x=2048,U=4096,P=8192,z=16384,We=32768,we=65536,Pe=1<<17,tn=1<<18,_e=1<<19,dt=1<<20,N=256,Ee=512,Te=32768,Le=1<<21,Ge=1<<22,ae=1<<23,ie=Symbol("$state"),or=Symbol("legacy props"),lr=Symbol(""),le=new class extends Error{name="StaleReactionError";message="The reaction that called `getAbortSignal()` was re-run or destroyed"},cr=1,Ne=3,ht=8;function nn(e){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function rn(){throw new Error("https://svelte.dev/e/missing_context")}function sn(){throw new Error("https://svelte.dev/e/async_derived_orphan")}function an(e){throw new Error("https://svelte.dev/e/effect_in_teardown")}function fn(){throw new Error("https://svelte.dev/e/effect_in_unowned_derived")}function on(e){throw new Error("https://svelte.dev/e/effect_orphan")}function ln(){throw new Error("https://svelte.dev/e/effect_update_depth_exceeded")}function un(){throw new Error("https://svelte.dev/e/experimental_async_fork")}function cn(){throw new Error("https://svelte.dev/e/fork_discarded")}function _n(){throw new Error("https://svelte.dev/e/fork_timing")}function _r(){throw new Error("https://svelte.dev/e/get_abort_signal_outside_reaction")}function vr(){throw new Error("https://svelte.dev/e/hydration_failed")}function dr(e){throw new Error("https://svelte.dev/e/lifecycle_legacy_only")}function hr(e){throw new Error("https://svelte.dev/e/props_invalid_value")}function vn(){throw new Error("https://svelte.dev/e/state_descriptors_fixed")}function dn(){throw new Error("https://svelte.dev/e/state_prototype_fixed")}function hn(){throw new Error("https://svelte.dev/e/state_unsafe_mutation")}function pr(){throw new Error("https://svelte.dev/e/svelte_boundary_reset_onerror")}const wr=1,mr=2,yr=4,br=8,gr=16,Er=1,Tr=2,kr=4,Ar=8,xr=16,pn=1,wn=2,mn="[",yn="[!",bn="]",$e={},E=Symbol(),Sr="http://www.w3.org/1999/xhtml",Rr="http://www.w3.org/2000/svg",Nr="@attach";function Ke(e){console.warn("https://svelte.dev/e/hydration_mismatch")}function Or(){console.warn("https://svelte.dev/e/select_multiple_invalid_value")}function Dr(){console.warn("https://svelte.dev/e/svelte_boundary_reset_noop")}let k=!1;function pt(e){k=e}let w;function Z(e){if(e===null)throw Ke(),$e;return w=e}function wt(){return Z(te(w))}function Cr(e){if(k){if(te(w)!==null)throw Ke(),$e;w=e}}function Ir(e=1){if(k){for(var t=e,n=w;t--;)n=te(n);w=n}}function Mr(e=!0){for(var t=0,n=w;;){if(n.nodeType===ht){var r=n.data;if(r===bn){if(t===0)return n;t-=1}else(r===mn||r===yn)&&(t+=1)}var s=te(n);e&&n.remove(),n=s}}function Pr(e){if(!e||e.nodeType!==ht)throw Ke(),$e;return e.data}function mt(e){return e===this.v}function gn(e,t){return e!=e?t==t:e!==t||e!==null&&typeof e=="object"||typeof e=="function"}function yt(e){return!gn(e,this.v)}let Oe=!1;function Lr(){Oe=!0}let y=null;function ke(e){y=e}function Fr(){const e={};return[()=>(kn(e)||rn(),En(e)),t=>Tn(e,t)]}function En(e){return De().get(e)}function Tn(e,t){return De().set(e,t),t}function kn(e){return De().has(e)}function jr(){return De()}function qr(e,t=!1,n){y={p:y,i:!1,c:null,e:null,s:e,x:null,l:Oe&&!t?{s:null,u:null,$:[]}:null}}function Yr(e){var t=y,n=t.e;if(n!==null){t.e=null;for(var r of n)Mt(r)}return t.i=!0,y=t.p,{}}function be(){return!Oe||y!==null&&y.l===null}function De(e){return y===null&&nn(),y.c??=new Map(An(y)||void 0)}function An(e){let t=e.p;for(;t!==null;){const n=t.c;if(n!==null)return n;t=t.p}return null}let re=[];function bt(){var e=re;re=[],en(e)}function gt(e){if(re.length===0&&!pe){var t=re;queueMicrotask(()=>{t===re&&bt()})}re.push(e)}function xn(){for(;re.length>0;)bt()}function Sn(e){var t=_;if(t===null)return c.f|=ae,e;if((t.f&We)===0){if((t.f&Ue)===0)throw e;t.b.error(e)}else Ae(e,t)}function Ae(e,t){for(;t!==null;){if((t.f&Ue)!==0)try{t.b.error(e);return}catch(n){e=n}t=t.parent}throw e}const se=new Set;let m=null,O=null,rt=new Set,M=[],Ce=null,Fe=!1,pe=!1;class Y{committed=!1;current=new Map;previous=new Map;#t=new Set;#n=new Set;#e=0;#r=0;#a=null;#i=[];#f=[];skipped_effects=new Set;is_fork=!1;process(t){M=[],this.apply();var n={parent:null,effect:null,effects:[],render_effects:[],block_effects:[]};for(const r of t)this.#o(r,n);this.is_fork||this.#l(),this.#r>0||this.is_fork?(this.#s(n.effects),this.#s(n.render_effects),this.#s(n.block_effects)):(m=null,st(n.render_effects),st(n.effects)),O=null}#o(t,n){t.f^=T;for(var r=t.first;r!==null;){var s=r.f,a=(s&(B|ee))!==0,i=a&&(s&T)!==0,o=i||(s&P)!==0||this.skipped_effects.has(r);if((r.f&Ue)!==0&&r.b?.is_pending()&&(n={parent:n,effect:r,effects:[],render_effects:[],block_effects:[]}),!o&&r.fn!==null){a?r.f^=T:(s&Be)!==0?n.effects.push(r):ge(r)&&((r.f&V)!==0&&n.block_effects.push(r),ye(r));var f=r.first;if(f!==null){r=f;continue}}var l=r.parent;for(r=r.next;r===null&&l!==null;)l===n.effect&&(this.#s(n.effects),this.#s(n.render_effects),this.#s(n.block_effects),n=n.parent),r=l.next,l=l.parent}}#s(t){for(const n of t)((n.f&x)!==0?this.#i:this.#f).push(n),b(n,T)}capture(t,n){this.previous.has(t)||this.previous.set(t,n),this.current.set(t,t.v),O?.set(t,t.v)}activate(){m=this}deactivate(){m=null,O=null}flush(){if(this.activate(),M.length>0){if(qe(),m!==null&&m!==this)return}else this.#e===0&&this.process([]);this.deactivate();for(const t of rt)if(rt.delete(t),t(),m!==null)break}discard(){for(const t of this.#n)t(this);this.#n.clear()}#l(){if(this.#r===0){for(const t of this.#t)t();this.#t.clear()}this.#e===0&&this.#u()}#u(){if(se.size>1){this.previous.clear();var t=O,n=!0,r={parent:null,effect:null,effects:[],render_effects:[],block_effects:[]};for(const s of se){if(s===this){n=!1;continue}const a=[];for(const[o,f]of this.current){if(s.current.has(o))if(n&&f!==s.current.get(o))s.current.set(o,f);else continue;a.push(o)}if(a.length===0)continue;const i=[...s.current.keys()].filter(o=>!this.current.has(o));if(i.length>0){const o=new Set,f=new Map;for(const l of a)Et(l,i,o,f);if(M.length>0){m=s,s.apply();for(const l of M)s.#o(l,r);M=[],s.deactivate()}}}m=null,O=t}this.committed=!0,se.delete(this),this.#a?.resolve()}increment(t){this.#e+=1,t&&(this.#r+=1)}decrement(t){this.#e-=1,t&&(this.#r-=1),this.revive()}revive(){for(const t of this.#i)b(t,x),oe(t);for(const t of this.#f)b(t,U),oe(t);this.#i=[],this.#f=[],this.flush()}oncommit(t){this.#t.add(t)}ondiscard(t){this.#n.add(t)}settled(){return(this.#a??=vt()).promise}static ensure(){if(m===null){const t=m=new Y;se.add(m),pe||Y.enqueue(()=>{m===t&&t.flush()})}return m}static enqueue(t){gt(t)}apply(){}}function je(e){var t=pe;pe=!0;try{var n;for(e&&(m!==null&&qe(),n=e());;){if(xn(),M.length===0&&(m?.flush(),M.length===0))return Ce=null,n;qe()}}finally{pe=t}}function qe(){var e=ce;Fe=!0;try{var t=0;for(ot(!0);M.length>0;){var n=Y.ensure();if(t++>1e3){var r,s;Rn()}n.process(M),X.clear()}}finally{Fe=!1,ot(e),Ce=null}}function Rn(){try{ln()}catch(e){Ae(e,Ce)}}let j=null;function st(e){var t=e.length;if(t!==0){for(var n=0;n0)){X.clear();for(const s of j){if((s.f&(z|P))!==0)continue;const a=[s];let i=s.parent;for(;i!==null;)j.has(i)&&(j.delete(i),a.push(i)),i=i.parent;for(let o=a.length-1;o>=0;o--){const f=a[o];(f.f&(z|P))===0&&ye(f)}}j.clear()}}j=null}}function Et(e,t,n,r){if(!n.has(e)&&(n.add(e),e.reactions!==null))for(const s of e.reactions){const a=s.f;(a&g)!==0?Et(s,t,n,r):(a&(Ge|V))!==0&&(a&x)===0&&kt(s,t,r)&&(b(s,x),oe(s))}}function Tt(e,t){if(e.reactions!==null)for(const n of e.reactions){const r=n.f;(r&g)!==0?Tt(n,t):(r&Pe)!==0&&(b(n,x),t.add(n))}}function kt(e,t,n){const r=n.get(e);if(r!==void 0)return r;if(e.deps!==null)for(const s of e.deps){if(t.includes(s))return!0;if((s.f&g)!==0&&kt(s,t,n))return n.set(s,!0),!0}return n.set(e,!1),!1}function oe(e){for(var t=Ce=e;t.parent!==null;){t=t.parent;var n=t.f;if(Fe&&t===_&&(n&V)!==0)return;if((n&(ee|B))!==0){if((n&T)===0)return;t.f^=T}}M.push(t)}function Hr(e){un(),m!==null&&_n();var t=Y.ensure();t.is_fork=!0;var n=!1,r=t.settled();je(e);for(var[s,a]of t.previous)s.v=a;return{commit:async()=>{if(n){await r;return}se.has(t)||cn(),n=!0,t.is_fork=!1;for(var[i,o]of t.current)i.v=o;je(()=>{var f=new Set;for(var l of t.current.keys())Tt(l,f);Mn(f),Rt()}),t.revive(),await r},discard:()=>{!n&&se.has(t)&&(se.delete(t),t.discard())}}}function Nn(e,t,n){const r=be()?ze:Cn;if(t.length===0){n(e.map(r));return}var s=m,a=_,i=On(),o=k;Promise.all(t.map(f=>Dn(f))).then(f=>{i();try{n([...e.map(r),...f])}catch(l){(a.f&z)===0&&Ae(l,a)}o&&pt(!1),s?.deactivate(),Ye()}).catch(f=>{Ae(f,a)})}function On(){var e=_,t=c,n=y,r=m,s=k;if(s)var a=w;return function(){Q(e),F(t),ke(n),r?.activate(),s&&(pt(!0),Z(a))}}function Ye(){Q(null),F(null),ke(null)}function ze(e){var t=g|x,n=c!==null&&(c.f&g)!==0?c:null;return _===null||n!==null&&(n.f&N)!==0?t|=N:_.f|=_e,{ctx:y,deps:null,effects:null,equals:mt,f:t,fn:e,reactions:null,rv:0,v:E,wv:0,parent:n??_,ac:null}}function Dn(e,t){let n=_;n===null&&sn();var r=n.b,s=void 0,a=Ze(E),i=!c,o=new Map;return jn(()=>{var f=vt();s=f.promise;try{Promise.resolve(e()).then(f.resolve,f.reject).then(()=>{l===m&&l.committed&&l.deactivate(),Ye()})}catch(d){f.reject(d),Ye()}var l=m;if(i){var u=!r.is_pending();r.update_pending_count(1),l.increment(u),o.get(l)?.reject(le),o.delete(l),o.set(l,f)}const v=(d,h=void 0)=>{if(l.activate(),h)h!==le&&(a.f|=ae,He(a,h));else{(a.f&ae)!==0&&(a.f^=ae),He(a,d);for(const[p,W]of o){if(o.delete(p),p===l)break;W.reject(le)}}i&&(r.update_pending_count(-1),l.decrement(u))};f.promise.then(v,d=>v(null,d||"unknown"))}),Qe(()=>{for(const f of o.values())f.reject(le)}),new Promise(f=>{function l(u){function v(){u===s?f(a):l(s)}u.then(v,v)}l(s)})}function Vr(e){const t=ze(e);return Ht(t),t}function Cn(e){const t=ze(e);return t.equals=yt,t}function At(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n0&&!St&&Rt()}return t}function Rt(){St=!1;const e=Array.from(xe);for(const t of e)(t.f&T)!==0&&b(t,U),ge(t)&&ye(t);xe.clear()}function Ur(e,t=1){var n=ue(e),r=t===1?n++:n--;return $(e,n),r}function Me(e){$(e,e.v+1)}function Nt(e,t){var n=e.reactions;if(n!==null)for(var r=be(),s=n.length,a=0;a{if(fe===a)return o();var f=c,l=fe;F(null),ut(a);var u=o();return F(f),ut(l),u};return r&&n.set("length",G(e.length)),new Proxy(e,{defineProperty(o,f,l){(!("value"in l)||l.configurable===!1||l.enumerable===!1||l.writable===!1)&&vn();var u=n.get(f);return u===void 0?u=i(()=>{var v=G(l.value);return n.set(f,v),v}):$(u,l.value,!0),!0},deleteProperty(o,f){var l=n.get(f);if(l===void 0){if(f in o){const u=i(()=>G(E));n.set(f,u),Me(s)}}else $(l,E),Me(s);return!0},get(o,f,l){if(f===ie)return e;var u=n.get(f),v=f in o;if(u===void 0&&(!v||he(o,f)?.writable)&&(u=i(()=>{var h=de(v?o[f]:E),p=G(h);return p}),n.set(f,u)),u!==void 0){var d=ue(u);return d===E?void 0:d}return Reflect.get(o,f,l)},getOwnPropertyDescriptor(o,f){var l=Reflect.getOwnPropertyDescriptor(o,f);if(l&&"value"in l){var u=n.get(f);u&&(l.value=ue(u))}else if(l===void 0){var v=n.get(f),d=v?.v;if(v!==void 0&&d!==E)return{enumerable:!0,configurable:!0,value:d,writable:!0}}return l},has(o,f){if(f===ie)return!0;var l=n.get(f),u=l!==void 0&&l.v!==E||Reflect.has(o,f);if(l!==void 0||_!==null&&(!u||he(o,f)?.writable)){l===void 0&&(l=i(()=>{var d=u?de(o[f]):E,h=G(d);return h}),n.set(f,l));var v=ue(l);if(v===E)return!1}return u},set(o,f,l,u){var v=n.get(f),d=f in o;if(r&&f==="length")for(var h=l;hG(E)),n.set(h+"",p))}if(v===void 0)(!d||he(o,f)?.writable)&&(v=i(()=>G(void 0)),$(v,de(l)),n.set(f,v));else{d=v.v!==E;var W=i(()=>de(l));$(v,W)}var ne=Reflect.getOwnPropertyDescriptor(o,f);if(ne?.set&&ne.set.call(u,l),!d){if(r&&typeof f=="string"){var tt=n.get("length"),Ie=Number(f);Number.isInteger(Ie)&&Ie>=tt.v&&$(tt,Ie+1)}Me(s)}return!0},ownKeys(o){ue(s);var f=Reflect.ownKeys(o).filter(v=>{var d=n.get(v);return d===void 0||d.v!==E});for(var[l,u]of n)u.v!==E&&!(l in o)&&f.push(l);return f},setPrototypeOf(){dn()}})}function at(e){try{if(e!==null&&typeof e=="object"&&ie in e)return e[ie]}catch{}return e}function Wr(e,t){return Object.is(at(e),at(t))}var it,Pn,Ot,Dt,Ct;function Gr(){if(it===void 0){it=window,Pn=document,Ot=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;Dt=he(t,"firstChild").get,Ct=he(t,"nextSibling").get,nt(e)&&(e.__click=void 0,e.__className=void 0,e.__attributes=null,e.__style=void 0,e.__e=void 0),nt(n)&&(n.__t=void 0)}}function H(e=""){return document.createTextNode(e)}function J(e){return Dt.call(e)}function te(e){return Ct.call(e)}function $r(e,t){if(!k)return J(e);var n=J(w);if(n===null)n=w.appendChild(H());else if(t&&n.nodeType!==Ne){var r=H();return n?.before(r),Z(r),r}return Z(n),n}function Kr(e,t=!1){if(!k){var n=J(e);return n instanceof Comment&&n.data===""?te(n):n}if(t&&w?.nodeType!==Ne){var r=H();return w?.before(r),Z(r),r}return w}function zr(e,t=1,n=!1){let r=k?w:e;for(var s;t--;)s=r,r=te(r);if(!k)return r;if(n&&r?.nodeType!==Ne){var a=H();return r===null?s?.after(a):r.before(a),Z(a),a}return Z(r),r}function Xr(e){e.textContent=""}function Ln(){return!1}function Je(e){var t=c,n=_;F(null),Q(null);try{return e()}finally{F(t),Q(n)}}function It(e){_===null&&c===null&&on(),c!==null&&(c.f&N)!==0&&_===null&&fn(),ve&&an()}function Fn(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function I(e,t,n,r=!0){var s=_;s!==null&&(s.f&P)!==0&&(e|=P);var a={ctx:y,deps:null,nodes_start:null,nodes_end:null,f:e|x,first:null,fn:t,last:null,next:null,parent:s,b:s&&s.b,prev:null,teardown:null,transitions:null,wv:0,ac:null};if(n)try{ye(a),a.f|=We}catch(f){throw C(a),f}else t!==null&&oe(a);if(r){var i=a;if(n&&i.deps===null&&i.teardown===null&&i.nodes_start===null&&i.first===i.last&&(i.f&_e)===0&&(i=i.first,(e&V)!==0&&(e&we)!==0&&i!==null&&(i.f|=we)),i!==null&&(i.parent=s,s!==null&&Fn(i,s),c!==null&&(c.f&g)!==0&&(e&ee)===0)){var o=c;(o.effects??=[]).push(i)}}return a}function Zr(){return c!==null&&!D}function Qe(e){const t=I(Re,null,!1);return b(t,T),t.teardown=e,t}function Jr(e){It();var t=_.f,n=!c&&(t&B)!==0&&(t&We)===0;if(n){var r=y;(r.e??=[]).push(e)}else return Mt(e)}function Mt(e){return I(Be|dt,e,!1)}function Qr(e){return It(),I(Re|dt,e,!0)}function es(e){Y.ensure();const t=I(ee|_e,e,!0);return()=>{C(t)}}function ts(e){Y.ensure();const t=I(ee|_e,e,!0);return(n={})=>new Promise(r=>{n.outro?jt(t,()=>{C(t),r(void 0)}):(C(t),r(void 0))})}function ns(e){return I(Be,e,!1)}function jn(e){return I(Ge|_e,e,!0)}function rs(e,t=0){return I(Re|t,e,!0)}function ss(e,t=[],n=[]){Nn(t,n,r=>{I(Re,()=>e(...r.map(ue)),!0)})}function qn(e,t=0){var n=I(V|t,e,!0);return n}function ft(e,t=!0){return I(B|_e,e,!0,t)}function Pt(e){var t=e.teardown;if(t!==null){const n=ve,r=c;lt(!0),F(null);try{t.call(null)}finally{lt(n),F(r)}}}function Lt(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){const s=n.ac;s!==null&&Je(()=>{s.abort(le)});var r=n.next;(n.f&ee)!==0?n.parent=null:C(n,t),n=r}}function Yn(e){for(var t=e.first;t!==null;){var n=t.next;(t.f&B)===0&&C(t),t=n}}function C(e,t=!0){var n=!1;(t||(e.f&tn)!==0)&&e.nodes_start!==null&&e.nodes_end!==null&&(Hn(e.nodes_start,e.nodes_end),n=!0),Lt(e,t&&!n),Se(e,0),b(e,z);var r=e.transitions;if(r!==null)for(const a of r)a.stop();Pt(e);var s=e.parent;s!==null&&s.first!==null&&Ft(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes_start=e.nodes_end=e.ac=null}function Hn(e,t){for(;e!==null;){var n=e===t?null:te(e);e.remove(),e=n}}function Ft(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function jt(e,t,n=!0){var r=[];qt(e,r,!0),Vn(r,()=>{n&&C(e),t&&t()})}function Vn(e,t){var n=e.length;if(n>0){var r=()=>--n||t();for(var s of e)s.out(r)}else t()}function qt(e,t,n){if((e.f&P)===0){if(e.f^=P,e.transitions!==null)for(const i of e.transitions)(i.is_global||n)&&t.push(i);for(var r=e.first;r!==null;){var s=r.next,a=(r.f&we)!==0||(r.f&B)!==0&&(e.f&V)!==0;qt(r,t,a?n:!1),r=s}}}function Bn(e){Yt(e,!0)}function Yt(e,t){if((e.f&P)!==0){e.f^=P,(e.f&T)===0&&(b(e,x),oe(e));for(var n=e.first;n!==null;){var r=n.next,s=(n.f&we)!==0||(n.f&B)!==0;Yt(n,s?t:!1),n=r}if(e.transitions!==null)for(const a of e.transitions)(a.is_global||t)&&a.in()}}function Un(e,t){for(var n=e.nodes_start,r=e.nodes_end;n!==null;){var s=n===r?null:te(n);t.append(n),n=s}}let ce=!1;function ot(e){ce=e}let ve=!1;function lt(e){ve=e}let c=null,D=!1;function F(e){c=e}let _=null;function Q(e){_=e}let q=null;function Ht(e){c!==null&&(q===null?q=[e]:q.push(e))}let A=null,S=0,R=null;function Wn(e){R=e}let Vt=1,me=0,fe=me;function ut(e){fe=e}let K=!1;function Bt(){return++Vt}function ge(e){var t=e.f;if((t&x)!==0)return!0;if((t&U)!==0){var n=e.deps,r=(t&N)!==0;if(t&g&&(e.f&=~Te),n!==null){var s,a,i=(t&Ee)!==0,o=r&&_!==null&&!K,f=n.length;if((i||o)&&(_===null||(_.f&z)===0)){var l=e,u=l.parent;for(s=0;se.wv)return!0}(!r||_!==null&&!K)&&b(e,T)}return!1}function Ut(e,t,n=!0){var r=e.reactions;if(r!==null&&!q?.includes(e))for(var s=0;s{e.ac.abort(le)}),e.ac=null);try{e.f|=Le;var v=e.fn,d=v(),h=e.deps;if(A!==null){var p;if(Se(e,S),h!==null&&S>0)for(h.length=S+A.length,p=0;pn?.call(this,a))}return e.startsWith("pointer")||e.startsWith("touch")||e==="wheel"?gt(()=>{t.addEventListener(e,s,r)}):t.addEventListener(e,s,r),s}function ds(e,t,n,r={}){var s=$t(t,e,n,r);return()=>{e.removeEventListener(t,s,r)}}function hs(e,t,n,r,s){var a={capture:r,passive:s},i=$t(e,t,n,a);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&Qe(()=>{t.removeEventListener(e,i,a)})}function ps(e){for(var t=0;t{throw ne});throw d}}finally{e.__root=t,delete e.currentTarget,F(u),Q(v)}}}function et(e){var t=document.createElement("template");return t.innerHTML=e.replaceAll("",""),t.content}function L(e,t){var n=_;n.nodes_start===null&&(n.nodes_start=e,n.nodes_end=t)}function ws(e,t){var n=(t&pn)!==0,r=(t&wn)!==0,s,a=!e.startsWith("");return()=>{if(k)return L(w,null),w;s===void 0&&(s=et(a?e:""+e),n||(s=J(s)));var i=r||Ot?document.importNode(s,!0):s.cloneNode(!0);if(n){var o=J(i),f=i.lastChild;L(o,f)}else L(i,i);return i}}function tr(e,t,n="svg"){var r=!e.startsWith(""),s=`<${n}>${r?e:""+e}${n}>`,a;return()=>{if(k)return L(w,null),w;if(!a){var i=et(s),o=J(i);a=J(o)}var f=a.cloneNode(!0);return L(f,f),f}}function ms(e,t){return tr(e,t,"svg")}function ys(e=""){if(!k){var t=H(e+"");return L(t,t),t}var n=w;return n.nodeType!==Ne&&(n.before(n=H()),Z(n)),L(n,n),n}function bs(){if(k)return L(w,null),w;var e=document.createDocumentFragment(),t=document.createComment(""),n=H();return e.append(t,n),L(t,n),e}function gs(e,t){if(k){_.nodes_end=w,wt();return}e!==null&&e.before(t)}class nr{anchor;#t=new Map;#n=new Map;#e=new Map;#r=!0;constructor(t,n=!0){this.anchor=t,this.#r=n}#a=()=>{var t=m;if(this.#t.has(t)){var n=this.#t.get(t),r=this.#n.get(n);if(r)Bn(r);else{var s=this.#e.get(n);s&&(this.#n.set(n,s.effect),this.#e.delete(n),s.fragment.lastChild.remove(),this.anchor.before(s.fragment),r=s.effect)}for(const[a,i]of this.#t){if(this.#t.delete(a),a===t)break;const o=this.#e.get(i);o&&(C(o.effect),this.#e.delete(i))}for(const[a,i]of this.#n){if(a===n)continue;const o=()=>{if(Array.from(this.#t.values()).includes(a)){var l=document.createDocumentFragment();Un(i,l),l.append(H()),this.#e.set(a,{effect:i,fragment:l})}else C(i);this.#n.delete(a)};this.#r||!r?jt(i,o,!1):o()}}};#i=t=>{this.#t.delete(t);const n=Array.from(this.#t.values());for(const[r,s]of this.#e)n.includes(r)||(C(s.effect),this.#e.delete(r))};ensure(t,n){var r=m,s=Ln();if(n&&!this.#n.has(t)&&!this.#e.has(t))if(s){var a=document.createDocumentFragment(),i=H();a.append(i),this.#e.set(t,{effect:ft(()=>n(i)),fragment:a})}else this.#n.set(t,ft(()=>n(this.anchor)));if(this.#t.set(r,t),s){for(const[o,f]of this.#n)o===t?r.skipped_effects.delete(f):r.skipped_effects.add(f);for(const[o,f]of this.#e)o===t?r.skipped_effects.delete(f.effect):r.skipped_effects.add(f.effect);r.oncommit(this.#a),r.ondiscard(this.#i)}else k&&(this.anchor=w),this.#a()}}function Es(e,t,...n){var r=new nr(e);qn(()=>{const s=t()??null;r.ensure(s,s&&(a=>s(a,...n)))},we)}function Ts(e){return(t,...n)=>{var r=e(...n),s;if(k)s=w,wt();else{var a=r.render().trim(),i=et(a);s=J(i),t.before(s)}const o=r.setup?.(s);L(s,s),typeof o=="function"&&Qe(o)}}export{w as $,je as A,Hr as B,jr as C,En as D,kn as E,Tn as F,is as G,as as H,qn as I,we as J,nr as K,k as L,wt as M,ps as N,Ir as O,G as P,de as Q,$ as R,H as S,yr as T,Z as U,J as V,Cn as W,Pr as X,yn as Y,Mr as Z,pt as _,gs as a,or as a$,ht as a0,bn as a1,ft as a2,Ln as a3,wr as a4,mr as a5,m as a6,He as a7,Br as a8,Ze as a9,fr as aA,Zr as aB,rs as aC,Me as aD,Y as aE,Q as aF,F as aG,ke as aH,Sn as aI,Un as aJ,rt as aK,Ae as aL,pr as aM,_e as aN,Ue as aO,Dr as aP,tn as aQ,mn as aR,Gr as aS,$e as aT,vr as aU,Jn as aV,Qn as aW,ts as aX,er as aY,_s as aZ,Ke as a_,rr as aa,Bn as ab,jt as ac,gr as ad,P as ae,gt as af,C as ag,br as ah,te as ai,qt as aj,Xr as ak,Vn as al,_ as am,cr as an,Rr as ao,L as ap,vs as aq,Qe as ar,hs as as,it as at,Qr as au,en as av,ir as aw,os as ax,ze as ay,ms as az,Yr as b,Xt as b0,ys as b1,ns as b2,ie as b3,he as b4,hr as b5,kr as b6,ve as b7,z as b8,Ar as b9,Tr as ba,Er as bb,sr as bc,Ur as bd,xr as be,Or as bf,Wr as bg,Nn as bh,Sr as bi,_t as bj,lr as bk,Nr as bl,Zt as bm,ls as bn,$t as bo,cs as bp,E as bq,us as br,gn as bs,ds as bt,es as bu,Pn as bv,bs as c,ws as d,$r as e,Kr as f,zr as g,ue as h,Lr as i,Jr as j,c as k,nn as l,_r as m,ar as n,y as o,qr as p,Kt as q,Cr as r,Es as s,ss as t,Vr as u,dr as v,Oe as w,fs as x,Fr as y,Ts as z};
diff --git a/_app/immutable/chunks/ByzbuU5l.js b/_app/immutable/chunks/ByzbuU5l.js
new file mode 100644
index 0000000..3eab34f
--- /dev/null
+++ b/_app/immutable/chunks/ByzbuU5l.js
@@ -0,0 +1 @@
+import{i as a}from"./BbMFilaF.js";a();
diff --git a/_app/immutable/chunks/Bzx_dmoR.js b/_app/immutable/chunks/Bzx_dmoR.js
new file mode 100644
index 0000000..2a00b42
--- /dev/null
+++ b/_app/immutable/chunks/Bzx_dmoR.js
@@ -0,0 +1 @@
+import{p as i,i as j,r as P,b}from"./CPdWO9vA.js";import{p as T,c as q,f as A,a as d,b as C,d as k,e as f,s as g,n as h,r as m}from"./BbMFilaF.js";import{a as x}from"./DZYZrkxG.js";import{c as p}from"./BECF2Gun.js";import{T as D}from"./Czk6ojrB.js";const y=D({base:"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",variants:{variant:{default:"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",destructive:"bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white",outline:"bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border",secondary:"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",link:"text-primary underline-offset-4 hover:underline",nav:"text-primary text-md hover:text-lg font-light hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50"},size:{default:"h-9 px-4 py-2 has-[>svg]:px-3",sm:"h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",lg:"h-10 rounded-md px-6 has-[>svg]:px-4",icon:"size-9","icon-sm":"size-8","icon-lg":"size-10"}},defaultVariants:{variant:"default",size:"default"}});var E=k(""),F=k("");function L(_,e){T(e,!0);let o=i(e,"variant",3,"default"),l=i(e,"size",3,"default"),n=i(e,"ref",15,null),c=i(e,"href",3,void 0),z=i(e,"type",3,"button"),v=P(e,["$$slots","$$events","$$legacy","class","variant","size","ref","href","type","disabled","children"]);var u=q(),w=A(u);{var B=r=>{var a=E();x(a,t=>({"data-slot":"button",class:t,href:e.disabled?void 0:c(),"aria-disabled":e.disabled,role:e.disabled?"link":void 0,tabindex:e.disabled?-1:void 0,...v}),[()=>p(y({variant:o(),size:l()}),e.class)]);var s=f(a);g(s,()=>e.children??h),m(a),b(a,t=>n(t),()=>n()),d(r,a)},V=r=>{var a=F();x(a,t=>({"data-slot":"button",class:t,type:z(),disabled:e.disabled,...v}),[()=>p(y({variant:o(),size:l()}),e.class)]);var s=f(a);g(s,()=>e.children??h),m(a),b(a,t=>n(t),()=>n()),d(r,a)};j(w,r=>{c()?r(B):r(V,!1)})}d(_,u),C()}export{L as B};
diff --git a/_app/immutable/chunks/CPdWO9vA.js b/_app/immutable/chunks/CPdWO9vA.js
new file mode 100644
index 0000000..2979ad8
--- /dev/null
+++ b/_app/immutable/chunks/CPdWO9vA.js
@@ -0,0 +1 @@
+import{I as M,L as I,M as U,J as C,X as j,Y as F,Z as $,U as q,K as Z,_ as y,b2 as z,aC as G,x as D,af as H,b3 as x,b4 as S,b5 as J,b6 as L,h as w,ay as Q,W as V,Q as W,R as X,b7 as m,am as R,b8 as k,a9 as ee,b9 as re,w as ne,ba as se,bb as ie,bc as _,a$ as N,bd as T,aF as A,be as te}from"./BbMFilaF.js";function pe(e,r,s=!1){I&&U();var n=new Z(e),i=s?C:0;function t(f,a){if(I){const v=j(e)===F;if(f===v){var l=$();q(l),n.anchor=l,y(!1),n.ensure(f,a),y(!0);return}}n.ensure(f,a)}M(()=>{var f=!1;r((a,l=!0)=>{f=!0,t(l,a)}),f||t(!1,null)},i)}function g(e,r){return e===r||e?.[x]===r}function _e(e={},r,s,n){return z(()=>{var i,t;return G(()=>{i=t,t=[],D(()=>{e!==s(...t)&&(r(e,...t),i&&g(s(...i),e)&&r(null,...i))})}),()=>{H(()=>{t&&g(s(...t),e)&&r(null,...t)})}}),e}let h=!1;function ue(e){var r=h;try{return h=!1,[e(),h]}finally{h=r}}const ae={get(e,r){if(!e.exclude.includes(r))return e.props[r]},set(e,r){return!1},getOwnPropertyDescriptor(e,r){if(!e.exclude.includes(r)&&r in e.props)return{enumerable:!0,configurable:!0,value:e.props[r]}},has(e,r){return e.exclude.includes(r)?!1:r in e.props},ownKeys(e){return Reflect.ownKeys(e.props).filter(r=>!e.exclude.includes(r))}};function ve(e,r,s){return new Proxy({props:e,exclude:r},ae)}const fe={get(e,r){if(!e.exclude.includes(r))return w(e.version),r in e.special?e.special[r]():e.props[r]},set(e,r,s){if(!(r in e.special)){var n=R;try{A(e.parent_effect),e.special[r]=ce({get[r](){return e.props[r]}},r,L)}finally{A(n)}}return e.special[r](s),T(e.version),!0},getOwnPropertyDescriptor(e,r){if(!e.exclude.includes(r)&&r in e.props)return{enumerable:!0,configurable:!0,value:e.props[r]}},deleteProperty(e,r){return e.exclude.includes(r)||(e.exclude.push(r),T(e.version)),!0},has(e,r){return e.exclude.includes(r)?!1:r in e.props},ownKeys(e){return Reflect.ownKeys(e.props).filter(r=>!e.exclude.includes(r))}};function be(e,r){return new Proxy({props:e,exclude:r,special:{},version:ee(0),parent_effect:R},fe)}const le={get(e,r){let s=e.props.length;for(;s--;){let n=e.props[s];if(_(n)&&(n=n()),typeof n=="object"&&n!==null&&r in n)return n[r]}},set(e,r,s){let n=e.props.length;for(;n--;){let i=e.props[n];_(i)&&(i=i());const t=S(i,r);if(t&&t.set)return t.set(s),!0}return!1},getOwnPropertyDescriptor(e,r){let s=e.props.length;for(;s--;){let n=e.props[s];if(_(n)&&(n=n()),typeof n=="object"&&n!==null&&r in n){const i=S(n,r);return i&&!i.configurable&&(i.configurable=!0),i}}},has(e,r){if(r===x||r===N)return!1;for(let s of e.props)if(_(s)&&(s=s()),s!=null&&r in s)return!0;return!1},ownKeys(e){const r=[];for(let s of e.props)if(_(s)&&(s=s()),!!s){for(const n in s)r.includes(n)||r.push(n);for(const n of Object.getOwnPropertySymbols(s))r.includes(n)||r.push(n)}return r}};function he(...e){return new Proxy({props:e},le)}function ce(e,r,s,n){var i=!ne||(s&se)!==0,t=(s&re)!==0,f=(s&te)!==0,a=n,l=!0,v=()=>(l&&(l=!1,a=f?D(n):n),a),o;if(t){var B=x in e||N in e;o=S(e,r)?.set??(B&&r in e?u=>e[r]=u:void 0)}var d,O=!1;t?[d,O]=ue(()=>e[r]):d=e[r],d===void 0&&n!==void 0&&(d=v(),o&&(i&&J(),o(d)));var c;if(i?c=()=>{var u=e[r];return u===void 0?v():(l=!0,u)}:c=()=>{var u=e[r];return u!==void 0&&(a=void 0),u===void 0?a:u},i&&(s&L)===0)return c;if(o){var K=e.$$legacy;return(function(u,b){return arguments.length>0?((!i||!b||K||O)&&o(b?c():u),u):c()})}var P=!1,p=((s&ie)!==0?Q:V)(()=>(P=!1,c()));t&&w(p);var Y=R;return(function(u,b){if(arguments.length>0){const E=b?w(p):i&&t?W(u):u;return X(p,E),P=!0,a!==void 0&&(a=E),u}return m&&P||(Y.f&k)!==0?p.v:w(p)})}const oe="5";typeof window<"u"&&((window.__svelte??={}).v??=new Set).add(oe);export{_e as b,pe as i,be as l,ce as p,ve as r,he as s};
diff --git a/_app/immutable/chunks/CVfNxELp.js b/_app/immutable/chunks/CVfNxELp.js
new file mode 100644
index 0000000..b75960e
--- /dev/null
+++ b/_app/immutable/chunks/CVfNxELp.js
@@ -0,0 +1,44 @@
+import{l as ae,s as oe,p as S,r as B,b as D,i as X}from"./CPdWO9vA.js";import{c as A,f as u,a as o,p as w,d,e as n,s as F,n as G,r as i,b as P,t as m,h as O,u as Z,g,O as se,b1 as le}from"./BbMFilaF.js";import{s as M}from"./DmP2n0k_.js";import{e as ie,i as ce}from"./BGFanmKd.js";import{g as ne,a as de}from"./Iu6NYhtk.js";import{c as y}from"./BaThn6Za.js";import{a as J,d as j,s as ve}from"./DZYZrkxG.js";import{C as fe,a as ue,b as me}from"./DZHFmSoN.js";import{c as K}from"./BECF2Gun.js";import{C as ge}from"./1Ez0xvU2.js";import"./ByzbuU5l.js";import{I as pe,s as _e}from"./B6MtVlrN.js";import{S as he}from"./B1wPNbHL.js";function be(l,e){const a=ae(e,["children","$$slots","$$events","$$legacy"]);/**
+ * @license lucide-svelte v0.545.0 - ISC
+ *
+ * ISC License
+ *
+ * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * ---
+ *
+ * The MIT License (MIT) (for portions derived from Feather)
+ *
+ * Copyright (c) 2013-2023 Cole Bemis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */const s=[["path",{d:"M15 3h6v6"}],["path",{d:"M10 14 21 3"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"}]];pe(l,oe({name:"external-link"},()=>a,{get iconNode(){return s},children:(r,c)=>{var t=A(),C=u(t);_e(C,e,"default",{}),o(r,t)},$$slots:{default:!0}}))}var xe=d("");function ye(l,e){w(e,!0);let a=S(e,"ref",15,null),s=B(e,["$$slots","$$events","$$legacy","ref","class","children"]);var r=xe();J(r,t=>({"data-slot":"card-description",class:t,...s}),[()=>K("text-sm text-muted-foreground",e.class)]);var c=n(r);F(c,()=>e.children??G),i(r),D(r,t=>a(t),()=>a()),o(l,r),P()}var je=d("");function we(l,e){w(e,!0);let a=S(e,"ref",15,null),s=B(e,["$$slots","$$events","$$legacy","ref","class","children"]);var r=je();J(r,t=>({"data-slot":"card-footer",class:t,...s}),[()=>K("flex items-center px-6 [.border-t]:pt-6",e.class)]);var c=n(r);F(c,()=>e.children??G),i(r),D(r,t=>a(t),()=>a()),o(l,r),P()}var Pe=d("");function Ce(l,e){w(e,!0);let a=S(e,"ref",15,null),s=B(e,["$$slots","$$events","$$legacy","ref","class","children"]);var r=Pe();J(r,t=>({"data-slot":"card-title",class:t,...s}),[()=>K("leading-none font-semibold",e.class)]);var c=n(r);F(c,()=>e.children??G),i(r),D(r,t=>a(t),()=>a()),o(l,r),P()}var $e=d('');function Re(l,e){w(e,!0);const a=Z(()=>e.youtubeURL.split("v=")[1].split("&")[0]);var s=$e();m(()=>j(s,"src",`https://www.youtube.com/embed/${O(a)??""};controls=0`)),o(l,s),P()}var ke=d(""),Le=d("
');function G(u,x){let v=.5,e=C(x,"topLevel",3,!0);var i=z(),_=o(i);{var w=a=>{var g=S();p(a,g)},b=a=>{var g=q();p(a,g)};L(_,a=>{e()?a(w):a(b,!1)})}var d=m(_,2),t=o(d),y=o(t);f(y,{path:"/logos/svg/logo-github.svg",scale:v}),s(t);var l=m(t,2),k=o(l);f(k,{path:"/logos/svg/logo-youtube.svg",scale:v}),s(l);var c=m(l,2),F=o(c);f(F,{path:"/logos/svg/logo-linkedin.svg",scale:v}),s(c),s(d),s(i),j(()=>{r(d,1,`flex flex-row justify-center gap-x-8 ${e()?"md:gap-x-12":""}`),r(t,1,n(e()?"md:scale-125":"")),r(l,1,n(e()?"md:scale-125":"")),r(c,1,n(e()?"md:scale-125":""))}),p(u,i)}export{G as F};
diff --git a/_app/immutable/entry/app.CvUz7jx4.js b/_app/immutable/entry/app.CvUz7jx4.js
new file mode 100644
index 0000000..ebfaa0a
--- /dev/null
+++ b/_app/immutable/entry/app.CvUz7jx4.js
@@ -0,0 +1,2 @@
+const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["../nodes/0.CnpaX_bC.js","../chunks/CPdWO9vA.js","../chunks/BbMFilaF.js","../chunks/BP4hYQWv.js","../chunks/DmP2n0k_.js","../chunks/DZYZrkxG.js","../chunks/BGFanmKd.js","../chunks/CnyALC89.js","../chunks/BaThn6Za.js","../chunks/ByzbuU5l.js","../chunks/Bzx_dmoR.js","../chunks/BECF2Gun.js","../chunks/Czk6ojrB.js","../chunks/B6MtVlrN.js","../chunks/B6-ZdAj4.js","../chunks/D9pCFzY-.js","../chunks/YuKHTIMG.js","../chunks/Iu6NYhtk.js","../assets/0.CgRkwH39.css","../nodes/1.C2dMvvnE.js","../chunks/DZHFmSoN.js","../chunks/B1wPNbHL.js","../nodes/2.DYQOGslj.js","../chunks/h4yY_LIJ.js","../chunks/1Ez0xvU2.js","../nodes/3.Dv4ePjt5.js","../nodes/4.Bu6R88KI.js","../chunks/CVfNxELp.js","../nodes/5.DmA1mkAy.js","../chunks/DhHeKD4j.js","../nodes/6.Bth7ph55.js","../nodes/7.C29PjVsk.js"])))=>i.map(i=>d[i]);
+import{R as L,a$ as B,h as d,A as G,b0 as H,a8 as W,p as Y,au as z,j as F,P as A,H as J,d as C,f as w,g as K,a as b,b as Q,c as k,u as x,e as X,r as Z,b1 as $,t as tt}from"../chunks/BbMFilaF.js";import{h as et,m as rt,u as st,s as at}from"../chunks/DmP2n0k_.js";import{p as S,i as T,b as D}from"../chunks/CPdWO9vA.js";import{o as nt}from"../chunks/BP4hYQWv.js";import{c as I}from"../chunks/BaThn6Za.js";function ot(i){return class extends it{constructor(t){super({component:i,...t})}}}class it{#e;#t;constructor(t){var a=new Map,c=(r,e)=>{var s=W(e,!1,!1);return a.set(r,s),s};const u=new Proxy({...t.props||{},$$events:{}},{get(r,e){return d(a.get(e)??c(e,Reflect.get(r,e)))},has(r,e){return e===B?!0:(d(a.get(e)??c(e,Reflect.get(r,e))),Reflect.has(r,e))},set(r,e,s){return L(a.get(e)??c(e,s),s),Reflect.set(r,e,s)}});this.#t=(t.hydrate?et:rt)(t.component,{target:t.target,anchor:t.anchor,props:u,context:t.context,intro:t.intro??!1,recover:t.recover}),(!t?.props?.$$host||t.sync===!1)&&G(),this.#e=u.$$events;for(const r of Object.keys(this.#t))r==="$set"||r==="$destroy"||r==="$on"||H(this,r,{get(){return this.#t[r]},set(e){this.#t[r]=e},enumerable:!0});this.#t.$set=r=>{Object.assign(u,r)},this.#t.$destroy=()=>{st(this.#t)}}$set(t){this.#t.$set(t)}$on(t,a){this.#e[t]=this.#e[t]||[];const c=(...u)=>a.call(this,...u);return this.#e[t].push(c),()=>{this.#e[t]=this.#e[t].filter(u=>u!==c)}}$destroy(){this.#t.$destroy()}}const ct="modulepreload",ut=function(i,t){return new URL(i,t).href},j={},f=function(t,a,c){let u=Promise.resolve();if(a&&a.length>0){let O=function(o){return Promise.all(o.map(m=>Promise.resolve(m).then(_=>({status:"fulfilled",value:_}),_=>({status:"rejected",reason:_}))))};const e=document.getElementsByTagName("link"),s=document.querySelector("meta[property=csp-nonce]"),R=s?.nonce||s?.getAttribute("nonce");u=O(a.map(o=>{if(o=ut(o,c),o in j)return;j[o]=!0;const m=o.endsWith(".css"),_=m?'[rel="stylesheet"]':"";if(c)for(let h=e.length-1;h>=0;h--){const n=e[h];if(n.href===o&&(!m||n.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${o}"]${_}`))return;const l=document.createElement("link");if(l.rel=m?"stylesheet":ct,m||(l.as="script"),l.crossOrigin="",l.href=o,R&&l.setAttribute("nonce",R),document.head.appendChild(l),m)return new Promise((h,n)=>{l.addEventListener("load",h),l.addEventListener("error",()=>n(new Error(`Unable to preload CSS for ${o}`)))})}))}function r(e){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=e,window.dispatchEvent(s),!s.defaultPrevented)throw e}return u.then(e=>{for(const s of e||[])s.status==="rejected"&&r(s.reason);return t().catch(r)})},Pt={};var lt=C(''),mt=C(" ",1);function dt(i,t){Y(t,!0);let a=S(t,"components",23,()=>[]),c=S(t,"data_0",3,null),u=S(t,"data_1",3,null);z(()=>t.stores.page.set(t.page)),F(()=>{t.stores,t.page,t.constructors,a(),t.form,c(),u(),t.stores.page.notify()});let r=A(!1),e=A(!1),s=A(null);nt(()=>{const n=t.stores.page.subscribe(()=>{d(r)&&(L(e,!0),J().then(()=>{L(s,document.title||"untitled page",!0)}))});return L(r,!0),n});const R=x(()=>t.constructors[1]);var O=mt(),o=w(O);{var m=n=>{const v=x(()=>t.constructors[0]);var g=k(),P=w(g);I(P,()=>d(v),(E,y)=>{D(y(E,{get data(){return c()},get form(){return t.form},get params(){return t.page.params},children:(p,ht)=>{var V=k(),M=w(V);I(M,()=>d(R),(N,q)=>{D(q(N,{get data(){return u()},get form(){return t.form},get params(){return t.page.params}}),U=>a()[1]=U,()=>a()?.[1])}),b(p,V)},$$slots:{default:!0}}),p=>a()[0]=p,()=>a()?.[0])}),b(n,g)},_=n=>{const v=x(()=>t.constructors[0]);var g=k(),P=w(g);I(P,()=>d(v),(E,y)=>{D(y(E,{get data(){return c()},get form(){return t.form},get params(){return t.page.params}}),p=>a()[0]=p,()=>a()?.[0])}),b(n,g)};T(o,n=>{t.constructors[1]?n(m):n(_,!1)})}var l=K(o,2);{var h=n=>{var v=lt(),g=X(v);{var P=E=>{var y=$();tt(()=>at(y,d(s))),b(E,y)};T(g,E=>{d(e)&&E(P)})}Z(v),b(n,v)};T(l,n=>{d(r)&&n(h)})}b(i,O),Q()}const pt=ot(dt),Rt=[()=>f(()=>import("../nodes/0.CnpaX_bC.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]),import.meta.url),()=>f(()=>import("../nodes/1.C2dMvvnE.js"),__vite__mapDeps([19,1,2,9,4,8,20,5,11,16,14,3,21]),import.meta.url),()=>f(()=>import("../nodes/2.DYQOGslj.js"),__vite__mapDeps([22,1,2,23,5,24,9,3,4,6,13,21,8,11,10,12]),import.meta.url),()=>f(()=>import("../nodes/3.Dv4ePjt5.js"),__vite__mapDeps([25,1,2,5,23,24,21,7,9,13,6]),import.meta.url),()=>f(()=>import("../nodes/4.Bu6R88KI.js"),__vite__mapDeps([26,1,2,27,4,6,17,8,5,20,11,24,9,13,21]),import.meta.url),()=>f(()=>import("../nodes/5.DmA1mkAy.js"),__vite__mapDeps([28,17,29,14,3,2,4,1,5,6,12,11,16,15,8,21,24]),import.meta.url),()=>f(()=>import("../nodes/6.Bth7ph55.js"),__vite__mapDeps([30,1,2,27,4,6,17,8,5,20,11,24,9,13,21]),import.meta.url),()=>f(()=>import("../nodes/7.C29PjVsk.js"),__vite__mapDeps([31,17,29,14,3,2,4,1,5,6,12,11,16,15,8,21,24]),import.meta.url)],Ot=[],wt={"/":[-3],"/contact":[3],"/games":[-5],"/games/[slug]":[5],"/tools":[-7],"/tools/[slug]":[7]},ft={handleError:(({error:i})=>{console.error(i)}),reroute:(()=>{}),transport:{}},_t=Object.fromEntries(Object.entries(ft.transport).map(([i,t])=>[i,t.decode])),Lt=!1,At=(i,t)=>_t[i](t);export{At as decode,_t as decoders,wt as dictionary,Lt as hash,ft as hooks,Pt as matchers,Rt as nodes,pt as root,Ot as server_loads};
diff --git a/_app/immutable/entry/start.B1mUjQiF.js b/_app/immutable/entry/start.B1mUjQiF.js
new file mode 100644
index 0000000..4276017
--- /dev/null
+++ b/_app/immutable/entry/start.B1mUjQiF.js
@@ -0,0 +1 @@
+import{l as o,d as r}from"../chunks/B6-ZdAj4.js";export{o as load_css,r as start};
diff --git a/_app/immutable/nodes/0.CnpaX_bC.js b/_app/immutable/nodes/0.CnpaX_bC.js
new file mode 100644
index 0000000..a689ca6
--- /dev/null
+++ b/_app/immutable/nodes/0.CnpaX_bC.js
@@ -0,0 +1,52 @@
+import{p as O,i as y,b as Je,s as ne,r as $e,l as _n}from"../chunks/CPdWO9vA.js";import{o as me}from"../chunks/BP4hYQWv.js";import{p as kt,d as Z,e as z,a as r,r as k,t as U,b as Pt,bt as _t,E as yn,D as We,F as xn,P as E,Q as qt,j as bt,R as i,h as e,u as m,x as Ft,g as ut,as as tn,s as dt,n as gt,c as w,f as v,N as en,b1 as ze,az as Wt,au as nn,bu as Ne,O as In,bv as Tn,at as Sn}from"../chunks/BbMFilaF.js";import{c as ae,s as ee,a as Cn}from"../chunks/DmP2n0k_.js";import{c as Et,s as vt,d as M,b as Pe,a as En,S as Bn,f as De}from"../chunks/DZYZrkxG.js";import{e as he}from"../chunks/BGFanmKd.js";import{a as J,c as Ct,S as pn}from"../chunks/CnyALC89.js";import{c as Ot}from"../chunks/BaThn6Za.js";import"../chunks/ByzbuU5l.js";import{B as Mn}from"../chunks/Bzx_dmoR.js";import{i as on,I as An,s as kn}from"../chunks/B6MtVlrN.js";import{b as Pn,r as Dn,a as Ln}from"../chunks/B6-ZdAj4.js";import{g as Hn,a as On,s as zn,b as Nn,c as Rn,d as Vn}from"../chunks/D9pCFzY-.js";import{p as le}from"../chunks/YuKHTIMG.js";import{p as Fn}from"../chunks/Iu6NYhtk.js";function qn(...n){return Pn+Dn(n[0],n[1])}const Wn=!0,Ga=Object.freeze(Object.defineProperty({__proto__:null,prerender:Wn},Symbol.toStringTag,{value:"Module"})),Zn="data:image/svg+xml,%3csvg%20id='Layer_2'%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201000%201000'%3e%3cdefs%3e%3cstyle%3e.cls-1{fill:%23fff;}.cls-2{fill:%23ee0000;}%3c/style%3e%3c/defs%3e%3cg%20id='Layer_1-2'%3e%3ccircle%20class='cls-2'%20cx='500'%20cy='500'%20r='500'/%3e%3cpath%20d='m678.56,694.12c-9.14-61.15-33.1-128.26-8.62-220.63,14.85-56,41.41-98.38,62.35-126.13,45.52-27.75,91.02-55.5,136.54-83.25-66.3-6.26-132.6-12.52-198.89-18.79-16.63-10.84-137.31-86.94-263.86-41.49-133.58,47.98-220.94,212.44-183.98,390.12-51.02,53.24-102.04,106.48-153.07,159.72,86.96,147.42,247.41,246.33,430.96,246.33,45.38,0,89.35-6.05,131.14-17.38,35.79-79.96,63.42-181.47,47.42-288.51Z'/%3e%3cpath%20class='cls-1'%20d='m732.3,347.35c-20.86,1.39-271.12,21.23-331.63,178.57-6.64,17.29-11.66,37.94-12.75,62.36,20.77,3.17,50.79,10.01,83.62,26.22,73.84,36.45,110.6,94.95,124,116.92,61.49,100.76,47.29,205.56,41.91,236.57,32.6-77.54,56.14-173.24,41.12-273.87-9.14-61.15-33.1-128.26-8.62-220.63,14.85-56,41.42-98.38,62.36-126.13Z'/%3e%3ccircle%20class='cls-1'%20cx='488.32'%20cy='311.43'%20r='33.61'/%3e%3c/g%3e%3c/svg%3e",jn=Array(12).fill(0);var Un=Z(''),Yn=Z('
');function Ea(n){var t=Ca(),o=z(t),a=z(o);Ta(a,{});var s=ut(a,2);xa(s),k(o);var c=ut(o,2);fn(c,{flipY:!0,className:"place-self-start z-50 h-12 "}),k(t),r(n,t)}var Ba=Z('');function pa(n){var t=Ba(),o=z(t);fn(o,{flipY:!0,className:"h-footer max-h-footer"}),k(t),r(n,t)}function Ma(n,t){const o=_n(t,["children","$$slots","$$events","$$legacy"]);/**
+ * @license lucide-svelte v0.545.0 - ISC
+ *
+ * ISC License
+ *
+ * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * ---
+ *
+ * The MIT License (MIT) (for portions derived from Feather)
+ *
+ * Copyright (c) 2013-2023 Cole Bemis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */const a=[["path",{d:"m17 11-5-5-5 5"}],["path",{d:"m17 18-5-5-5 5"}]];An(n,ne({name:"chevrons-up"},()=>o,{get iconNode(){return a},children:(s,c)=>{var f=w(),x=v(f);kn(x,t,"default",{}),r(s,f)},$$slots:{default:!0}}))}var Aa=Z('');function ka(n,t){kt(t,!1);let o=Hn(),a=On();function s(){const P=o();if(!P){console.warn("Scroll container not found in context.");return}const A=a();if(!A){console.warn("Content container not found in context.");return}P&&A&&zn(P,A)}on();var c=Aa();c.__click=()=>s();var f=z(c),x=z(f);Ma(x,{}),k(f),k(c),r(n,c),Pt()}en(["click"]);var Pa=Z(''),Da=Z('
',1);function Ja(n,t){kt(t,!0);let o=m(()=>{if(le.url.pathname.startsWith("/games/")||le.url.pathname.startsWith("/tools/")){const K=le.url.pathname.split("/")[2],rt=Fn.find(It=>It.slug===K)?.title;return rt?`${rt} | ${zt}`:zt}return dn.find(K=>K.path===le.url.pathname)?.title??zt}),a=E(void 0),s=E(void 0),c=E(void 0),f=E(void 0);Nn(()=>e(s)),Rn(()=>e(a)),Vn(()=>e(c));let x=E(void 0),P=m(()=>e(x)&&e(s)?e(x)-e(s).offsetHeight:0),A=m(()=>e(f)?e(f).offsetHeight>e(P):!1);me(()=>{ft(),R()}),Ln(()=>{R()});function R(){i(f,e(c)?.querySelector("section:first-child")??void 0,!0)}function ft(){i(x,window.innerHeight,!0)}function yt(j){return K=>j(K)}var Bt=Da();Cn(j=>{var K=Pa();U(()=>{Tn.title=e(o)??"",M(K,"href",Zn)}),r(j,K)}),tn("resize",Sn,ft);var Y=v(Bt);_a(Y,{});var at=ut(Y,2),pt=z(at);ka(pt,{}),k(at);var $=ut(at,2),et=z($),Dt=z(et);Ea(Dt),k(et),De(et,()=>yt(j=>i(s,j,!0)));var xt=ut(et,2),Zt=z(xt);dt(Zt,()=>t.children??gt),k(xt),De(xt,()=>yt(j=>i(c,j,!0)));var I=ut(xt,2),st=z(I);pa(st),k(I),k($),De($,()=>yt(j=>i(a,j,!0))),U(()=>vt(xt,1,`[&.first-section-overflowing>:first-child]:snap-start [&>section]:min-h-page [&>section]:snap-start [&>section]:first:min-h-page-without-header [&>section]:first:snap-end [&>section]:last:min-h-page-without-footer [&>section]:only:min-h-page-without-header-and-footer [&>section]:only:snap-start ${e(A)?"first-section-overflowing":""}`)),r(n,Bt),Pt()}export{Ja as component,Ga as universal};
diff --git a/_app/immutable/nodes/1.C2dMvvnE.js b/_app/immutable/nodes/1.C2dMvvnE.js
new file mode 100644
index 0000000..7f8a701
--- /dev/null
+++ b/_app/immutable/nodes/1.C2dMvvnE.js
@@ -0,0 +1 @@
+import{i as D}from"../chunks/CPdWO9vA.js";import"../chunks/ByzbuU5l.js";import{p as F,c as G,f,d as a,O as H,a as r,g,e as x,r as b,h as l,u as C,t as P,b as I}from"../chunks/BbMFilaF.js";import{s as w}from"../chunks/DmP2n0k_.js";import{c}from"../chunks/BaThn6Za.js";import{C as J,a as K,b as L}from"../chunks/DZHFmSoN.js";import{p as S}from"../chunks/YuKHTIMG.js";import{S as N}from"../chunks/B1wPNbHL.js";var Q=a('
Oops! 😦
Something went wrong while trying to load this page.
',1),R=a("
"),T=a("
",1),U=a(" ",1);function V(e,s){F(s,!0);const u=C(()=>S.status),_=C(()=>S.error?.message??void 0);var h=G(),y=f(h);c(y,()=>L,(O,j)=>{j(O,{children:(k,W)=>{var v=U(),$=f(v);c($,()=>J,(o,n)=>{n(o,{children:(p,M)=>{var t=Q();H(2),r(p,t)},$$slots:{default:!0}})});var E=g($,2);c(E,()=>K,(o,n)=>{n(o,{children:(p,M)=>{var t=T(),i=f(t),q=x(i,!0);b(i);var z=g(i,2);{var A=m=>{var d=R(),B=x(d,!0);b(d),P(()=>w(B,l(_))),r(m,d)};D(z,m=>{l(_)&&m(A)})}P(()=>w(q,l(u))),r(p,t)},$$slots:{default:!0}})}),r(k,v)},$$slots:{default:!0}})}),r(e,h),I()}function or(e){N(e,{class:"justify-center px-8",children:(s,u)=>{V(s,{})},$$slots:{default:!0}})}export{or as component};
diff --git a/_app/immutable/nodes/2.DYQOGslj.js b/_app/immutable/nodes/2.DYQOGslj.js
new file mode 100644
index 0000000..3202839
--- /dev/null
+++ b/_app/immutable/nodes/2.DYQOGslj.js
@@ -0,0 +1,130 @@
+import{l as te,s as ee,p as W,i as Kt,r as ne,b as se}from"../chunks/CPdWO9vA.js";import{b2 as Qe,x as Xe,aC as Ye,ax as We,bs as Ze,c as Lt,f as Z,a as j,p as vt,P as gt,Q as Tt,j as re,h as A,R as nt,d as H,as as Ae,at as tn,e as O,g as q,t as rt,b as ht,H as Jt,r as C,O as Qt,N as ke,E as en,D as nn,F as sn,s as oe,n as ie,u as wt}from"../chunks/BbMFilaF.js";import{F as rn}from"../chunks/h4yY_LIJ.js";import"../chunks/ByzbuU5l.js";import{o as on}from"../chunks/BP4hYQWv.js";import{s as at}from"../chunks/DmP2n0k_.js";import{e as ft,i as It}from"../chunks/BGFanmKd.js";import{s as pt,b as mt,c as _t,d as zt,a as ae}from"../chunks/DZYZrkxG.js";import{I as ce,s as le}from"../chunks/B6MtVlrN.js";import{S as Ft}from"../chunks/B1wPNbHL.js";import{C as je}from"../chunks/1Ez0xvU2.js";import{c as $t}from"../chunks/BaThn6Za.js";import{c as ue}from"../chunks/BECF2Gun.js";import"../chunks/Bzx_dmoR.js";function Xt(e,t,n){Qe(()=>{var s=Xe(()=>t(e,n?.())||{});if(n&&s?.update){var a=!1,o={};Ye(()=>{var r=n();We(r),a&&Ze(o,r)&&(o=r,s.update(r))}),a=!0}if(s?.destroy)return()=>s.destroy()})}function an(e,t){const n=te(t,["children","$$slots","$$events","$$legacy"]);/**
+ * @license lucide-svelte v0.545.0 - ISC
+ *
+ * ISC License
+ *
+ * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * ---
+ *
+ * The MIT License (MIT) (for portions derived from Feather)
+ *
+ * Copyright (c) 2013-2023 Cole Bemis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */const s=[["circle",{cx:"12.1",cy:"12.1",r:"1"}]];ce(e,ee({name:"dot"},()=>n,{get iconNode(){return s},children:(a,o)=>{var r=Lt(),c=Z(r);le(c,t,"default",{}),j(a,r)},$$slots:{default:!0}}))}function cn(e,t){const n=te(t,["children","$$slots","$$events","$$legacy"]);/**
+ * @license lucide-svelte v0.545.0 - ISC
+ *
+ * ISC License
+ *
+ * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * ---
+ *
+ * The MIT License (MIT) (for portions derived from Feather)
+ *
+ * Copyright (c) 2013-2023 Cole Bemis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */const s=[["path",{d:"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"}],["circle",{cx:"12",cy:"10",r:"3"}]];ce(e,ee({name:"map-pin"},()=>n,{get iconNode(){return s},children:(a,o)=>{var r=Lt(),c=Z(r);le(c,t,"default",{}),j(a,r)},$$slots:{default:!0}}))}function Ee(e,t){const n=te(t,["children","$$slots","$$events","$$legacy"]);/**
+ * @license lucide-svelte v0.545.0 - ISC
+ *
+ * ISC License
+ *
+ * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * ---
+ *
+ * The MIT License (MIT) (for portions derived from Feather)
+ *
+ * Copyright (c) 2013-2023 Cole Bemis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */const s=[["path",{d:"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"}],["path",{d:"M16 9a5 5 0 0 1 0 6"}],["path",{d:"M19.364 18.364a9 9 0 0 0 0-12.728"}]];ce(e,ee({name:"volume-2"},()=>n,{get iconNode(){return s},children:(a,o)=>{var r=Lt(),c=Z(r);le(c,t,"default",{}),j(a,r)},$$slots:{default:!0}}))}var ln=H(""),un=H(""),fn=H(""),dn=H(""),pn=H(""),mn=H(""),gn=H('
');function vn(e,t){vt(t,!0);let n,s,a=gt(""),o=gt(Tt([])),r=gt(Tt([])),c=!0;function i(b){n=b}function l(b){s=b}on(()=>(P(),f(),()=>{c=!1})),re(()=>{if(!c)return;const b=A(o).length,D=n?.scrollHeight??NaN;let _="";isNaN(D)?_=`${b}lh`:_=`${D}px`,nt(a,`height: ${_}; transition: height ${v()}ms ease-in-out;`)});let p=W(t,"pregrow",3,!1),u=W(t,"baseStyle",3,"display: flex; flex-direction: column; white-space: pre-line;"),d=W(t,"baseClass",3,""),h=W(t,"defaultLineDelayMs",3,100),m=W(t,"defaultChunkDelayMs",3,50),g=W(t,"charDelayMs",3,50),v=W(t,"growDelayMs",3,500);async function f(){if(c){if(p()){const b=await w(t.message);nt(a,`height: ${b}px;`)}for(const b of t.message.lines){if(!c)return;await x(b)}}}async function x(b){if(!c)return;const D=Tt({chunks:[],class:b.class});if(!p()){const _=await I(b);if(!c)return;nt(a,`height: ${_}px; transition: height ${v()}ms ease-in-out;`)}await M(v()),c&&(nt(o,[...A(o),D],!0),await y(b,D),await M(b.delay??h()))}async function y(b,D){if(c)for(const _ of b.chunks){if(!c)return;const B=Tt({text:"",class:_.class,style:_.style,href:_.href});D.chunks=[...D.chunks,B],await T(_,B),await M(_.delay??m())}}async function T(b,D){if(c)for(const _ of b.text){if(!c)return;D.text+=_,await M(g())}}function M(b){return new Promise(D=>setTimeout(D,b))}async function I(b){if(!s)return console.error("Clone element not found for height measurement."),0;if(!c)return 0;let D=b.chunks.map(_=>({text:_.text,class:_.class,href:_.href,style:_.style}));return nt(r,[...A(r),{chunks:D,class:b.class,style:b.style}],!0),await Jt(),c?s.scrollHeight:0}async function w(b){if(!c)return 0;let D=b.lines.map(_=>({chunks:_.chunks.map(B=>({text:B.text,class:B.class,href:B.href,style:B.style})),class:_.class,style:_.style}));return nt(r,D,!0),await Jt(),c?s?.scrollHeight??0:0}async function L(){if(!s){console.error("Clone element not found for height recalculation.");return}if(!c)return;s.style.height="0",await Jt();const b=s.scrollHeight;nt(a,`height: ${b}px; transition: height 100ms ease-in-out;`)}function P(){v()<100&&console.warn(`[TypeWriter]: growDelayMs is set to ${v()}ms, which may be too short for smooth height transitions, as the DOM might update too late, resulting in incorrect height measurements. Consider setting it to at least 100ms.`)}var N=gn();Ae("resize",tn,L);var E=O(N);ft(E,20,()=>A(o),b=>b,(b,D)=>{var _=fn();ft(_,20,()=>D.chunks,B=>B,(B,$)=>{var K=Lt(),tt=Z(K);{var ct=R=>{var k=ln(),J=O(k,!0);C(k),rt(()=>{zt(k,"href",$.href),pt(k,1,_t($.class??"")),mt(k,$.style??""),at(J,$.text)}),j(R,k)},Y=R=>{var k=un(),J=O(k,!0);C(k),rt(()=>{pt(k,1,_t($.class??"")),mt(k,$.style??""),at(J,$.text)}),j(R,k)};Kt(tt,R=>{$.href!=null?R(ct):R(Y,!1)})}j(B,K)}),C(_),rt(()=>{pt(_,1,_t(D.class??"")),mt(_,D.style??"")}),j(b,_)}),C(E),Xt(E,b=>i?.(b));var F=q(E,2);ft(F,20,()=>A(r),b=>b,(b,D)=>{var _=mn();ft(_,20,()=>D.chunks,B=>B,(B,$)=>{var K=Lt(),tt=Z(K);{var ct=R=>{var k=dn(),J=O(k,!0);C(k),rt(()=>{zt(k,"href",$.href),pt(k,1,_t($.class??"")),mt(k,$.style??""),at(J,$.text)}),j(R,k)},Y=R=>{var k=pn(),J=O(k,!0);C(k),rt(()=>{pt(k,1,_t($.class??"")),mt(k,$.style??""),at(J,$.text)}),j(R,k)};Kt(tt,R=>{$.href!=null?R(ct):R(Y,!1)})}j(B,K)}),C(_),rt(()=>{pt(_,1,_t(D.class??"")),mt(_,D.style??"")}),j(b,_)}),C(F),Xt(F,b=>l?.(b)),C(N),rt(()=>{pt(E,1,`${d()??""} ${t.message.class??""??""}`),mt(E,`${u()??""} ${A(a)??""} ${t.message.style??""??""}`),pt(F,1,`${d()??""} ${t.message.class??""??""}`),mt(F,`${u()??""} ${A(a)??""} ${t.message.style??""??""} position: absolute; top: 0; left: 0; visibility: hidden;`)}),j(e,N),ht()}var hn=H('
');function xn(e){const t={lines:[{chunks:[{text:"Hi!"}],class:"text-3xl font-bold md:text-5xl"},{chunks:[{text:"My name is "},{text:"codevogel",class:"font-extrabold text-background bg-primary px-2 py-1 rounded-xl"},{text:" ."}],class:"my-2"},{chunks:[{text:"I make "},{text:"games",class:"text-primary"},{text:" and build "},{text:"software",class:"text-primary"},{text:"."}]},{chunks:[{text:"Check out some of my "},{text:"games",class:"text-primary underline",href:"/games"},{text:" and "},{text:"tools",class:"text-primary underline",href:"/tools"},{text:"!"}]}],class:"text-lg"};var n=hn(),s=O(n);vn(s,{get message(){return t},baseClass:"max-w-72 min-w-72 bg-accent rounded-lg p-4 md:text-lg md:max-w-90 md:min-w-90",pregrow:!1}),Qt(4),C(n),j(e,n)}var yn=H('
Click on a job to see more details, or swipe to view past jobs
',1);function bs(e,t){var n=ys(),s=q(Z(n),2);xs(s,{get jobs(){return t.jobs}}),j(e,n)}var Ss=H(" ",1),_s=H(" ",1);function Fs(e,t){vt(t,!0);const n=wt(()=>t.data.skillGroups),s=wt(()=>t.data.jobs);var a=_s(),o=Z(a);Ft(o,{class:"justify-center",children:(l,p)=>{xn(l)},$$slots:{default:!0}});var r=q(o,2);Ft(r,{class:"justify-center gap-y-24",children:(l,p)=>{var u=Ss(),d=Z(u);bn(d,{});var h=q(d,2);rn(h,{}),j(l,u)},$$slots:{default:!0}});var c=q(r,2);Ft(c,{class:"justify-center p-4",children:(l,p)=>{bs(l,{get jobs(){return A(s)}})},$$slots:{default:!0}});var i=q(c,2);Ft(i,{class:"justify-center",children:(l,p)=>{Ln(l,{get skillGroups(){return A(n)}})},$$slots:{default:!0}}),j(e,a),ht()}export{Fs as component};
diff --git a/_app/immutable/nodes/3.Dv4ePjt5.js b/_app/immutable/nodes/3.Dv4ePjt5.js
new file mode 100644
index 0000000..bbc2a93
--- /dev/null
+++ b/_app/immutable/nodes/3.Dv4ePjt5.js
@@ -0,0 +1,88 @@
+import{l as w,s as I,i as T}from"../chunks/CPdWO9vA.js";import{c as S,f as h,a as l,N as j,p as C,d as $,as as R,b as A,at as H,g as d,n as V,h as g,P as x,R as y,e as f,r as u}from"../chunks/BbMFilaF.js";import{f as B}from"../chunks/DZYZrkxG.js";import{F as q}from"../chunks/h4yY_LIJ.js";import{S as b}from"../chunks/B1wPNbHL.js";import{t as D}from"../chunks/CnyALC89.js";import"../chunks/ByzbuU5l.js";import{I as z,s as L}from"../chunks/B6MtVlrN.js";function E(s,o){const e=w(o,["children","$$slots","$$events","$$legacy"]);/**
+ * @license lucide-svelte v0.545.0 - ISC
+ *
+ * ISC License
+ *
+ * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * ---
+ *
+ * The MIT License (MIT) (for portions derived from Feather)
+ *
+ * Copyright (c) 2013-2023 Cole Bemis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */const t=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"}]];z(s,I({name:"copy"},()=>e,{get iconNode(){return t},children:(r,c)=>{var a=S(),i=h(a);L(i,o,"default",{}),l(r,a)},$$slots:{default:!0}}))}function G(s,o){const e=w(o,["children","$$slots","$$events","$$legacy"]);/**
+ * @license lucide-svelte v0.545.0 - ISC
+ *
+ * ISC License
+ *
+ * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2023 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2025.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * ---
+ *
+ * The MIT License (MIT) (for portions derived from Feather)
+ *
+ * Copyright (c) 2013-2023 Cole Bemis
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */const t=[["path",{d:"M14 2v4a2 2 0 0 0 2 2h4"}],["path",{d:"M15 18a3 3 0 1 0-6 0"}],["path",{d:"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"}],["circle",{cx:"12",cy:"13",r:"2"}]];z(s,I({name:"file-user"},()=>e,{get iconNode(){return t},children:(r,c)=>{var a=S(),i=h(a);L(i,o,"default",{}),l(r,a)},$$slots:{default:!0}}))}const k=(s,o=V)=>{var e=J(),t=h(e);q(t,{get topLevel(){return o()}});var r=d(t,4),c=f(r);G(c,{size:48,class:"hover:stroke-primary"}),u(r),l(s,e)};var J=$('
My Resume
',1),K=$(`
Contact
I am currently open to work.
Are you interested in working together? Then, I would love to hear from you! Please don't hesitate to contact me at:
Feel free to contact me for anything else as well. Looking forward to your message!
Are you a recruiter? Then swipe
+ down to see my resume 😉
+
+
diff --git a/games/__data.json b/games/__data.json
new file mode 100644
index 0000000..a712b09
--- /dev/null
+++ b/games/__data.json
@@ -0,0 +1 @@
+{"type":"data","nodes":[null,{"type":"data","data":[{"projects":1},[2,9,15,20,25,31,37,44],{"type":3,"title":4,"shortDescription":5,"description":6,"slug":7,"youtubeURL":8},"game","Detective Duck","Demonstrates use of LLMs in serious games.","A visual novel game where you play as a duck detective solving mysteries.","detective-duck","https://www.youtube.com/watch?v=e1HN3MVz4hs",{"type":3,"title":10,"shortDescription":11,"description":12,"slug":13,"youtubeURL":14},"Reggie: The Game","A gravity-bending platformer in which you travel through time","Control the direction of gravity as you, run, jump and explore in a time-traveling adventure.","reggie","https://www.youtube.com/watch?v=EsMAK-gHFMI",{"type":3,"title":16,"shortDescription":17,"description":18,"slug":19},"Look Out!","An eye-tracked horror game. Every glance could be your last.","A horror game that uses eye-tracking technology to create an immersive and terrifying experience.","look-out",{"type":3,"title":21,"shortDescription":22,"description":23,"slug":24},"Ouija.TV","Play Ouija with your twitch chat. Let the spirits guide you.","Ouija.tv was an online multiplayer Ouija board that allowed streamers to ask questions to their viewers.","ouija-tv",{"type":3,"title":26,"shortDescription":27,"description":28,"slug":29,"youtubeURL":30},"Robotlab VR","VR training simulator for industrial robots.","A virtual reality application that allows users to train operating with industrial robots in a safe environment.","robotlab-vr","https://www.youtube.com/watch?v=oIzACzUwmVM",{"type":3,"title":32,"shortDescription":33,"description":34,"workStatus":35,"slug":36},"Runic Roots","A farming game meets a deckbuilder.","Build out your deck by growing and harvesting magical plants, then use your deck to defeat enemies.","active","runic-roots",{"type":3,"title":38,"shortDescription":39,"description":40,"hasGif":41,"workStatus":42,"slug":43},"Pogo Peaks","Climb a mountain using a pogo stick.","A physics-based climbing game where you use a pogo stick to ascend challenging peaks.",true,"icebox","pogo-peaks",{"type":3,"title":45,"shortDescription":46,"description":47,"workStatus":35,"slug":48},"Liars!","A physical card game about deception and deduction.","A social trivia game where players bluff, and try to catch others in their lies.","liars"],"uses":{}}]}
diff --git a/games/detective-duck.html b/games/detective-duck.html
new file mode 100644
index 0000000..7bf34ad
--- /dev/null
+++ b/games/detective-duck.html
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Detective Duck | codevogel 🐦
+
+
+
+
Detective Duck
+
+
+
+
What is Detective Duck?
+
Detective Duck is a 'whodunit' game that
+experiments with real-time dialogue generation using ChatGPT for educational
+applied games. The game is designed for
+&Ranj, a Dutch game studio that specializes
+in creating serious games. It is the result of my graduation thesis for the
+Bachelor of Science in Game Development at the Amsterdam University of Applied
+Sciences.
+
Continuing on this work, I wrote a paper titled
+
+
Designing Meaningful AI-Generated Dialogue: The Behaviour-Driven Conditional
+Prompting Framework for Serious Games
Look Out! is a groundbreaking horror game that utilizes the Tobii Eye Tracker
+system to create a spine-chilling experience. By tracking the player's eye
+movements, the game introduces unique gameplay mechanics that can both
+manipulate and terrify the player. The game begins innocently enough, with a
+normal workday scenario, but quickly descends into a nightmarish reality. The
+psychological terrors in this game are intensified by the eye tracking
+mechanics. You'll be constantly aware of what you are looking at, and might even
+start feeling like you're being watched yourself...
+
Look Out! Is a Horror Game incorporating the
+Tobii Eye Tracker system.
+
This game was an exploratory prototype to see what effect eye-tracking could
+have on gameplay.
+
Eye tracking can be used in two ways: You can determine whether the player is
+physically looking at a certain object, but you can also determine whether the
+player is not looking at an object.
+
This provides ample oppertunity to mess with the player phsycologically.
+
The game starts off as a regular, normal work day. The player receives an e-mail
+and is tasked to pick up a few documents that need shredding from their
+coworkers. The contents of these e-mails quickly grow more and more disturbing,
+and something is clearly off...
+
After the player completes a few tasks, the entire office scene suddenly turns
+into an otherworldy version of itself. The scene turns dark, red, and moody.
+Coworkers are replaced by creatures that seem like your coworkers, but have
+glowing red eyes that always seem to look directly at you. If you look at them
+directly, they get disturbed and start chasing you.
+
Look at some images of how the game changes looks:
+
+
+
+
+
Exploring further, the player will come across hostile monsters which slowly
+move towards you whenever you're not looking at them. These may remind you of
+the Weeping Angles from Doctor Who. These force players to face their fears, as
+the players need to look directly at these horrendous monsters to survive.
+Finally, there are creatures which are only visible out of the corner of your
+eye. They dissappear whenever the player looks at them, making it impossible to
+get a good look at them. These add an extra layer of psychological terror, they
+make the player feel watched, but the player can never truly figure out what was
+watching them.. The player then receives a mysterious message, leading (or
+luring ..?) them towards the dark basement. Would you dare to tread down those
+steps?
+
My contributions
+
Everything!
+
+
Concept
+
Game and Level Design
+
Eye Tracking Integration
+
Asset Creation
+
+
Play it
+
Unfortunately, this game is not available for direct download. However, you can
+clone the project from GitHub
+and build it yourself. Please don't take that as an invitation to work on a
+clone of this project without first getting written approval. For any further
+questions, feel free to contact me.
Ouija.TV is an online multiplayer version of the classic board game. You
+could create a lobby, share it with your friends, and let them answer your
+questions as spirits!
+
It includes Twitch.TV chat integration, allowing streamers to play the game with
+their viewers.
+
Can I play it?
+
Unfortunately, due to server costs, we had to shut it down. (for now)
+
If you're still interested in this project and would like to see it back online,
+please contact me through my Contact page!
🧊 This project is taking a break. It's in the icebox for now, but I hope to revisit it when the time feels right. 🧊
+
What is Pogo Peaks?
+
Prepare yourself for an adrenaline-fueled journey as you defy gravity and
+conquer the daunting heights of a majestic mountain range using nothing but your
+trusty pogo stick. A thrilling experience that combines skill, precision, and
+the unyielding determination to reach the summit.
Reggie, his cousin, two scientists and most likely the end of the world
+
+
+
+
+
+
What is Reggie?
+
"Reggie, his cousin, two scientists and most likely the end of the world”
+is an adventure about time travel and its consequences. Its rich, quirky, plot
+involves a grumpy scientist from the sixteenth century, black holes, time
+travel, dinosaurs, a gnome University, alternative dark dimensions and an
+unexpected final villain who likes the universe without science better. Humour
+is a constant and frequent plot twists keep the player guessing about what will
+happen next!
+
+
_Note: I worked as an intern on this game, at
+Degoma. CodeVogel.com does not own it's
+intellectual rights. For more information on Reggie, please visit
+https://www.reggiegame.com/ ).
+
My contributions
+
+
Programmed one of the boss fights in this game.
+
Developed a mechanic that generatively spreads fire around level geometry at
+game-time.
+
Performed playtests and helped redesign levels.
+
Worked with the art team to alter the design.
+
Researched how the Reggie EP could best be translated to a mobile game.
+
Developed a prototype of this mobile game, which was presented to the team.
Robotlab VR was the first big VR project I worked on. At the Amsterdam
+University of Applied Sciences' esteemed Robot Lab, we recognized the potential
+dangers associated with working directly with robotic arms. To address these
+concerns, we set out to create a tool that would allow students to practice and
+refine their skills in a risk-free virtual setting.
+
The goal
+
The Robot Lab was coping with a limited availability of physical robots for
+training purposes, and the potential for damage caused to those robots by
+improper handling. With this in mind, we developed a serious game that provides
+students with a safe and immersive training experience. The game allows learners
+to navigate through various scenarios, honing their ability to control and
+manipulate robotic arms before they ever step foot into a real-world laboratory.
+They familiarize themselves with the usage of the Flexpendant - a controller
+designed specifically to control such robots.
+
By simulating real-life situations, students can experiment, learn, and grow
+without the fear of physical repercussions.
+
My contributions
+
I made a realistic model of the Flexpendant, and mapped the buttons and
+joysticks so that the user experience is as close to the real thing as possible.
+I implemented the physics of the robotic arm, and programmed a few of the
+mini-games that students can play to practice their skills.
+
I also oversaw the art direction for this project, and composed some atmospheric
+music for this project, enhancing the immersive experience. The aim was to
+foster an ambiance that amplifies students' concentration and focus during their
+virtual training sessions.
🚧 This project is still under construction. I am currently actively building it, so stay tuned! 🚧
+
Runic Roots is a game that combines elements of roguelike deckbuilders
+with farming games.
+
In this game, players manage a magical farm where they grow mystical plants and
+herbs imbued with runic energies. The plants on your farm are not just for show;
+they provide you with a powerful deck of cards that you can use in combat to
+defeat various enemies and bosses.
Thesis Intern: AI generated
+dialogue for serious games Rotterdam, NL
01/09/2023-01/02/2024
Teaching Assistant: Minor Programming Amsterdam, NL
01/09/2022-01/01/2024
Intern game design, development, and porting Amsterdam, NL
01/09/2021-01/02/2022
Skills
Game Engines
Design Tools
Technologies
Programming Languages
+
+
+
+
+
diff --git a/app/static/logos/svg/logo-codevogel-head.svg b/logos/svg/logo-codevogel-head.svg
similarity index 100%
rename from app/static/logos/svg/logo-codevogel-head.svg
rename to logos/svg/logo-codevogel-head.svg
diff --git a/app/static/logos/svg/logo-codevogel-text.svg b/logos/svg/logo-codevogel-text.svg
similarity index 100%
rename from app/static/logos/svg/logo-codevogel-text.svg
rename to logos/svg/logo-codevogel-text.svg
diff --git a/app/static/logos/svg/logo-degoma.svg b/logos/svg/logo-degoma.svg
similarity index 100%
rename from app/static/logos/svg/logo-degoma.svg
rename to logos/svg/logo-degoma.svg
diff --git a/app/static/logos/svg/logo-github.svg b/logos/svg/logo-github.svg
similarity index 100%
rename from app/static/logos/svg/logo-github.svg
rename to logos/svg/logo-github.svg
diff --git a/app/static/logos/svg/logo-glu-square.svg b/logos/svg/logo-glu-square.svg
similarity index 100%
rename from app/static/logos/svg/logo-glu-square.svg
rename to logos/svg/logo-glu-square.svg
diff --git a/app/static/logos/svg/logo-linkedin.svg b/logos/svg/logo-linkedin.svg
similarity index 100%
rename from app/static/logos/svg/logo-linkedin.svg
rename to logos/svg/logo-linkedin.svg
diff --git a/app/static/logos/svg/logo-ranj.svg b/logos/svg/logo-ranj.svg
similarity index 100%
rename from app/static/logos/svg/logo-ranj.svg
rename to logos/svg/logo-ranj.svg
diff --git a/app/static/logos/svg/logo-superprof.svg b/logos/svg/logo-superprof.svg
similarity index 100%
rename from app/static/logos/svg/logo-superprof.svg
rename to logos/svg/logo-superprof.svg
diff --git a/app/static/logos/svg/logo-uva.svg b/logos/svg/logo-uva.svg
similarity index 100%
rename from app/static/logos/svg/logo-uva.svg
rename to logos/svg/logo-uva.svg
diff --git a/app/static/logos/svg/logo-youtube.svg b/logos/svg/logo-youtube.svg
similarity index 100%
rename from app/static/logos/svg/logo-youtube.svg
rename to logos/svg/logo-youtube.svg
diff --git a/app/static/logos/svg/skills/logo-blender.svg b/logos/svg/skills/logo-blender.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-blender.svg
rename to logos/svg/skills/logo-blender.svg
diff --git a/app/static/logos/svg/skills/logo-bootstrap.svg b/logos/svg/skills/logo-bootstrap.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-bootstrap.svg
rename to logos/svg/skills/logo-bootstrap.svg
diff --git a/app/static/logos/svg/skills/logo-c.svg b/logos/svg/skills/logo-c.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-c.svg
rename to logos/svg/skills/logo-c.svg
diff --git a/app/static/logos/svg/skills/logo-cplusplus.svg b/logos/svg/skills/logo-cplusplus.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-cplusplus.svg
rename to logos/svg/skills/logo-cplusplus.svg
diff --git a/app/static/logos/svg/skills/logo-csharp.svg b/logos/svg/skills/logo-csharp.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-csharp.svg
rename to logos/svg/skills/logo-csharp.svg
diff --git a/app/static/logos/svg/skills/logo-drizzle.svg b/logos/svg/skills/logo-drizzle.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-drizzle.svg
rename to logos/svg/skills/logo-drizzle.svg
diff --git a/app/static/logos/svg/skills/logo-figma.svg b/logos/svg/skills/logo-figma.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-figma.svg
rename to logos/svg/skills/logo-figma.svg
diff --git a/app/static/logos/svg/skills/logo-godot.svg b/logos/svg/skills/logo-godot.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-godot.svg
rename to logos/svg/skills/logo-godot.svg
diff --git a/app/static/logos/svg/skills/logo-illustrator.svg b/logos/svg/skills/logo-illustrator.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-illustrator.svg
rename to logos/svg/skills/logo-illustrator.svg
diff --git a/app/static/logos/svg/skills/logo-java.svg b/logos/svg/skills/logo-java.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-java.svg
rename to logos/svg/skills/logo-java.svg
diff --git a/app/static/logos/svg/skills/logo-javascript.svg b/logos/svg/skills/logo-javascript.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-javascript.svg
rename to logos/svg/skills/logo-javascript.svg
diff --git a/app/static/logos/svg/skills/logo-kotlin.svg b/logos/svg/skills/logo-kotlin.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-kotlin.svg
rename to logos/svg/skills/logo-kotlin.svg
diff --git a/app/static/logos/svg/skills/logo-lightroom.svg b/logos/svg/skills/logo-lightroom.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-lightroom.svg
rename to logos/svg/skills/logo-lightroom.svg
diff --git a/app/static/logos/svg/skills/logo-lua.svg b/logos/svg/skills/logo-lua.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-lua.svg
rename to logos/svg/skills/logo-lua.svg
diff --git a/app/static/logos/svg/skills/logo-magicavoxel.svg b/logos/svg/skills/logo-magicavoxel.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-magicavoxel.svg
rename to logos/svg/skills/logo-magicavoxel.svg
diff --git a/app/static/logos/svg/skills/logo-material-maker.svg b/logos/svg/skills/logo-material-maker.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-material-maker.svg
rename to logos/svg/skills/logo-material-maker.svg
diff --git a/app/static/logos/svg/skills/logo-mongodb.svg b/logos/svg/skills/logo-mongodb.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-mongodb.svg
rename to logos/svg/skills/logo-mongodb.svg
diff --git a/app/static/logos/svg/skills/logo-mysql.svg b/logos/svg/skills/logo-mysql.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-mysql.svg
rename to logos/svg/skills/logo-mysql.svg
diff --git a/app/static/logos/svg/skills/logo-photoshop.svg b/logos/svg/skills/logo-photoshop.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-photoshop.svg
rename to logos/svg/skills/logo-photoshop.svg
diff --git a/app/static/logos/svg/skills/logo-python.svg b/logos/svg/skills/logo-python.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-python.svg
rename to logos/svg/skills/logo-python.svg
diff --git a/app/static/logos/svg/skills/logo-react.svg b/logos/svg/skills/logo-react.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-react.svg
rename to logos/svg/skills/logo-react.svg
diff --git a/app/static/logos/svg/skills/logo-substance-painter.svg b/logos/svg/skills/logo-substance-painter.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-substance-painter.svg
rename to logos/svg/skills/logo-substance-painter.svg
diff --git a/app/static/logos/svg/skills/logo-svelte.svg b/logos/svg/skills/logo-svelte.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-svelte.svg
rename to logos/svg/skills/logo-svelte.svg
diff --git a/app/static/logos/svg/skills/logo-tailwind.svg b/logos/svg/skills/logo-tailwind.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-tailwind.svg
rename to logos/svg/skills/logo-tailwind.svg
diff --git a/app/static/logos/svg/skills/logo-typescript.svg b/logos/svg/skills/logo-typescript.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-typescript.svg
rename to logos/svg/skills/logo-typescript.svg
diff --git a/app/static/logos/svg/skills/logo-umbraco.svg b/logos/svg/skills/logo-umbraco.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-umbraco.svg
rename to logos/svg/skills/logo-umbraco.svg
diff --git a/app/static/logos/svg/skills/logo-unity.svg b/logos/svg/skills/logo-unity.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-unity.svg
rename to logos/svg/skills/logo-unity.svg
diff --git a/app/static/logos/svg/skills/logo-unreal.svg b/logos/svg/skills/logo-unreal.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-unreal.svg
rename to logos/svg/skills/logo-unreal.svg
diff --git a/app/static/logos/svg/skills/logo-vue.svg b/logos/svg/skills/logo-vue.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-vue.svg
rename to logos/svg/skills/logo-vue.svg
diff --git a/app/static/logos/svg/skills/logo-xamarin.svg b/logos/svg/skills/logo-xamarin.svg
similarity index 100%
rename from app/static/logos/svg/skills/logo-xamarin.svg
rename to logos/svg/skills/logo-xamarin.svg
diff --git a/app/static/pdf/resume-kamiel-de-visser.pdf b/pdf/resume-kamiel-de-visser.pdf
similarity index 100%
rename from app/static/pdf/resume-kamiel-de-visser.pdf
rename to pdf/resume-kamiel-de-visser.pdf
diff --git a/app/static/projects/games/detective-duck/README.md b/projects/games/detective-duck/README.md
similarity index 100%
rename from app/static/projects/games/detective-duck/README.md
rename to projects/games/detective-duck/README.md
diff --git a/app/static/projects/games/detective-duck/detective-duck.png b/projects/games/detective-duck/detective-duck.png
similarity index 100%
rename from app/static/projects/games/detective-duck/detective-duck.png
rename to projects/games/detective-duck/detective-duck.png
diff --git a/app/static/projects/games/liars/README.md b/projects/games/liars/README.md
similarity index 100%
rename from app/static/projects/games/liars/README.md
rename to projects/games/liars/README.md
diff --git a/app/static/projects/games/liars/liars.png b/projects/games/liars/liars.png
similarity index 100%
rename from app/static/projects/games/liars/liars.png
rename to projects/games/liars/liars.png
diff --git a/app/static/projects/games/look-out/README.md b/projects/games/look-out/README.md
similarity index 100%
rename from app/static/projects/games/look-out/README.md
rename to projects/games/look-out/README.md
diff --git a/app/static/projects/games/look-out/after-1.png b/projects/games/look-out/after-1.png
similarity index 100%
rename from app/static/projects/games/look-out/after-1.png
rename to projects/games/look-out/after-1.png
diff --git a/app/static/projects/games/look-out/after-2.png b/projects/games/look-out/after-2.png
similarity index 100%
rename from app/static/projects/games/look-out/after-2.png
rename to projects/games/look-out/after-2.png
diff --git a/app/static/projects/games/look-out/before-1.png b/projects/games/look-out/before-1.png
similarity index 100%
rename from app/static/projects/games/look-out/before-1.png
rename to projects/games/look-out/before-1.png
diff --git a/app/static/projects/games/look-out/before-2.png b/projects/games/look-out/before-2.png
similarity index 100%
rename from app/static/projects/games/look-out/before-2.png
rename to projects/games/look-out/before-2.png
diff --git a/app/static/projects/games/look-out/look-out.png b/projects/games/look-out/look-out.png
similarity index 100%
rename from app/static/projects/games/look-out/look-out.png
rename to projects/games/look-out/look-out.png
diff --git a/app/static/projects/games/ouija-tv/README.md b/projects/games/ouija-tv/README.md
similarity index 100%
rename from app/static/projects/games/ouija-tv/README.md
rename to projects/games/ouija-tv/README.md
diff --git a/app/static/projects/games/ouija-tv/ouija-tv.png b/projects/games/ouija-tv/ouija-tv.png
similarity index 100%
rename from app/static/projects/games/ouija-tv/ouija-tv.png
rename to projects/games/ouija-tv/ouija-tv.png
diff --git a/app/static/projects/games/pogo-peaks/README.md b/projects/games/pogo-peaks/README.md
similarity index 100%
rename from app/static/projects/games/pogo-peaks/README.md
rename to projects/games/pogo-peaks/README.md
diff --git a/app/static/projects/games/pogo-peaks/pogo-peaks.gif b/projects/games/pogo-peaks/pogo-peaks.gif
similarity index 100%
rename from app/static/projects/games/pogo-peaks/pogo-peaks.gif
rename to projects/games/pogo-peaks/pogo-peaks.gif
diff --git a/app/static/projects/games/reggie/README.md b/projects/games/reggie/README.md
similarity index 100%
rename from app/static/projects/games/reggie/README.md
rename to projects/games/reggie/README.md
diff --git a/app/static/projects/games/reggie/reggie.png b/projects/games/reggie/reggie.png
similarity index 100%
rename from app/static/projects/games/reggie/reggie.png
rename to projects/games/reggie/reggie.png
diff --git a/app/static/projects/games/robotlab-vr/README.md b/projects/games/robotlab-vr/README.md
similarity index 100%
rename from app/static/projects/games/robotlab-vr/README.md
rename to projects/games/robotlab-vr/README.md
diff --git a/app/static/projects/games/robotlab-vr/robotlab-vr.png b/projects/games/robotlab-vr/robotlab-vr.png
similarity index 100%
rename from app/static/projects/games/robotlab-vr/robotlab-vr.png
rename to projects/games/robotlab-vr/robotlab-vr.png
diff --git a/app/static/projects/games/runic-roots/README.md b/projects/games/runic-roots/README.md
similarity index 100%
rename from app/static/projects/games/runic-roots/README.md
rename to projects/games/runic-roots/README.md
diff --git a/app/static/projects/games/runic-roots/runic-roots.png b/projects/games/runic-roots/runic-roots.png
similarity index 100%
rename from app/static/projects/games/runic-roots/runic-roots.png
rename to projects/games/runic-roots/runic-roots.png
diff --git a/app/static/projects/tools/circuit-patch-converter/README.md b/projects/tools/circuit-patch-converter/README.md
similarity index 100%
rename from app/static/projects/tools/circuit-patch-converter/README.md
rename to projects/tools/circuit-patch-converter/README.md
diff --git a/app/static/projects/tools/circuit-patch-converter/circuit-patch-converter.png b/projects/tools/circuit-patch-converter/circuit-patch-converter.png
similarity index 100%
rename from app/static/projects/tools/circuit-patch-converter/circuit-patch-converter.png
rename to projects/tools/circuit-patch-converter/circuit-patch-converter.png
diff --git a/app/static/projects/tools/commit-for-me/README.md b/projects/tools/commit-for-me/README.md
similarity index 100%
rename from app/static/projects/tools/commit-for-me/README.md
rename to projects/tools/commit-for-me/README.md
diff --git a/app/static/projects/tools/commit-for-me/commit-for-me.png b/projects/tools/commit-for-me/commit-for-me.png
similarity index 100%
rename from app/static/projects/tools/commit-for-me/commit-for-me.png
rename to projects/tools/commit-for-me/commit-for-me.png
diff --git a/app/static/projects/tools/faux-3d-perspective-shader/README.md b/projects/tools/faux-3d-perspective-shader/README.md
similarity index 100%
rename from app/static/projects/tools/faux-3d-perspective-shader/README.md
rename to projects/tools/faux-3d-perspective-shader/README.md
diff --git a/app/static/projects/tools/faux-3d-perspective-shader/faux-3d-perspective-shader.gif b/projects/tools/faux-3d-perspective-shader/faux-3d-perspective-shader.gif
similarity index 100%
rename from app/static/projects/tools/faux-3d-perspective-shader/faux-3d-perspective-shader.gif
rename to projects/tools/faux-3d-perspective-shader/faux-3d-perspective-shader.gif
diff --git a/app/static/projects/tools/godot-doctor/README.md b/projects/tools/godot-doctor/README.md
similarity index 100%
rename from app/static/projects/tools/godot-doctor/README.md
rename to projects/tools/godot-doctor/README.md
diff --git a/app/static/projects/tools/godot-doctor/godot-doctor.md b/projects/tools/godot-doctor/godot-doctor.md
similarity index 100%
rename from app/static/projects/tools/godot-doctor/godot-doctor.md
rename to projects/tools/godot-doctor/godot-doctor.md
diff --git a/app/static/projects/tools/godot-doctor/godot-doctor.png b/projects/tools/godot-doctor/godot-doctor.png
similarity index 100%
rename from app/static/projects/tools/godot-doctor/godot-doctor.png
rename to projects/tools/godot-doctor/godot-doctor.png
diff --git a/app/static/projects/tools/mkp5/README.md b/projects/tools/mkp5/README.md
similarity index 100%
rename from app/static/projects/tools/mkp5/README.md
rename to projects/tools/mkp5/README.md
diff --git a/app/static/projects/tools/mkp5/mkp5.png b/projects/tools/mkp5/mkp5.png
similarity index 100%
rename from app/static/projects/tools/mkp5/mkp5.png
rename to projects/tools/mkp5/mkp5.png
diff --git a/app/static/projects/tools/peck/README.md b/projects/tools/peck/README.md
similarity index 100%
rename from app/static/projects/tools/peck/README.md
rename to projects/tools/peck/README.md
diff --git a/app/static/projects/tools/peck/peck.png b/projects/tools/peck/peck.png
similarity index 100%
rename from app/static/projects/tools/peck/peck.png
rename to projects/tools/peck/peck.png
diff --git a/app/static/projects/tools/pls/README.md b/projects/tools/pls/README.md
similarity index 100%
rename from app/static/projects/tools/pls/README.md
rename to projects/tools/pls/README.md
diff --git a/app/static/projects/tools/pls/pls.png b/projects/tools/pls/pls.png
similarity index 100%
rename from app/static/projects/tools/pls/pls.png
rename to projects/tools/pls/pls.png
diff --git a/app/static/robots.txt b/robots.txt
similarity index 100%
rename from app/static/robots.txt
rename to robots.txt
diff --git a/app/static/sounds/codevogel-en-f.mp3 b/sounds/codevogel-en-f.mp3
similarity index 100%
rename from app/static/sounds/codevogel-en-f.mp3
rename to sounds/codevogel-en-f.mp3
diff --git a/app/static/sounds/codevogel-en-m.mp3 b/sounds/codevogel-en-m.mp3
similarity index 100%
rename from app/static/sounds/codevogel-en-m.mp3
rename to sounds/codevogel-en-m.mp3
diff --git a/app/static/sounds/codevogel-nl-f.mp3 b/sounds/codevogel-nl-f.mp3
similarity index 100%
rename from app/static/sounds/codevogel-nl-f.mp3
rename to sounds/codevogel-nl-f.mp3
diff --git a/app/static/sounds/codevogel-nl-m.mp3 b/sounds/codevogel-nl-m.mp3
similarity index 100%
rename from app/static/sounds/codevogel-nl-m.mp3
rename to sounds/codevogel-nl-m.mp3
diff --git a/tools.html b/tools.html
new file mode 100644
index 0000000..fd8a79f
--- /dev/null
+++ b/tools.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tools | codevogel 🐦
+
+
+
A powerful validation plugin for Godot that catches errors before they reach runtime. Validate scenes, nodes, and resources using a declarative, test-driven approach. No @tool required!
A command-line tool that leverages AI to generate conventional commit messages based on your code changes. Easily customizable to your own needs. Save time and maintain consistency in your commit history.
peck is a screen capture tool for Wayland. It provides easy ways to capture screenshots, record your screen, and optionally convert outputs to different formats. peck isn't a tool that tries to reinvent the wheel. Rather, it's just a simple wrapper bash script that makes interfacing with grim and wf-recorder easier.
A versatile shader for Godot that simulates a faux 3D perspective effect. Perfect for creating depth and immersion in 2D games, this shader can be easily customized to fit your art style and game design needs.
Project Level Shortcuts (pls) is a command-line tool that allows you to create, manage, and execute custom context-aware aliases for frequently used commands using portable .pls.yml files.
Converts patches from the old Novation Circuit format to the new Circuit format, bringing back backwards compatability.
+
+
+
+
+
diff --git a/tools/__data.json b/tools/__data.json
new file mode 100644
index 0000000..563c2ec
--- /dev/null
+++ b/tools/__data.json
@@ -0,0 +1 @@
+{"type":"data","nodes":[null,{"type":"data","data":[{"projects":1},[2,11,19,27,36,43,50],{"type":3,"title":4,"shortDescription":5,"description":6,"slug":7,"readmeURL":8,"githubRawURL":9,"githubURL":10},"tool","Godot Doctor","A plugin that helps you validate your scenes in Godot.","A powerful validation plugin for Godot that catches errors before they reach runtime. Validate scenes, nodes, and resources using a declarative, test-driven approach. No @tool required!","godot-doctor","https://raw.githubusercontent.com/codevogel/godot_doctor/refs/heads/main/addons/godot_doctor/README.md","https://raw.githubusercontent.com/codevogel/godot_doctor/refs/heads/main/","https://github.com/codevogel/godot_doctor",{"type":3,"title":12,"shortDescription":13,"description":14,"slug":15,"readmeURL":16,"githubRawURL":17,"githubURL":18},"Commit for me","Generate coventional commit messages with AI.","A command-line tool that leverages AI to generate conventional commit messages based on your code changes. Easily customizable to your own needs. Save time and maintain consistency in your commit history.","commit-for-me","https://raw.githubusercontent.com/codevogel/commit-for-me/refs/heads/main/README.md","https://raw.githubusercontent.com/codevogel/commit-for-me/refs/heads/main/","https://github.com/codevogel/commit-for-me",{"type":3,"title":20,"shortDescription":21,"description":22,"slug":23,"readmeURL":24,"githubRawURL":25,"githubURL":26},"Peck","📷 Easily capture Screenshots / Screen Recordings on Wayland.","peck is a screen capture tool for Wayland. It provides easy ways to capture screenshots, record your screen, and optionally convert outputs to different formats. peck isn't a tool that tries to reinvent the wheel. Rather, it's just a simple wrapper bash script that makes interfacing with grim and wf-recorder easier.","peck","https://raw.githubusercontent.com/codevogel/peck/refs/heads/main/README.md","https://raw.githubusercontent.com/codevogel/peck/refs/heads/main/","https://github.com/codevogel/peck",{"type":3,"title":28,"shortDescription":29,"description":30,"slug":31,"readmeURL":32,"githubRawURL":33,"hasGif":34,"githubURL":35},"Faux 3D Perspective Shader","A shader that creates a faux 3D perspective effect in Godot.","A versatile shader for Godot that simulates a faux 3D perspective effect. Perfect for creating depth and immersion in 2D games, this shader can be easily customized to fit your art style and game design needs.","faux-3d-perspective-shader","https://raw.githubusercontent.com/codevogel/faux-3d-perspective-shader-godot/refs/heads/main/README.md","https://raw.githubusercontent.com/codevogel/faux-3d-perspective-shader-godot/refs/heads/main/",true,"https://github.com/codevogel/faux-3d-perspective-shader-godot",{"type":3,"title":37,"shortDescription":38,"description":39,"slug":37,"readmeURL":40,"githubRawURL":41,"githubURL":42},"mkp5","Creates p5.js projects from the command line.","A command-line tool that streamlines the creation of p5.js projects. With mkp5, you can quickly set up a new p5.js project with a single command.","https://raw.githubusercontent.com/codevogel/mkp5/refs/heads/main/README.md","https://raw.githubusercontent.com/codevogel/mkp5/refs/heads/main/","https://github.com/codevogel/mkp5",{"type":3,"title":44,"shortDescription":45,"description":46,"slug":44,"readmeURL":47,"githubRawURL":48,"githubURL":49},"pls","pls centralize aliases! Unify your workflow with context-aware alias management.","Project Level Shortcuts (pls) is a command-line tool that allows you to create, manage, and execute custom context-aware aliases for frequently used commands using portable .pls.yml files.","https://raw.githubusercontent.com/codevogel/pls/refs/heads/main/README.md","https://raw.githubusercontent.com/codevogel/pls/refs/heads/main/","https://github.com/codevogel/pls",{"type":3,"title":51,"shortDescription":52,"description":53,"slug":54,"readmeURL":55,"githubRawURL":56,"githubURL":57},"Circuit Patch Converter","Converts patches between different formats for the Novation Circuit.","Converts patches from the old Novation Circuit format to the new Circuit format, bringing back backwards compatability.","circuit-patch-converter","https://raw.githubusercontent.com/codevogel/CircuitPatchConverter/refs/heads/main/README.md","https://raw.githubusercontent.com/codevogel/CircuitPatchConverter/refs/heads/main/","https://github.com/codevogel/CircuitPatchConverter"],"uses":{}}]}
diff --git a/tools/circuit-patch-converter.html b/tools/circuit-patch-converter.html
new file mode 100644
index 0000000..43aa14a
--- /dev/null
+++ b/tools/circuit-patch-converter.html
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Circuit Patch Converter | codevogel 🐦
+
+
+
Circuit Patch converter takes in a .circuittrackspack (or other .zip file),
+extracts the patches and converts these to the OG circuit format.
+It does this by altering the 5th hexadecimal value from 64 to 60.
+
Requirements:
+
+
Python 3.6+
+
+
Step by step example:
+
+
Clone this repository or download it as a .zip
+
(Extract zip to a directory)
+
Put your .circuittrackspack files in a folder called 'Packs' (like someDir\CircuitPatchConverter\Packs)
+
cd to someDir\CircuitPatchConverter\
+
Launch the script using python circuit-convert.py -i ./Packs/example.circuittrackspack
+
Find your newly converted patches in someDir\CircuitPatchConverter\Patches
+
+
Please note: this program will overwrite existing .syx files in the output folder. If you are converting multiple packs one by one, be sure to alter the output directory using the -o option.
Commit for me! 🤖 Generate commit messages using aichat.
+
Uses the conventional-commits standard by default, but is easily customizable
+to your own needs.
+
Just run cfme after staging your changes, and it will generate a commit message
+using AI, prompt you to review and edit it, then commit it for you.
+
Demo 🎥
+
+
+
There is also a demo available showing how to use cfme from within
+Lazygit.
+Jump to the Lazygit Integration section below.
+
+
Features ✨
+
+
AI-Powered Commit Messages - Leverages modern AI models
+(OpenAI GPT, GitHub Copilot) to analyze your staged changes and generate
+meaningful commit messages
+
Interactive Review & Selection - Uses fzf to let you choose from
+multiple AI-generated candidates, then edit before committing
+
Conventional Commits by Default - Follows the
+conventional-commits standard
+out of the box for consistent, parseable commit history
+
Fully Customizable - Bring your own prompts, variables, and commit
+message standards - cfme adapts to your team's workflow
+
Template-Based Prompts - Simple <__MY_VARIABLE__> syntax for injecting
+git diffs, test results, custom instructions, or any command output into your prompts
+
Multiple Prompt Types - Switch between different commit message standards
+(conventional-commits, your-team-standard, etc.) with a single environment variable
+
Runtime Instructions - Pass specific context with -i "Fixed login bug"
+to guide the AI without editing prompt files
+
Pipe-Friendly Output - Use -r or -m flags to output messages for
+integration with other tools and workflows
+
Free Tier Available - Works with GitHub Copilot's free tier (everyone
+with a GitHub account has access)
+
Cross-Platform - Works on macOS, Linux, and Windows (via WSL/Git Bash/Cygwin)
+
Tested - Uses Shellspec
+unit tests to ensure reliability and correctness
curl -sSL https://github.com/codevogel/cfme/releases/download/latest/cfme
+chmod +x cfme
+sudo mv cfme /usr/local/bin # or any directory, as long as it's in your $PATH
+
+
+
+
Setup aichat credentials (just run aichat and follow the prompts)
An optional brief set of instructions to pass the AI, to help it generate commit message candidates. Replaces template string <__INSTRUCTIONS__> in the prompt file.
--prompt-file
-p
FILE_PATH
Overrides the default prompt by reading from a specified file.
--variables-file
-v
FILE_PATH
Overrides path for the prompt variables file ($CFME_PROMPT_VARIABLES_FILE) by reading from a specified file.
--response
-r
(none)
Prints the direct response from the AI, instead of prompting to review response and then committing. Used for piping the response into other tools.
Conflicts with --message
--message
-m
(none)
Prints the reviewed commit message instead of committing. Used for piping the reviewed message into other tools.
Conflicts with --response
--silent
-s
(none)
Suppresses all non-error output. Also possible through the $CFME_SILENT_MODE environment variable.
--print-parsed-prompt
(none)
(none)
Prints the parsed prompt after replacing template strings with variable values, then exits. Useful for debugging.
Generates a raw response to the prompt, replacing the <__INSTRUCTIONS__> template string, using a custom prompt file and variables file, while suppressing all non-error output.
+
+
Installation
+
The Quick Start section above provides a quick way to setup cfme.
+What follows is a more detailed explanation of the installation process.
+
+
+
Install dependencies:
+
cfme depends on the following command-line tools. Please refer to their
+respective installation instructions for your platform.
This is used to parse the YAML variables file for use in the prompt.
+
Note: There are two popular yq tools, make sure to install the one from
+mikefarah, as the syntax differs between the two, and you might run into
+issues otherwise.
+
+
+
+
Brew is a convenient way to install all of the dependencies
+on macOS and Linux, but feel free to use any other method you prefer.
+
brew install aichat fzf yq
+
+
+
+
Install cfme:
+Download the latest release binary from the
+Releases page,
+make it executable, and move it to a directory in your $PATH.
+
On Unix-like systems, you can do this with the following commands:
+
curl -sSL https://github.com/codevogel/cfme/releases/download/latest/cfme
+chmod +x cfme
+sudo mv cfme /usr/local/bin # or any directory, as long as it's in your $PATH
+
+
On Windows, it is important to know that cfme is a shell script written
+in bash, so you will need to have a bash environment set up, e.g. through
+WSL or,
+Git Bash (which comes with Git),
+Cygwin,
+mingw-64 etc.
+
+
+
Setup aichat credentials:
+Run aichat and follow the prompts to set up your AI chat model and API Key.
+
+
+
For GitHub Copilot, you can get your 'API Key' by creating a
+Personal Access Token (classic).
+Be sure to give this token the Copilot scope.
+Everyone who has a GitHub account has access to the GitHub Copilot Free plan.
+You may hit limits with this plan, however.
+
Educators, students, and open source contributors may be eligible for
+free access to GitHub Copilot Pro. See this page
+for more information.
+
+
+
aichat supports a bunch of different AI providers, such as OpenAI GPT,
+Claude, and more.
+
+
+
Choose a model wisely: For example, GPT-4 mini generates faster commit message
+faster, whereas GPT-4 probably writes more accurate messages.
+
+
+
+
+
Stage some changes:
+cfme generates commit messages based on the staged changes in your git repository.
+Note that changed files aren't yet staged, so you need to stage them first using
+git add <file>.
+(You can optionally alter your prompt to include unstaged changes if you want
+to, see the 'Customization' section below for more information.)
+
+
+
Run cfme to generate commit message:
+Simply run cfme in your terminal while in the git repository with staged changes.
+Follow the prompts to review and edit the generated commit message, then
+confirm to commit it.
+
Be sure to check out the 'Customization' section below
+to learn how to customize the prompt and variables to your own needs,
+and check out the 'Usage' section to learn about the available options,
+such as passing instructions to the AI on what the commit is about, piping
+the output to other tools, and more.
+
+
+
Customization
+
Not happy with the default prompt or variables?
+
cfme is designed to be customizable to your needs.
+
Simply create your own prompt at $CFME_CONFIG_DIR/$CFME_PROMPT_DIR/$CFME_PROMPT_TYPE/<your-prompt>.md
+and/or your own variables file at $CFME_CONFIG_DIR/$CFME_PROMPT_DIR/$CFME_PROMPT_TYPE/<your-variables>.yaml.
+then set $CFME_DEFAULT_PROMPT_FILE and/or $CFME_PROMPT_VARIABLES_FILE to
+point to your custom files, or use the -p and -v flags to specify them at runtime.
+
$CFME_PROMPT_TYPE will determine the subdirectory in the $CFME_PROMPT_DIR
+directory by default, allowing you to quickly switch between different prompt
+types (e.g. conventional-commits, or any other standard your team uses).
+
Example:
+
+
+
Say you want to create a custom prompt for your own commit message standard,
+you could do the following:
ℹ️ If the specified $CFME_DEFAULT_PROMPT_FILE or $CFME_PROMPT_VARIABLES file
+does not yet exist, cfme will attempt to fetch these files from the /defaults
+folder in this repository. This requires $CFME_PROMPT_TYPE to be set to the default
+value of conventional-commits. See the 'Environment Variables' section below
+for more information.
+
+
Writing a custom prompt file
+
cfme uses a prompt file in markdown format, in which you can write your prompt
+that will be sent to the AI.
+
cfme will parse your prompt file, replacing any template strings in the format
+of <__MY_TEMPLATE_STRING__> with the corresponding values from your variables file.
+
There are three special template strings that cfme recognizes by default:
+
+
<__GIT_DIFF__> (required): Replaced with the output of git diff --cached
+(i.e. the staged changes)
+
<__RESPONSE_FORMAT_REQUIREMENTS__> (required): Replaced with a list of requirements
+that dictates how the AI should format its response. This is essential so
+that cfme can parse the AI's response.
+Note that these requirements merely dictate the format of the response, they
+do not influence the requirements for the commit message itself.
+
<__INSTRUCTIONS__> (optional): Replaced with any instructions passed via the
+[-i|--instructions] flag.
+
+
If no instructions are provided, this template string is replaced with an
+empty string.
+
You can leave out this template string from your prompt file if you don't
+plan to use it.
+
+
+
+
+
ℹ️cfme will warn you if you have template strings in your prompt file that
+are not defined in your variables file, and vice versa, it will warn you if
+you have variables defined that are not used in your prompt file.
+
+
For example, the simplest prompt file could look like this:
+
Generate a commit message for the following changes:
+<__GIT_DIFF__>
+<__RESPONSE__REQUIREMENTS__>
+
A more complex prompt file could look like this:
+
You are an expert software developer who writes clear and concise commit messages.
+Your task is to generate a commit message based on the following instructions,
+if present:
+<__INSTRUCTIONS__>
+The commit message should follow these guidelines:
+<__COMMIT_MESSAGE_GUIDELINES__>
+Here are the changes that need to be committed:
+<__GIT_DIFF__>
+Generate <__NUM_COMMIT_MESSAGES__> commit message candidates that adheres to the
+above guidelines.
+Here are the test results for the changes:
+<__TEST_RESULTS__>
+<__RESPONSE__REQUIREMENTS__>
+
See below for how to write the corresponding variables file.
+
Writing a variables file
+
The variables file is a simple YAML file that defines key-value pairs
+for use in your prompt file.
+You can either define literal strings (single or multi-line), or
+command (sequences) that will be evaluated, and their output used as the value.
+
A valid variables file must have a top-level key vars, which contains
+an array of variable definitions.
+
A variable definition
+
+
must have a template_string key, which defines the
+template string to be replaced in the prompt file (including the <__ and
+__> delimiters).
+
must have either a
+
+
value key, which defines the literal string value to replace the
+template string with, or
+
value_from key, which defines a command (sequence) to be executed.
+The output of those commands is used as the value to replace the template
+string with.
+
+
+
+
For example, the variables file corresponding to the above complex prompt
+could look like this:
+
vars:
+ - template_string: "<__COMMIT_MESSAGE_GUIDELINES__>"
+ value: |
+ - Use the imperative mood in the subject line.
+ - Limit the subject line to 50 characters.
+ - Wrap the body at 72 characters.
+ - Use the body to explain what and why vs. how.
+ - template_string: "<__NUM_COMMIT_MESSAGES__>"
+ value: "3"
+ - template_string: "<__TEST_RESULTS__>"
+ value_from: "pnpm test:unit --reporter=summary"
+
+
+
ℹ️ If you want to do so, you can use define default <__INSTRUCTIONS__>
+variable in your variables file, which you can overwrite by passing the
+[-i|--instructions] flag at runtime.
+You can also just leave out the <__INSTRUCTIONS__> template string
+from the variables file, and cfme will replace it with an empty string
+if no instructions are provided at runtime.
+
+
Environment Variables
+
cfme supports the following environment variables for configuration:
+
CFME_CONFIG_DIR
+ default: ${XDG_CONFIG_HOME:-$HOME/.config}/cfme
+ help: Directory to store configuration files for Commit For Me
+CFME_PROMPT_DIR
+ default: ${CFME_CONFIG_DIR}/prompts
+ help: Directory to store prompt files for Commit For Me
+CFME_PROMPT_TYPE
+ default: conventional-commits
+ help: The default subfolder to process prompts from.
+CFME_DEFAULT_PROMPT_FILE
+ default: ${CFME_PROMPT_DIR}/${CFME_PROMPT_TYPE}/default.md
+ help: Path to the default prompt file for generating commit messages
+ (overridden by --file flag)
+CFME_DEFAULT_PROMPT_VARIABLES_FILE
+ default: ${CFME_PROMPT_DIR}/${CFME_PROMPT_TYPE}/default-vars.yaml
+ help: Path to the file containing prompt variables.
+CFME_SILENT_MODE
+ default: "false"
+ help: If set to true, suppresses all non-error output.
+ (the [-s|--silent] flag does the same)
+
+
Recommendations:
+
+
keep CFME_CONFIG_DIR and CFME_PROMPT_DIR at their default values,
+unless you have a specific reason to change them.
+
set CFME_PROMPT_TYPE to the prompt type you want to use by default
+(e.g. conventional-commits), if you have multiple prompt types set up.
+(see Customization section above for more information)
+
keep the structure of CFME_DEFAULT_PROMPT_FILE and
+CFME_DEFAULT_PROMPT_VARIABLES_FILE as is, (i.e. keep the
+${CFME_PROMPT_DIR}/${CFME_PROMPT_TYPE}/ prefix)
+
+
For advanced users, you can also set where cfme fetches the default files
+from, should they not exist on your system. This requires $CFME_PROMPT_TYPE
+to be set something other than conventional-commits.
+This is useful if you maintain your own fork with custom default prompts you want
+to use.
+
CFME_DEFAULT_PROMPT_FILE_FETCH_URL
+ default: https://raw.githubusercontent.com/codevogel/commit-for-me/refs/heads/main/defaults/prompts/conventional-commits/default.md
+ help: URL used to fetch the default prompt file if it does not exist locally.
+CFME_DEFAULT_PROMPT_VARIABLES_FILE_FETCH_URL
+ default: https://raw.githubusercontent.com/codevogel/commit-for-me/refs/heads/main/defaults/prompts/conventional-commits/default-vars.yaml
+ help: URL used to fetch the default prompt variables file if
+ it does not exist locally.
+
+
Integration into other tools
+
Lazygit
+
Lazygit is awesome.
+But cfme allows you to be even lazier! You can use cfme
+from within Lazygit's TUI to generate commit messages:
+
+
Just add the following custom command to your Lazygit config file
+(e.g. ~/.config/lazygit/config.yml):
And now, use lazygit to stage your changes, then just press Ctrl+a
+to run cfme and generate a commit message for the staged changes!
+
Other tools
+
As cfme is just a bash script, you can easily integrate it into
+other tools and workflows.
+Have a look at the Lazygit section above for an example
+of how to do this.
+
The -r and -m flags are especially interesting for integration
+into other tools, as they allow you to pipe the AI-generated
+response or the reviewed commit message into other tools, instead
+of committing it directly.
+
The -p and -v flags allow you to specify custom prompt
+and variables files, which is useful if you want to have
+different prompts for different workflows.
+
I'm sure you can come up with many more use cases!
+If you ever get stuck integrating cfme into your workflow,
+or if you need an additional feature to make the integration easier,
+feel free to ask for help. Just open up an issue!
+
Contributing
+
If you would like to contribute to this project, you're more than welcome
+to do so!
+Feel free to open up issues or submit pull requests.
+
Example contributions include:
+
+
Wrote a prompt that you think delivers awesome results? Share it as a
+new prompt type!
+
Found a way to improve the default prompt or variables? Submit a PR!
+
Added a new feature or fixed a bug? Pull request welcome!
+
Want to improve the documentation? Contributions are welcome!
+
+
See CONTRIBUTING.md for more information on how to contribute.
+
License
+
This project is licensed under the MIT License. See the LICENSE
+file for details.
The most important difference to note in this shader are:
+
+
This shader uses updated syntax (Compatible with Godot 4.4+)
+
It adds a parameter 'use_front' that allows for the Canvas Item to have a front and the back side.
+
The UV's will scale so the textures are applied as you would expect any other 3D object to behave.
+
It adds an example use case script for simulating 3D cards in a 2D card game, which uses the shader applied on a SubViewportContainer, allowing you to layout the cards with 2D UI elements while retaining their 3D looks.
The best way to explore this shader's usage is to clone this project and open it in Godot 4.4+.
+
General Usage
+
+
Create a new ShaderMaterial and assign this shader to it.
+
Apply the ShaderMaterial to a CanvasItem, such as a TextureRect or SubViewportContainer.
+
Adjust the shader parameters to achieve the desired perspective effect.
+
+
Card Game Example
+
+
Create a new SubViewportContainer node.
+
Add a SubViewport as a child of the SubViewportContainer.
+
Add a VBoxContainer as a child of the SubViewport.
+
Add a TextureRect as a child of the VBoxContainer. This will be used to display the card art.
+
Add a CardContents node as a child of the VBoxContainer. This will be used to display any UI elements on the card.
+
Add any Controls of your liking to the CardContents node.
+
Assign the Faux 3D Perspective shader to a ShaderMaterial and assign it to the SubViewportContainer's material property.
+
+
See the Card.gd script below for an example of how to control the shader parameters and switch between front and back art based on rotation.
+The latest version can be found in Card.gd, but here is a copy for convenience:
+
# An example of a simulating a 3D card with a 2D TextureRect
+# using the Faux 3D Perspective shader by CodeVogel (https://github.com/codevogel/faux-3d-perspective-shader-godot)
+@tool
+extends SubViewportContainer
+class_name Card
+
+@export var front_art: Texture2D = preload("uid://d0cdfxs15e68h"):
+ set(value):
+ front_art = value
+ _refresh()
+@export var back_art: Texture2D = preload("uid://d2qctukn38v5q"):
+ set(value):
+ back_art = value
+ _refresh()
+@export var cull_backface: bool = false
+
+@onready var art_texture_rect: TextureRect = $SubViewport/ArtTextureRect
+@onready var card_contents: Control = $SubViewport/CardContents
+
+@export_range(1, 120, 1) var simulated_camera_fov: float = 60:
+ set(value):
+ simulated_camera_fov = value
+ _refresh()
+@export_range(-360, 360, 1) var rotation_y: float = 0.0:
+ set(value):
+ rotation_y = value
+ _refresh()
+@export_range(-360, 360, 1) var rotation_x: float = 0.0:
+ set(value):
+ rotation_x = value
+ _refresh()
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var warnings: PackedStringArray = []
+ if not front_art:
+ warnings.append("Front art texture is not assigned.")
+ if cull_backface:
+ if back_art:
+ warnings.append(
+ "Back art texture will not be visible because backface culling is enabled."
+ )
+ elif not back_art:
+ warnings.append("Back art texture is not assigned.")
+ if not (material is ShaderMaterial):
+ warnings.append("CardArt requires a ShaderMaterial to function properly.")
+ return warnings
+
+
+func _ready():
+ if not Engine.is_editor_hint():
+ _refresh()
+
+
+func _refresh():
+ if not (material is ShaderMaterial):
+ return
+ if not card_contents or not art_texture_rect:
+ return
+ var shader_material := material as ShaderMaterial
+ shader_material.set_shader_parameter("rot_y_deg", rotation_y)
+ shader_material.set_shader_parameter("rot_x_deg", rotation_x)
+ shader_material.set_shader_parameter("cull_backface", cull_backface)
+ shader_material.set_shader_parameter("fov", simulated_camera_fov)
+ _refresh_texture()
+
+
+func _refresh_texture():
+ if not front_art or not back_art:
+ return
+
+ var rot_x_deg = wrapf(rotation_x, 0, 360)
+ var rot_y_deg = wrapf(rotation_y, 0, 360)
+ var front_facing_over_x = rot_x_deg < 90 or rot_x_deg > 270
+ var front_facing_over_y = rot_y_deg < 90 or rot_y_deg > 270
+ var use_front = front_facing_over_y == front_facing_over_x
+ card_contents.visible = use_front
+ art_texture_rect.texture = front_art if use_front else back_art
+ material.set_shader_parameter("use_front", use_front)
A powerful validation plugin for Godot that catches errors before they reach
+runtime. Validate scenes, nodes, and resources using a declarative, test-driven
+approach, in either gdscript or C#. No @tool required!
Copy the addons/godot_doctor folder to your project's addons/ directory.
+(Optional:) If you don't use C# in your project, you can safely delete the
+addons/godot_doctor/core/csharp and the
+addons/godot_doctor/examples/csharp directories.
+
+
+
Enable the plugin in Project Settings > Plugins
+
+
+
(Optional:) Adjust the settings asset in addons/godot_doctor/settings.
Godot Doctor is a Godot plugin that validates your scenes and nodes using a
+declarative, test-driven approach. Instead of writing procedural warning code,
+you define validation conditions using callables that focus on validation logic
+first, with error messages as metadata.
+
Why Use Godot Doctor?
+
No-code default validations
+
Realistically, when you add any @export variables, you don't want them to stay
+unassigned. Nor do you want to @export a string only for it to stay empty. But
+we often forget to assign a value to these. So, new in Godot Doctor v1.1 are
+default validation conditions:
+
Godot Doctor will validate any nodes that have scripts attached to them (and any
+opened resource), scan it's @export properties, and automatically reports on
+unassigned objects and empty strings, without even needing to write a single
+line of validation code!
+
+
ℹ️ You can turn off default validations alltogether in the settings asset, or
+you can add scripts to the ignore list, which will only disable default
+validations for those specific scripts.
+
+
No @tool Required
+
Unlike
+_get_configuration_warnings(),
+Godot Doctor works without requiring the
+@tool
+annotation on your scripts. This means that you no longer have to worry about
+your gameplay code being muddied by editor-specific logic.
+
See the difference for yourself:
+
+
Or how about this:
+
+
Our gameplay code stays much more clean and focused!
+
+
+
+
+More examples
+
+
+
+
+
Verify type of PackedScene
+
Godot has a problem with PackedScene type safety.
+We can not strongly type PackedScenes.
+This means that you may want to instantiate a scene that represents a Friend,
+but accidentally assign an Enemy scene instead. Oops! Godot Doctor can
+validate the type of a PackedScene, ensuring that the root of the scene that
+you are instancing is of the expected type (e.g. has a script attached of that
+type), before you even run the game.
+
GDscript:
+
## Example: A validation condition that checks whether the `PackedScene`
+## variable `scene_of_foo_type` is of type `Foo`.
+ValidationCondition.is_scene_of_type(scene_of_foo_type, Foo)
+
C#:
+
// Example: A validation condition that checks whether the `PackedScene`
+// variable `PackedSceneOfFooType` is of type `Foo`.
+ValidationCondition.IsSceneOfType<Foo>(PackedSceneOfFooType)
+
+
+
+
+
+For verifying the type of an inherited `PackedScene`, you can use any of the
+following search strategies provided by the `ValidationCondition` class:
+
+
enum InheritanceSearchStrategy {
+ ## Only consider the root node of the PackedScene referenced,
+ ## regardless of whether it has a script or not.
+ ## i.e. the root node of the directly referenced scene,
+ ## even if it has a scene as a parent scene.
+ DIRECT,
+ ## Only consider the very topmost root node of the PackedScene's inheritance chain,
+ ## regardless of whether it has a script or not.
+ ## i.e. the root node of the base scene that has no further parents.
+ TOPMOST_ROOT,
+ ## Only consider the first root node in the PackedScene's inheritance chain,
+ ## that has a script attached.
+ ## i.e. the root node of the first parent scene that has a script, starting
+ ## from the directly referenced scene and moving up the inheritance chain.
+ FIRST_SCRIPT_ROOT,
+ ## Only consider the last root node in the PackedScene's inheritance chain,
+ ## that has a script attached.
+ ## i.e. the root node of the last parent scene that has a script.
+ LAST_SCRIPT_ROOT
+}
+
+
+
Automatic Scene Validation
+
Validations run automatically when you save scenes, providing immediate feedback
+during development. Errors are displayed in a dedicated dock, and you can click
+on them to navigate directly to the problematic nodes.
+
+
Validate Nodes AND Resources
+
Godot Doctor can not only validate nodes in your scene, but Resource scripts
+can define their own validation conditions as well. Very useful for validating
+whether your resources have conflicting data (i.e. a value that is higher than
+the maximum value), or missing references (i.e. an empty string, or a missing
+texture).
+
Test-Driven Validation
+
Godot Doctor encourages you to write validation logic that resembles unit tests
+rather than write code that returns strings containing warnings. This
+encourages:
+
+
Testable validation logic
+
Organized code
+
Better maintainability
+
Human-readable validation conditions
+
Separation of concerns between validation logic and error messages
+
+
Declarative Syntax
+
Where _get_configuration_warnings() makes you write code that generates
+strings, Godot Doctor lets you design your validation logic separately from the
+error messages, making it easier to read and maintain.
+
Syntax
+
GDscript:
+
Just add a _get_validation_conditions() method script that returns an array of
+ValidationCondition objects to any .gd script:
+
func _get_validation_conditions() -> Array[ValidationCondition]:
+ return [
+ ValidationCondition.new(
+ func(): return health > 0,
+ "Health must be greater than 0"
+ ),
+ ValidationCondition.simple(
+ health > 0,
+ "Health must be greater than 0"
+ ),
+ ValidationCondition.is_scene_of_type(scene_of_foo_type, Foo)
+ ]
+
Note: You do not need to add @tool to Node scripts for this to work, so you
+can keep your gameplay code clean and focused! However, if you want to validate
+a Resource script, you will need to add @tool to that script, due to
+engine limitations. However, Resource scripts don't need to be guarded against
+running editor code in runtime, so the @tool annotation should be all you
+need.
+
C#:
+
Implement the IValidatable interface and a public implementation of the
+GetValidationConditions() method that returns a Godot.Collections.Array.
+(This interface is optional, but it makes it easier to implement consistently).
+
Create an System.Array<VAlidationCondition> of
+GodotDoctor.Core.Primitives.ValidationCondition objects, then convert it to a
+Godot.Collections.Array using the ToGodotArray() extension method:
+
public Array GetValidationConditions()
+{
+ ValidationCondition[] conditions =
+ [
+ new ValidationCondition(
+ () => InitialHealth > 0,
+ "Initial health must be greater than 0"
+ ),
+ ValidationCondition.Simple(
+ InitialHealth > 0,
+ "Initial health must be greater than 0"
+ ),
+ ValidationCondition.IsSceneOfType<Foo>(SceneOfFooType)
+ ];
+ return conditions.ToGodotArray();
+}
+
+
Note: Just like with GDScript, you do not need to add the [Tool] attribute to
+Node scripts for this to work, so you can keep your gameplay code clean and
+focused! However, if you want to validate a Resource script, you will need
+to add the [Tool] attribute to that script, due to engine limitations.
+However, Resource scripts don't need to be guarded against running editor code
+in runtime, so the [Tool] attribute should be all you need.
+
ValidationCondition
+
The core of Godot Doctor is the ValidationCondition class, which takes a
+callable and an error message:
+
GDscript:
+
# Basic validation condition
+var condition = ValidationCondition.new(
+ func(): return health > 0,
+ "Health must be greater than 0"
+)
+
C#:
+
// Basic validation condition in C#
+var condition = new ValidationCondition(
+ () => health > 0,
+ "Health must be greater than 0"
+);
+
+
Optionally, you can also pass one of three severity levels (INFO, WARNING,
+ERROR) as a third argument, which will adjust at what level of severity the
+error is reported in the Godot Doctor dock:
+
GDscript:
+
# Validation condition with severity level
+var condition = ValidationCondition.new(
+ func(): return health > 0,
+ "Health must be greater than 0",
+ ValidationCondition.Severity.ERROR
+)
+
C#:
+
// Validation condition with severity level in C#
+var condition = new ValidationCondition(
+ () => health > 0,
+ "Health must be greater than 0",
+ ValidationCondition.Severity.ERROR
+);
+
+
Simple validations
+
For basic boolean validations, use the convenience simple() method, allowing
+you to skip the func() wrapper:
+
GDscript:
+
# Equivalent to the above, but more concise
+var condition = ValidationCondition.simple(
+ health > 0,
+ "Health must be greater than 0"
+)
+
C#:
+
// Equivalent to the above, but more concise in C#
+var condition = ValidationCondition.Simple(
+ health > 0,
+ "Health must be greater than 0"
+);
+
+
Predefined Common Validation Conditions
+
There's also a bunch of often-used validation conditions available as static
+methods on the ValidationCondition class, such as is_scene_of_type,
+is_instance_valid, is_string_not_empty, and more, which saves you time
+writing common validation logic.
var condition = new ValidationCondition(
+ ComplexValidationLogic(value),
+ "Complex validation failed"
+);
+
+// C# implementation of the complex logic in a separate method
+private static bool ComplexValidationLogicImplementation(int value) {
+ // Complex logic here
+};
+
+// Wrapper function to return a Godot Variant from the implementation
+private static System.Func<Variant> ComplexValidationLogic(int value) => () => ComplexValidationLogicImplementation(value);
+
+
+
+
Nested Validation Conditions
+
Making use of variatic typing, Validation conditions can return arrays of other
+validation conditions, allowing you to nest validation logic where needed:
+
GDscript:
+
ValidationCondition.new(
+ func() -> Variant:
+ if not is_instance_valid(weapon_resource):
+ # We return false in case the weapon resource is null
+ return false
+ # or an array of this resource's conditions in case it is valid.
+ return weapon_resource.get_validation_conditions(),
+ "Weapon resource validation failed"
+)
+
C#:
+
new ValidationCondition(
+ () =>
+ {
+ if (IsInstanceValid(WeaponResource))
+ {
+ return WeaponResource.GetValidationConditions();
+ }
+ return false;
+ },
+ "Weapon resource is not valid."
+)
+
+
IValidatable Interface
+
The C# wrapper also includes a IValidatable interface that you can implement
+on your C# scripts, which makes it easier to add validation conditions to your
+script without needing to remember the exact method signature of
+GetValidationConditions():
+
public partial class ExampleMyEnemy : Node, IValidatable
+{
+ /// <summary>The health value the enemy starts with.</summary>
+ [Export]
+ public int InitialHealth { get; set; } = 120;
+
+ /// <summary>The maximum health value the enemy can have.</summary>
+ [Export]
+ public int MaxHealth { get; set; } = 100;
+
+ public Array GetValidationConditions()
+ {
+ ValidationCondition[] conditions =
+ [
+ ValidationCondition.Simple(
+ InitialHealth <= MaxHealth,
+ "Initial health should not be greater than max health."
+ ),
+ ];
+ return conditions.ToGodotArray();
+ }
+}
+
+
Running Godot Doctor on the CLI
+
Godot Doctor can be run from the command line, allowing you to integrate it into
+your CI/CD pipeline or run it as a standalone validation tool. While using it in
+the editor provides real-time feedback, running it on the CLI can be useful for
+automated checks during development or before commits, ensuring your entire
+project adheres to your validation rules.
+
To run Godot Doctor on the CLI:
+
+
+
Create a GodotDoctorValidationSuite resource in your project. By default,
+it will generatively collect all scenes and resources in your project. You
+can also exclude specific scripts or directories in the suite asset from this
+collection process, or create multiple custom validation suites that only
+validate specific scenes or resources.
+
+
ℹ️ There is an
+example that goes
+more in depth on how to set up validation suites.
+
+
+
+
Assign the suite resource to the validation_suites property of the
+GodotDoctorSettings resource
+(addons/godot_doctor/settings/godot_doctor_settings.tres).
+
+
+
run Godot Doctor on the CLI, use the following command:
ℹ️ The --quit-after 30 flag is used to ensure that Godot exits after 30
+seconds, just in case there the plugin doesn't initialize properly. You can
+adjust this timer as needed.
+
+
+
+
The output is presented in a tree structure, making it easy to identify which
+scenes and nodes have validation issues:
+
+
The CLI output exits with a non-zero status code if any validation conditions
+fail, making it easy to integrate into CI/CD pipelines.
+
+
Currently, there is no proper built-in way to have an EditorPlugin wait for
+the editor to finish initializing when running in headless mode. There is an
+open proposal
+to address this, but until then, timing the run of the CLI is a bit hacky.
+
So as a workaround, the plugin currently detects this through an internal
+editor hook (_set_window_layout). However, this hook is not guaranteed to
+fire in all environments. For example, on some CI runners there is no saved
+editor layout, and the _set_window_layout may not be called. To handle this,
+Godot Doctor provides two fallback timer settings in the GodotDoctorSettings
+resource (addons/godot_doctor/settings/godot_doctor_settings.tres).
+fallback_cli_delay_before_start determines how long to wait for the editor
+hook to fire before starting validation regardless of editor initialization,
+and fallback_cli_delay_before_quit determines how long to allow the
+validation run to execute before force-quitting with exit code 1. This
+ensures that the CLI validation process doesn't hang indefinitely if something
+goes wrong. (Note that these timeouts are separate from the --quit-after
+flag passed to Godot, which is used for when the plugin doesn't initialize at
+all.) You can adjust these timeouts as needed based on the expected
+initialization time of your project, to save on runner minutes.
+
+
CI/CD Integration
+
You can integrate Godot Doctor into your CI/CD pipeline (e.g., GitHub Actions,
+GitLab CI, Jenkins) to automatically validate your project on every push or pull
+request. This helps catch issues early and maintain code quality across your
+team.
+
An example GitHub Actions workflow may look like this:
+
name: "Godot Doctor"
+
+on:
+ # Allow the workflow to be triggered manually from the GitHub Actions tab
+ workflow_dispatch:
+ # Trigger the workflow on push to any branch
+ push:
+ branches: ["**"]
+ # Trigger the workflow on pull request to any branch
+ pull_request:
+ branches: ["**"]
+
+jobs:
+ # This job runs the Godot Doctor CLI to analyze the project and (optionally) generate a report
+ godot-doctor:
+ name: "Run Godot Doctor CLI 🩺"
+ runs-on: ubuntu-latest
+ steps:
+ # Checkout the repository to access the Godot project files
+ - uses: "actions/checkout@v6.0.2"
+ # Install Godot
+ - name: "Install Godot"
+ run: |
+ GODOT_VERSION="4.6.1"
+ wget -q "https://github.com/godotengine/godot/releases/download/${GODOT_VERSION}-stable/Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip"
+ unzip -q "Godot_v${GODOT_VERSION}-stable_linux.x86_64.zip"
+ mv "Godot_v${GODOT_VERSION}-stable_linux.x86_64" /usr/local/bin/godot
+ chmod +x /usr/local/bin/godot
+ # Import the project first to ensure that it is ready for analysis.
+ - name: Import project
+ # This quits as soon as the project is imported, which is sufficient
+ # for preparing the project for analysis
+ # We use `--quit-after 30` as a failsafe so the runner doesn't hang
+ # indefinitely if something goes wrong during importing
+ # You may want to adjust this timeout if you find that your project
+ # takes longer than 30 seconds to import.
+ run: godot --headless --editor --quit --quit-after 30
+ - name: "Run Godot Doctor CLI"
+ # Now we run Godot Doctor again, this time running Godot Doctor through the CLI.
+ # Again, we use `--quit-after 30` as a failsafe to prevent hanging indefinitely.
+ run: godot --headless --editor --quit-after 30 -- --run-godot-doctor
+ # Optional: When using the `enable_xml_report` setting, you can upload
+ # the generated XML report as an artifact for later analysis
+ - name: "Upload Godot Doctor Report"
+ # Run regardless of the success or failure of the previous step,
+ # as we want to know the results of Godot Doctor even if
+ # it finds issues in the project, and thus fails the previous step.
+ if: always()
+ uses: actions/upload-artifact@v7
+ with:
+ # The name of the artifact can be anything you like.
+ # Here we use "godot-doctor-report" for clarity.
+ name: godot-doctor-report
+ # The path to the generated XML report.
+ # This should match the path you configured in the settings resource.
+ path: tests/reports/godot_doctor_report.xml
+ # Optional: You can also summarise the report in the workflow logs for a quick overview.
+ # (See the next section 'Generating a JUnit-like XML Report' for more details
+ # on the XML report and how to generate it.)
+ - name: Summarise report
+ if: always()
+ # The contents of this script can be found in the `cli_example` example.
+ run:
+ python3 .github/workflows/scripts/godot_doctor_job_summary_reporter.py
+
+
Placing this file at .github/workflows/godot_doctor.yaml in your repository
+will set up the workflow to run on every push and pull request, installing
+Godot, importing the project, and executing Godot Doctor in headless mode. If
+any validation conditions fail, the workflow will exit with a non-zero status,
+causing the check to fail and alerting the developers to the issues that need to
+be addressed.
+
You can setup GitHub to require this check to pass before allowing pull requests
+to be merged, ensuring that all code merged into your main branches adheres to
+your validation rules.
+
Generating a JUnit-like XML Report
+
In addition to the console output, you can configure Godot Doctor to generate a
+JUnit-like XML report of the validation results. This makes it easier to parse
+the results of the report in later stages of your CI/CD pipeline, such as
+generating a
+Job Summary
+that shows the validation results in a human-readable format directly in the
+GitHub Actions UI. (An example of such a script is found in the
+cli_example.)
+
To enable XML report generation, set the export_xml_report property to true
+in the GodotDoctorSettings resource. Optionally, you can also specify the
+output directory and filename for the XML report using the
+xml_report_output_dir (default: res://tests/reports/) and
+xml_report_filename (default: godot_doctor_report.xml). properties.
+
+
ℹ️ Don't forget to add the output directory to your .gitignore, as you
+likely don't want to commit generated reports to your repository.
+
+
The XML report option will generate a report as such:
+
<?xml version="1.0" encoding="UTF-8"?>
+<testsuites tests="7" messages="9" failures="8" harderrors="4" warnings="4" infos="1" timestamp="2026-03-21T12:21:11">
+ <testsuite name="example_validation_suite.tres" path="res://addons/godot_doctor/examples/cli_example/example_validation_suite.tres" tests="7" messages="9" failures="8" harderrors="4" warnings="4" infos="1">
+ <testcase name="general_example.tscn" path="res://addons/godot_doctor/examples/gdscript/general_example/general_example.tscn" type="scene" tests="5" messages="6" failures="5" harderrors="3" warnings="2" infos="1">
+ <node name="FooSpawner" path="MyGame/FooSpawner" messages="2" failures="2" harderrors="1" warnings="1" infos="0">
+ <harderror>packed_scene_of_foo_type is not a valid instance.</harderror>
+ <warning type="promoted_to_error">packed_scene_of_foo_type is null.</warning>
+ </node>
+ <node name="MyEnemy" path="MyGame/MyEnemy" messages="1" failures="1" harderrors="0" warnings="1" infos="0">
+ <warning type="promoted_to_error">Initial health should not be greater than max health.</warning>
+ </node>
+ <node name="PlayerController" path="MyGame/PlayerController" messages="1" failures="1" harderrors="1" warnings="0" infos="0">
+ <harderror>player is not a valid instance.</harderror>
+ </node>
+ <node name="Player" path="MyGame/Player" messages="1" failures="0" harderrors="0" warnings="0" infos="1">
+ <info>Player name longer than 12 characters may cause UI issues.</info>
+ </node>
+ <node name="WeaponSpawner" path="MyGame/WeaponSpawner" messages="1" failures="1" harderrors="1" warnings="0" infos="0">
+ <harderror>weapon_resource is not a valid instance.</harderror>
+ </node>
+ </testcase>
+ <testcase name="example_scene_foo_type.tscn" path="res://addons/godot_doctor/examples/gdscript/general_example/example_scene_foo_type.tscn" type="scene" tests="1" messages="0" failures="0" harderrors="0" warnings="0" infos="0">
+ </testcase>
+ <testcase name="example_weapon.tres" path="res://addons/godot_doctor/examples/gdscript/general_example/example_weapon.tres" type="resource" tests="1" messages="3" failures="3" harderrors="1" warnings="2" infos="0">
+ <resource name="example_weapon.tres" path="res://addons/godot_doctor/examples/gdscript/general_example/example_weapon.tres" messages="3" failures="3" harderrors="1" warnings="2" infos="0">
+ <harderror>sprite is not a valid instance.</harderror>
+ <warning type="promoted_to_error">Damage should be a positive value.</warning>
+ <warning type="promoted_to_error">Melee reach should not be greater than ranged reach.</warning>
+ </resource>
+ </testcase>
+ </testsuite>
+</testsuites>
+
+
The xml report is JUnit-like, meaning it follows a structure somewhat
+similar to the XML reports generated by testing frameworks like JUnit. Godot
+Doctor takes some liberties with the structure to better fit the validation
+context, so here's a breakdown of the structure:
+
+
The top-level element is <testsuites>, which collects all the validation
+suites that were run.
+
Each <testsuite> element represents a GodotDoctorValidationSuite resource,
+tallying up the statistics for all the scenes and resources that were
+validated as part of that suite. The name attribute corresponds to the name
+of the suite resource, and the path attribute indicates the file path to
+that resource.
+
Inside each <testsuite>, there are <testcase> elements, which represent
+individual scenes or resources that were validated. The name attribute is
+the name of the scene or resource, the path attribute is the file path to
+that scene or resource, and the type attribute indicates whether it's a
+scene or a resource.
+
Within each <testcase>, there are either <node> elements or <resource>
+elements, depending on the type of the testcase.
+
+
<node> elements represent individual nodes in a scene that had validation
+messages reported. The name attribute is the name of the node, and the
+path attribute is the node's path within the scene.
+
<resource> elements represent individual resources that had validation
+messages reported. The name attribute is the name of the resource, and the
+path attribute is the file path to that resource.
+
+
+
Inside each <node> or <resource>, there are <harderror>, <warning>,
+and <info> elements, which represent the individual validation messages that
+were reported for that node or resource. The text content of these elements is
+the error message associated with the validation condition that failed.
+
Each <testsuites>, <testsuite>, <testcase>, <node>, and <resource>
+element includes attributes that tally up the total number of tests, messages,
+failures, hard errors, warnings, and infos that were reported at that level of
+the hierarchy.
+
+
How It Works
+
+
Automatic Discovery: When you save a scene, Godot Doctor scans all nodes
+for @export properties and a _get_validation_conditions() method
+
Instance Creation: For non-@tool scripts, temporary instances are
+created to run validation logic
+
Condition Evaluation: Each validation condition's callable is executed
+
Error Reporting: Failed conditions display their error messages in the
+Godot Doctor dock
+
Navigation: Click on errors in the dock to navigate directly to the
+problematic nodes
+
+
Examples
+
There are many examples available that help you better understand how to use
+Godot Doctor in your project, and how to write validation conditions for
+different use cases. You can find them all in
+the examples README.
+
Installation
+
+
Copy the addons/godot_doctor folder to your project's addons/ directory
+
Enable the plugin in Project Settings > Plugins
+
The Godot Doctor dock will appear in the editor's left panel
+
use_default_validations is on by default in the settings resource
+(addons/godot_doctor/settings/godot_doctor_settings.tres), so it will start
+reporting any of the default validations as
+soon as you save a scene.
+
Start adding custom validations by adding a _get_validation_conditions()
+method to your scripts, then save your scenes to see validation results!
+
+
License
+
Godot Doctor is released under the MIT License. See the LICENSE file for
+details.
+
Attribution
+
If you end up using Godot Doctor in your project, a line in your credits would
+be very much appreciated! 🐦
+
Contributing, Bug Reports & Feature Requests
+
Godot Doctor is open-source and welcomes any contributions! Feel free to open
+issues or submit pull requests on
+GitHub.
Initialize a p5.js project from the command line! 🐦
+
This script creates all the necessary files for a p5.js project, including an index.html, sketch.js, and style.css file. You can choose a directory to create the p5 project in (default: current directory, aka .), and the name of the sketch (default: sketch).
peck is a screen capture tool for Wayland. It provides easy ways to
+capture screenshots, record your screen, and optionally convert outputs to
+different formats.
+
peck isn't a tool that tries to reinvent the wheel. Rather, it's just a simple
+wrapper bash script that makes interfacing with grim and wf-recorder easier.
+
Features
+
+
Capture screenshots or record your screen by region.
+
Start and stop recordings easily.
+
Save to default directories, temporary directories, or a custom path.
+
Copy output directly to the clipboard.
+
Convert recordings to various formats (mp4, gif, webm, etc.) using ffmpeg.
+
Optionally sends notifications using libnotify about the capture status.
wayfreeze is not available through apt, so must be installed from source.
+Though wayfreeze is only needed for the --freeze argument, so can be omitted
+if you can live without freezing the screen before taking a screenshot.
There is an unofficial wayfreeze package
+on the AUR.
+
NixOS
+
# This example uses Home Manager. You can also just use environment.systemPackages
+home.packages = with pkgs; [
+ grim
+ slurp
+ wf-recorder
+ ffmpeg
+ wl-clipboard
+ wayfreeze
+];
+
+
Download and add peck to your PATH
+
Download the latest version of peck from the releases, then add it to your
+$PATH.
+
e.g. on Bash or ZSH:
+
curl -L -o some/path/peck https://github.com/codevogel/peck/releases/download/v0.1.0/peck
+chmod +x some/path/peck
+
+# If the path where you store peck is not in $PATH, be sure to add it to your path:
+echo 'export PATH="some/path:$PATH"' >> ~/.bashrc # for Bash
+source ~/.bashrc
+# or
+echo 'export PATH="some/path:$PATH"' >> ~/.zshrc # for Zsh
+source ~/.zshrc
+
+
Verify it runs with peck --help.
+
Example install with NixOS
+
An example peck.nix to add to your modules, which installs peck from the
+releases page, and installs the dependenceis using home manager:
This would set SUPER+SHIFT+[S|R|G] to capture a screenshot, recording, or gif
+respectively, instructing peck to store the file in a temporary dir (which is
+cleared upon next invocation), and copy the output file to the clipboard, ready
+for pasting.
+
peck stops recording when invoked again while recording, so to end a
+recording, just hit SUPER+SHIFT+[R|G] again while recording.
+
Usage
+
Recommended usage is by integrating peck into your window manager as above, but
+you can also run peck through the CLI.
+
Below are some examples that show how to use peck.
+
Basic Screenshot
+
peck
+
+
Captures a screenshot by region and saves it to the default screenshot directory
+(set with $PECK_SCREENSHOT_DIR, which defaults to $HOME/Pictures/).
+
Screenshot with Custom Path
+
peck ./foo.png
+peck bar/baz
+
+
Stores the screenshot to the specified path. (In this example, would store the
+screenshot at ./foo.png or ./bar/baz.png)
+
Recording
+
peck --record
+
+
Starts a screen recording by region, storing the recording in the default
+directory (set with $PECK_RECORDING_DIR, which defaults to
+$HOME/Recordings/).
+
Stop recording with CTRL-C (when launched from the CLI), or by running peck
+again.
+
Recording with Format Conversion
+
peck --record --format=gif
+
+
Starts a screen recording, and converts the output file to GIF using ffmpeg.
+
Temporary Screenshot to Clipboard
+
peck --temp --clipboard
+
+
Capture a screenshot, storing the output file in /tmp/peck/out (which is
+periodically cleared by peck to prevent clogging up the file system), and copy
+the output file to the clipboard.
Store the file in /tmp/peck/out. This is cleared upon each invocation of peck, so captures don't clog up your filesystem.
--freeze
-z
Freeze the screen before taking a screenshot. Conflicts with --record.
--format <file_format>
-f
Override output format (png, ppm, jpegfor screenshots, mp4, gif for recordings). Also allows for other recording formats, but these may need --filter to work.
Project Level Shortcuts (pls) is a command-line tool designed to streamline your workflow by allowing you to create, manage, and execute custom context-aware aliases for frequently used commands using portable .pls.yml files. Say goodbye to messy shell profiles, and say hello to pls! 🐦
+
Run your most frequently used commands with ease, e.g. pls test to run tests, pls clone my-repo to clone a repository under your name, pls open-here to open the current directory in your file manager, and whatever else you can think of!
+
Demo
+
+
Quick Start 🚀
+
+
+
Install
+Here's a quick guide to get pls working on your system.
+
# cd to directory that should house the pls script
+cd /path/to/pls/dir
+# download and make executable
+curl -sS https://raw.githubusercontent.com/codevogel/pls/main/release/pls > pls && chmod +x pls
+# add pls to your PATH
+echo 'export PATH="$PATH:/path/to/pls/dir"' >> ~/.bashrc
+
+
+
+
Try in Docker
+
If you want to try pls without installing it on your own system first, you can use the following commands:
+
# cd to directory that should house the Dockerfile
+cd ./pls-test
+# download the Dockerfile
+curl -sS https://raw.githubusercontent.com/codevogel/pls/main/docker/Dockerfile > Dockerfile
+# build and run the Docker container
+docker build -t plsbuntu . --no-cache
+docker run -it plsbuntu
+
+
+
+
Nix
+
Nix users can just add this module to their system.
pls allows you to store your aliases in pls files. You can either define system-wide aliases in a global file, or place project-specific aliases in a local file, e.g., in the root of your project directory. The pls file (by default named .pls.yml) contains a list of aliases and their corresponding commands. When you run pls <alias>, the command associated with that alias is executed. Aliases from a local, project-specific context take precedence over aliases from the global, system-wide context, allowing you to reuse the same alias in different contexts. For example, if you have two projects that use different testing frameworks, pls allows you to just run pls test in either directory, reducing the need to remember framework-specific commands.
+
pls supports single-line and multi-line commands, as well as parameterized commands. Additionally, it caches the commands you run, and warns you when an alias points to an uncached command. This lets you be sure that you are only running the commands that you expect.
+
+ ❓Why would I use pls?
+
+
Organized alias management: Instead of cluttering your shell profile with numerous aliases, pls lets you keep them organized in separate files, making it easier to manage and maintain your shortcuts.
+
Context-aware aliases:pls allows you to have project-specific aliases that override global ones, enabling you to reuse common alias names (like "test") across different projects with varying implementations.
+
Command cache verification: The command caching feature warns you when you're about to run a new or modified command, helping prevent accidental execution of potentially harmful commands.
+
Portability and synchronization: Easily sync your aliases between different machines (e.g., work and home computers) by managing your .pls.yml files.
+
Fuzzy command picker: The built-in alias picker (with optional fzf integration) makes it easy to find and execute aliases without remembering exact names.
+
Parameterized commands:pls supports parameterized commands, allowing you to pass arguments to your aliases.
+
Works with bash:pls is a lightweight shell script that works with any shell that supports bash syntax.
+
+
+
+ ❓How does it work under the hood?
+ If you execute an alias, pls will first query the global context (which is the file that lives at $PLS_GLOBAL). It then searches for the closest file that matches $PLS_FILENAME that lives in the current, or any of the parent directories (excluding $PLS_GLOBAL file). If it finds any conflicts (aliases that appear in both the global and local context), the command from the local file is picked. It then verifies whether this exact command has been ran before, taking into account the alias, the file which it is stored in, and the exact command contents. If the command is new, was moved to a different location, or has changed since the last execution, pls will prompt you to confirm the execution of the command. This is to prevent accidental execution of potentially harmful commands.
+
+
Getting started
+
Installation
+
This is a quick guide to get pls working on your system. 🔧🐦
+
Nix
+
Nix users can just add the following module to their config. (If you don't use home-manager, replace home.packages with environment.systemPackages.
pls hello --add --command "echo 'Hello, World!'" --scope local
+
+
+
ℹ️ Note: As pls supports combining flags, this is a shorter equivalent of the above command:
+
+
pls hello -ac "echo 'Hello, World!'" -s local
+
+
+
+
+
+
To execute the alias, run pls hello.
+You should first be prompted to confirm that you indeed want to run this command. Enter y or Y and press Return.
+
+
ℹ️ Note: When you execute the alias another time, pls will have cached usage of the command, and you will not be prompted until the command (or location of the file) changes.
+
+
❯ ./pls hello
+Alias 'hello' was found in 'https://raw.githubusercontent.com/codevogel/pls/refs/heads/main/home/codevogel/work/pls/.pls.yml', but this command seems new.
+
+echo 'Hello, World!'
+
+Are you sure you want to invoke the above command? [y/n] y
+Hello, World!
+
+❯ ./pls hello
+Hello, World!
+
+
+
ℹ️ Note: When you add the -p flag, the command will be printed instead of executed. This is useful if you want to run the command in your main shell process, or want to pipe the command to run in a different shell.
+
+
+
+
You can also leave out the alias, and pls will launch an interactive picker that lets you choose from a list of available aliases. Try it now, just run pls !
+
+
+
Command overview
+
To keep the syntax for using pls as short as possible, and because we want to keep the namespace for aliases clean of commands, pls uses just a single root command: pls. This command can be followed by an alias, command arguments, or flags.
+
pls [alias] [command_args] [flags]
+
+
Operation Flags
+
This is an overview of the flags that trigger pls to perform alternative operations (instead of the default operation: executing an alias). Each of these operation flags conflict with one another: you can only use one at a time.
+
| Operation | Shorthand | Needs Flags | Optional Flags | Description |
+| ---------- | --------- | ------------------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
+| --add | -a | --command --scope | --force | Adds a new alias for a --command <command> to the pls file at a given --scope <[g]lobal, [l]ocal, [h]ere>. Use with --force to overwrite the alias if it exists. |
+| --delete | -d | --scope | | Delete the alias from the pls file at given --scope <[g]lobal, [l]ocal, [h]ere>. |
+| --list | -l | | --scope--print | List all aliases available in the current directory. Or, list aliases only from --scope <[g]lobal, [l]ocal, [a]ll>. Use with --print to print the command associated with each alias. | |
+
Other Flags
+
These are the flags that do not represent operations. Some of them are used to provide additional information to operations, or to modify the behavior of the root command.
Supplies a command to associate with the alias in the --add operation. Pass the command as a string, newline characters ('\n') are allowed.
--scope
yes
--add --delete --list
The selected scope for --add, --delete, and --list operations. Allowed scopes are [g]lobal, [l]ocal, [a]ll, [h]ere. The 'all' scope is exclusive to --list. The 'here' scope is excluded from --list.
--print
root command --list
Prints the command instead of executing it when used with root command. Useful for piping to other commands or shells. Or, if used in conjunction with --list, it additionally prints the command for each alias.
--force
--add
Overwrite the alias if it already exists when using the --add operation.
--clear-cache
Before doing anything, clears the cache of all commands that have been run with pls.
--help
Show the help.
--version
Show the version number.
+
Examples
+
View EXAMPLES.md (here) to see some examples of how to use commands that pls provides.
# Clone a repository under my account by name
+$ pls clone svelte-db-portal
+
+# Sets the display brightness to 100%
+$ pls bright 100%
+
+## The following two commands require you to 'Execute commands in the main shell' (see next section).
+
+# Takes you to a frequently used directory (in this case ~/.config/nvim/lua)
+$ plz nvimconf
+
+# Launches <executable>, disowns the process from the shell, and then closes the terminal.
+$ plz se <executable>
+
+
Execute commands in the main shell
+
If your main shell is bash
+
If you want pls to execute the command in your main shell process, and your shell is bash, then you can simply add . or source in front of your pls command:
+
# Executes 'cd ~/work/godot/' in the main shell process
+. pls godot
+
+
You could setup an alias for this in your .bashrc:
+
alias plz=". pls"
+
+
If your main shell is not bash
+
If your main shell is not bash, you can still use pls in your main shell process. Just know that the command will be executed in a bash subshell. If you want to execute the command in your main shell process, you can make use of the -p flag and eval. Here is an example for zsh, in which we set up a function plz that executes the command in the main shell process:
+
We create a zsh function at ~/.zshfuncs/plz
+
cmd="$(pls "$1" -p)"
+execute_command() { # Use a function so we can use parameterized commands as well
+ eval "$cmd"
+}
+execute_command ${@:2}
+
+
Then we load it in our .zshrc:
+
# Load the zsh function 'plz'
+echo 'fpath=(~/.zshfuncs "${fpath[@]}")' >> ~/.zshrc
+echo 'autoload -Uz plz' >> ~/.zshrc
+
+
Now say we have the following in our .pls.yml file:
+
commands:
+ - alias: go
+ command: cd $1 && ls -al
+
+
We can use plz in our zsh shell:
+
plz go ~/work/godot/ # cd's to ~/work/godot/ and lists the contents
+
+
Real-world example
+
Here are some real-world examples that show how I use pls.
+
Easy git clones
+
Using the alias below, I can easily clone any of my repositories by just running pls clone <repo-name>.
No more remembering the full git SSH URL, or having to copy-paste it from the browser!
+Expanding this further, I use the following alias to quickly clone repositories by other users than myself:
After using z to jump to a project directory, I can quickly launch a default tmux session for that project by running pls dev:
+
- alias: dev
+ command: |
+ ~/startup/default
+
+
This runs a tmux startup script defined in my .dotfiles repository, which launches a tmux session with a few default windows, one running neovim, one running lazygit, and one running a shell.
+
Running and disowning processes
+
Sometimes I want to launch a GUI application from the terminal. But closing that very terminal would also close the application. We can use disown to prevent this from happening. But typing out the correct sequence of commands can become tedious. So, I created the following alias:
+
- alias: se
+ command: |
+ $1 &
+ disown
+ exit
+
+
Now, when I run plz se <executable>, it launched the executable, disowns it from the terminal, and closes the terminal.
+
Switching keyboard layouts
+
I usually type on a Sofle Choc, a split keyboard, which has CTRL and CAPS LOCK swapped in it's firmware. When I want to use my laptop keyboard, for example when I'm on the go, I need to swap these keys in software instead. I created the following alias to make this easy:
pls uses YAML files to store aliases. YAML is a human-readable data serialization format that is easy to read and write, which should make adding commands a pretty straight-forward experience. An example of a properly formatted .pls.yml file is shown here:
+
commands:
+ - alias: my-alias
+ command: echo "hello from my-alias!"
+ - alias: my-multiline-command
+ command: |
+ echo "echo!"
+ echo "... echo!"
+ - alias: work
+ command: cd ~/some/really/long/path/to/my/directory/work
+ - alias: greet me
+ command: |
+ echo "Hello, my name is $1!"
+
+
A few points of interest here:
+
+
The commands key should be the only root key of the YAML file, and must always be present. (This means that at minimum, a pls file should contain commands:)
+
The commands key contains an array of objects (which may be empty), and can hold:
+
+
An alias key that contains a single-line string representing the alias you want to use. (Spaces and symbols are allowed, but discouraged.)
+
The command key that contains either a single-line or multi-line string containing the command you want to execute. Multi-line commands must be preceded by a | or |- character and indented.
+
The command key can also contain a parameterized command, where $1, $2, etc. are replaced with the arguments passed to the alias.
+
+
+
+
+
ℹ️ Don't worry too much about remembering all this - if you break any of these rules, you should get an error when running pls, pointing you in the right direction to correct the formatting.
+
+
Command cache
+
Each time you execute an alias, pls stores:
+
+
The alias that was executed
+
The exact command that was executed
+
The path to the file where the alias was found
+
+
This allows pls to warn you when you try to execute an alias that points to a different command than the one you have previously executed. This is especially useful when you are working on a project with multiple collaborators (maybe someone has changed one of the aliases), or when you have multiple aliases with the same name in different directories.
+
+
ℹ️ If you want to be informed about the contents of the command, regardless of it being cached already, or if you want to turn off the warning altogether, see the PLS_ALWAYS_VERIFY and PLS_ENABLE_CACHE_CHECK environment variables in the Configuration section.
+
+
To clear the cache, you can run pls clear_cache.
+
Configuration
+
pls can be configured using a .plsrc file, which is stored at the environment variable $PLS_RC. If you have not set $PLS_RC, then it will be created at $HOME/.config/pls/.plsrc by default, including the default values listed in the table below:
If set to true, pls will check the cache for the command before executing it.
true
PLS_ALWAYS_VERIFY
If set to true, pls will always prompt you to confirm the execution of the command, even if PLS_ENABLE_CACHE_CHECK is false
false
PLS_ENABLE_FZF
If set to true, pls will use fzf as the picker (given that you have it installed).
N/A
PLS_DIR
The directory where internal pls files are stored. Not recommended to place this in /tmp/ as that would delete the cache on reboot.
$HOME/local/.share/pls
+
Make sure each of these variables is on a new line in the .plsrc file, and that you are not missing any. If in doubt, you can always delete the .plsrc file and pls will recreate it with the default values.
+
Roadmap
+
+
Add support for single-line commands
+
Add support for multi-line commands
+
Add support for parameterized commands
+
Add context-aware aliases
+
Add command cache verification
+
List available aliases
+
Add alias picker
+
Add .rc file
+
Use pls with flags, preventing commands from taking up alias namespace.
+
What else would you like to see? Let us know by opening an issue!
+
+
Contributing
+
Please refer to the CONTRIBUTING.md file for more information on how to contribute to this project.