diff --git a/.antigravitycli/e547d5bd-3b28-4c52-8054-414343a96789.json b/.antigravitycli/e547d5bd-3b28-4c52-8054-414343a96789.json deleted file mode 120000 index 6f7c6a4..0000000 --- a/.antigravitycli/e547d5bd-3b28-4c52-8054-414343a96789.json +++ /dev/null @@ -1 +0,0 @@ -C:/Users/StephenBrown/.gemini/config/projects/e547d5bd-3b28-4c52-8054-414343a96789.json \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 5f0889c..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 73973b9..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Build Vitepress Site with pnpm and deploy to pages -on: - push: - branches: - - main - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: pages - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v6 - - name: Setup PNPM - uses: pnpm/action-setup@v4 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - cache: 'pnpm' - - name: Install dependencies - run: pnpm install - - name: Build site - run: pnpm run build - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: .vitepress/dist - - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - needs: build - runs-on: ubuntu-latest - name: Deploy - permissions: - pages: write - id-token: write - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 9e650de..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -dist -.vitepress/cache \ No newline at end of file diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/.vitepress/config.ts b/.vitepress/config.ts deleted file mode 100644 index 6d4672c..0000000 --- a/.vitepress/config.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { defineConfig } from 'vitepress' -import tailwind from '@tailwindcss/vite' -import { genFeed } from './genFeed.js' - -export default defineConfig({ - title: "steve's blog", - description: "c#, paragliding, sports", - cleanUrls: true, - themeConfig: { - nav: [ - { text: 'Home', link: '/' }, - ], - - search: { - provider: 'local' - }, - - socialLinks: [ - { icon: 'github', link: 'https://github.com/evolvedlight/evolvedlight.github.io' } - ] - }, - vite: { - plugins: [tailwind()] - }, - buildEnd: genFeed -}) diff --git a/.vitepress/genFeed.ts b/.vitepress/genFeed.ts deleted file mode 100644 index 1eb099b..0000000 --- a/.vitepress/genFeed.ts +++ /dev/null @@ -1,52 +0,0 @@ -import path from 'path' -import { writeFileSync } from 'fs' -import { Feed } from 'feed' -import { createContentLoader, type SiteConfig } from 'vitepress' - -const baseUrl = `https://brown.bg` - -export async function genFeed(config: SiteConfig) { - const feed = new Feed({ - title: 'Steve@brown.bg', - description: 'Steve\'s blog', - id: baseUrl, - link: baseUrl, - language: 'en', - image: 'https://brown.bg/logo.webp', - favicon: `${baseUrl}/favicon.ico`, - copyright: - 'Copyright (c) 2021-present, Steve Brown' - }) - - const posts = await createContentLoader('posts/*.md', { - excerpt: true, - render: true - }).load() - - posts.sort( - (a, b) => - +new Date(b.frontmatter.date as string) - - +new Date(a.frontmatter.date as string) - ) - - for (const { url, excerpt, frontmatter, html } of posts) { - feed.addItem({ - title: frontmatter.title, - id: `${baseUrl}${url}`, - link: `${baseUrl}${url}`, - description: excerpt, - content: html, - author: [ - { - name: frontmatter.author, - link: frontmatter.twitter - ? `https://twitter.com/${frontmatter.twitter}` - : undefined - } - ], - date: frontmatter.date - }) - } - - writeFileSync(path.join(config.outDir, 'feed.rss'), feed.rss2()) -} diff --git a/.vitepress/theme/Article.vue b/.vitepress/theme/Article.vue deleted file mode 100644 index e51b891..0000000 --- a/.vitepress/theme/Article.vue +++ /dev/null @@ -1,73 +0,0 @@ - - - diff --git a/.vitepress/theme/Author.vue b/.vitepress/theme/Author.vue deleted file mode 100644 index b6ab442..0000000 --- a/.vitepress/theme/Author.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/.vitepress/theme/Date.vue b/.vitepress/theme/Date.vue deleted file mode 100644 index e602f7a..0000000 --- a/.vitepress/theme/Date.vue +++ /dev/null @@ -1,18 +0,0 @@ - - - diff --git a/.vitepress/theme/Home.vue b/.vitepress/theme/Home.vue deleted file mode 100644 index 73b7263..0000000 --- a/.vitepress/theme/Home.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - diff --git a/.vitepress/theme/Layout.vue b/.vitepress/theme/Layout.vue deleted file mode 100644 index c4fdd55..0000000 --- a/.vitepress/theme/Layout.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - diff --git a/.vitepress/theme/NotFound.vue b/.vitepress/theme/NotFound.vue deleted file mode 100644 index a1d7496..0000000 --- a/.vitepress/theme/NotFound.vue +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts deleted file mode 100644 index 3b695c8..0000000 --- a/.vitepress/theme/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import './style.css' -import Layout from './Layout.vue' - -export default { - Layout -} diff --git a/.vitepress/theme/posts.data.ts b/.vitepress/theme/posts.data.ts deleted file mode 100644 index 1a5cb47..0000000 --- a/.vitepress/theme/posts.data.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { createContentLoader } from 'vitepress' - -interface Post { - title: string - url: string - date: { - time: number - string: string - } - excerpt: string | undefined -} - -declare const data: Post[] -export { data } - -export default createContentLoader('posts/*.md', { - excerpt: true, - transform(raw): Post[] { - return raw - .map(({ url, frontmatter, excerpt }) => ({ - title: frontmatter.title, - url, - excerpt, - date: formatDate(frontmatter.date) - })) - .sort((a, b) => b.date.time - a.date.time) - } -}) - -function formatDate(raw: string): Post['date'] { - const date = new Date(raw) - date.setUTCHours(12) - return { - time: +date, - string: date.toLocaleDateString('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric' - }) - } -} diff --git a/.vitepress/theme/style.css b/.vitepress/theme/style.css deleted file mode 100644 index 6557f4e..0000000 --- a/.vitepress/theme/style.css +++ /dev/null @@ -1,150 +0,0 @@ -@import "tailwindcss"; -@plugin "@tailwindcss/typography"; - -:root { - --c-brand: #3eaf7c; - --c-brand-light: #4abf8a; -} - -nav img { - vertical-align: middle; -} - -p img { - margin: 0px auto; -} - -.prose hr { - border-top: 1px solid #e5e7eb; -} - -.link { - color: var(--c-brand); -} - -.link:hover { - color: var(--c-brand-light); -} - -.header-anchor { - display: none; -} - -/** - * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML. - * Based on https://github.com/chriskempson/tomorrow-theme - * - * @author Rose Pritchard - */ -.token.comment, -.token.block-comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: #999; -} - -.token.punctuation { - color: #ccc; -} - -.token.tag, -.token.attr-name, -.token.namespace, -.token.deleted { - color: #e2777a; -} - -.token.function-name { - color: #6196cc; -} - -.token.boolean, -.token.number, -.token.function { - color: #f08d49; -} - -.token.property, -.token.class-name, -.token.constant, -.token.symbol { - color: #f8c555; -} - -.token.selector, -.token.important, -.token.atrule, -.token.keyword, -.token.builtin { - color: #cc99cd; -} - -.token.string, -.token.char, -.token.attr-value, -.token.regex, -.token.variable { - color: #7ec699; -} - -.token.operator, -.token.entity, -.token.url { - color: #67cdcc; -} - -.token.important, -.token.bold { - font-weight: bold; -} - -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - -.token.inserted { - color: #67cdcc; -} - -button.copy { - display: none; -} - -span.lang { - position: absolute; - right: 0.5em; - font-size: 0.75em; - color: #999; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - 'Liberation Mono', 'Courier New', monospace; -} - -.custom-block { - margin: 28px 0; - padding: 0 24px 2px; - border-radius: 8px; - overflow-x: auto; - position: relative; - font-size: 14px; - line-height: 1.3; - font-weight: 500; - color: #444; - background-color: #f9f9f9; -} -.custom-block .custom-block-title { - margin-bottom: 8px; - font-size: 15px; - font-weight: bold; -} - -.custom-block.tip { - border: 1px solid #42b883; -} -.custom-block.tip:before { - color: #42b883; -} diff --git a/404.html b/404.html new file mode 100644 index 0000000..5f43d6a --- /dev/null +++ b/404.html @@ -0,0 +1,18 @@ + + + + + + 404 | steve's blog + + + + + + +

404 Page Not Found

+ + + + \ No newline at end of file diff --git a/CNAME b/CNAME index 39ea3b6..999af1f 100644 --- a/CNAME +++ b/CNAME @@ -1 +1 @@ -brown.bg \ No newline at end of file +brown.bg diff --git a/README.txt b/README.txt deleted file mode 100644 index e380f78..0000000 --- a/README.txt +++ /dev/null @@ -1,12 +0,0 @@ -This is my personal blog. - -To build: - - Install pnpm - - -Windows: -``` -iwr https://get.pnpm.io/install.ps1 -useb | iex -pnpm install -pnpm run dev -``` \ No newline at end of file diff --git a/assets/app.31511a71.js b/assets/app.31511a71.js new file mode 100644 index 0000000..d0bb791 --- /dev/null +++ b/assets/app.31511a71.js @@ -0,0 +1 @@ +import{d as p,o,c as i,b as t,t as d,u as g,e as a,F as A,r as P,f,g as l,h as D,i as y,j as C,_ as S,k as v,a as T,l as b,m as I,n as N,w as R,p as L,q as V,s as F,v as j,R as E,x as H,y as O,C as B,z as M,A as G,B as K,D as U,E as W,G as q}from"./chunks/framework.f55bdc3d.js";const z="/logo.webp",J=t("dt",{class:"sr-only"},"Published on",-1),Q={class:"text-base leading-6 font-medium text-gray-500 dark:text-gray-300"},X=["datetime"],k=p({__name:"Date",props:{date:null},setup(s){const e=s;function n(){return new Date(e.date.time).toISOString()}return(r,_)=>(o(),i("dl",null,[J,t("dd",Q,[t("time",{datetime:n()},d(s.date.string),9,X)])]))}}),m=JSON.parse(`[{"title":"Running MassTransit queues with KEDA","url":"/posts/scaling-masstransit-jobs","excerpt":"

We have an interesting challenge in some of our microservices - we run lots and lots of microservices, all using a relatively small amount of memory, but the usage patterns may be unusual.\\nAll these microservices talk MassTransit - some also have external triggers, API endpoints or scheduled jobs.

\\n","date":{"time":1693828800000,"string":"September 4, 2023"}},{"title":"Creating Kerberos secured connections from Dotnet on Linux","url":"/posts/dotnet-kerberos","excerpt":"

For the last few years at my workplace we've been using Kerberos authentication to connect to the on-prem SQL Server databases. On Windows this just worked like magic, as the services ran as a Windows domain user and so could connect to the database without any additional configuration. However, when moving to Kubernetes and Linux systems, this didn't work out of the box.

\\n","date":{"time":1691236800000,"string":"August 5, 2023"}},{"title":"TV7 (IPTV from Init7) with Dream Machine and Unity Dream machine","url":"/posts/init7-tv7","excerpt":"

If you're an Init7 TV customer and you want to use the Unify Dream Machine as your IPTV receiver, I figured it out for you! Here's a step-by-step guide to help you get started.

\\n","date":{"time":1691150400000,"string":"August 4, 2023"}},{"title":"Dockerless docker builds in Github Actions with Dotnet 7","url":"/posts/github-actions-net7-docker","excerpt":"

How to setup a github build action that uses the dockerless build in .Net 7

\\n","date":{"time":1665748800000,"string":"October 14, 2022"}}]`),Y={class:"divide-y divide-gray-200 dark:divide-slate-200/5"},Z={class:"pt-6 pb-8 space-y-2 md:space-y-5"},tt={class:"text-3xl leading-9 font-extrabold text-gray-900 dark:text-white tracking-tight sm:text-4xl sm:leading-10 md:text-6xl md:leading-14"},et={class:"text-lg leading-7 text-gray-500 dark:text-white"},st={class:"divide-y divide-gray-200 dark:divide-slate-200/5"},at={class:"py-12"},nt={class:"space-y-2 xl:grid xl:grid-cols-4 xl:space-y-0 xl:items-baseline"},ot={class:"space-y-5 xl:col-span-3"},it={class:"space-y-6"},rt={class:"text-2xl leading-8 font-bold tracking-tight"},dt=["href"],lt=["innerHTML"],ct={class:"text-base leading-6 font-medium"},ut=["href"],pt=p({__name:"Home",setup(s){const{frontmatter:e}=g();return(n,r)=>(o(),i("div",Y,[t("div",Z,[t("h1",tt,d(a(e).title),1),t("p",et,d(a(e).subtext),1)]),t("ul",st,[(o(!0),i(A,null,P(a(m),({title:_,url:c,date:h,excerpt:x})=>(o(),i("li",at,[t("article",nt,[f(k,{date:h},null,8,["date"]),t("div",ot,[t("div",it,[t("h2",rt,[t("a",{class:"text-gray-900 dark:text-white",href:c},d(_),9,dt)]),x?(o(),i("div",{key:0,class:"prose dark:prose-invert max-w-none text-gray-500 dark:text-gray-300",innerHTML:x},null,8,lt)):l("",!0)]),t("div",ct,[t("a",{class:"link","aria-label":"read more",href:c},"Read more →",8,ut)])])])]))),256))])]))}}),_t={class:"pt-6 pb-10 xl:pt-11 xl:border-b xl:border-gray-200 dark:xl:border-slate-200/5"},ht=t("dt",{class:"sr-only"},"Authors",-1),xt={class:"flex justify-center xl:block space-x-8 sm:space-x-12 xl:space-x-0 xl:space-y-8"},mt={class:"flex items-center space-x-2"},gt=["src"],ft=["src"],yt={class:"text-sm font-medium leading-5 whitespace-nowrap"},vt=t("dt",{class:"sr-only"},"Name",-1),bt={class:"text-gray-900 dark:text-white"},kt={key:0,class:"sr-only"},wt={key:1},$t=["href"],At=p({__name:"Author",setup(s){const{frontmatter:e}=g();return(n,r)=>(o(),i("dl",_t,[ht,t("dd",null,[t("ul",xt,[t("li",mt,[a(e).gravatar?(o(),i("img",{key:0,src:"https://gravatar.com/avatar/"+a(e).gravatar,alt:"author image",class:"w-10 h-10 rounded-full"},null,8,gt)):a(e).avatar?(o(),i("img",{key:1,src:a(e).avatar,alt:"author image",class:"w-10 h-10 rounded-full"},null,8,ft)):l("",!0),t("dl",yt,[vt,t("dd",bt,d(a(e).author),1),a(e).twitter?(o(),i("dt",kt,"Twitter")):l("",!0),a(e).twitter?(o(),i("dd",wt,[t("a",{href:"https://twitter.com/"+a(e).twitter,target:"_blank",rel:"noopnener noreferrer",class:"link"},d(a(e).twitter),9,$t)])):l("",!0)])])])])]))}}),Pt={class:"xl:divide-y xl:divide-gray-200 dark:xl:divide-slate-200/5"},Dt={class:"pt-6 xl:pb-10 space-y-1 text-center"},Ct={class:"text-3xl leading-9 font-extrabold text-gray-900 dark:text-white tracking-tight sm:text-4xl sm:leading-10 md:text-5xl md:leading-14"},St={class:"divide-y xl:divide-y-0 divide-gray-200 dark:divide-slate-200/5 xl:grid xl:grid-cols-4 xl:gap-x-10 pb-16 xl:pb-20",style:{"grid-template-rows":"auto 1fr"}},Tt={class:"divide-y divide-gray-200 dark:divide-slate-200/5 xl:pb-0 xl:col-span-3 xl:row-span-2"},It={class:"text-sm font-medium leading-5 divide-y divide-gray-200 dark:divide-slate-200/5 xl:col-start-1 xl:row-start-2"},Nt={key:0,class:"py-8"},Rt=t("h2",{class:"text-xs tracking-wide uppercase text-gray-500 dark:text-white"}," Next Article ",-1),Lt={class:"link"},Vt=["href"],Ft={key:1,class:"py-8"},jt=t("h2",{class:"text-xs tracking-wide uppercase text-gray-500 dark:text-white"}," Previous Article ",-1),Et={class:"link"},Ht=["href"],Ot=t("div",{class:"pt-8"},[t("a",{class:"link",href:"/"},"← Back to the blog")],-1),Bt=p({__name:"Article",setup(s){const{frontmatter:e}=g(),n=D();function r(){return m.findIndex(x=>x.url===n.path)}const _=y(()=>m[r()].date),c=y(()=>m[r()-1]),h=y(()=>m[r()+1]);return(x,ie)=>{const $=C("Content");return o(),i("article",Pt,[t("header",Dt,[f(k,{date:a(_)},null,8,["date"]),t("h1",Ct,d(a(e).title),1)]),t("div",St,[f(At),t("div",Tt,[f($,{class:"prose dark:prose-invert max-w-none pt-10 pb-8"})]),t("footer",It,[a(c)?(o(),i("div",Nt,[Rt,t("div",Lt,[t("a",{href:a(c).url},d(a(c).title),9,Vt)])])):l("",!0),a(h)?(o(),i("div",Ft,[jt,t("div",Et,[t("a",{href:a(h).url},d(a(h).title),9,Ht)])])):l("",!0),Ot])])])}}}),Mt={},Gt={class:"text-3xl font-bold"};function Kt(s,e){return o(),i("h1",Gt,"404 Page Not Found")}const Ut=S(Mt,[["render",Kt]]),Wt={class:"antialiased dark:bg-slate-900"},qt={class:"max-w-3xl mx-auto px-4 sm:px-6 xl:max-w-5xl xl:px-0"},zt={class:"flex justify-between items-center py-10 font-bold"},Jt={class:"text-xl",href:"/","aria-label":"steve's blog"},Qt=t("img",{class:"inline-block mr-2",style:{width:"100px",height:"100px"},alt:"logo",src:z},null,-1),Xt={key:0,class:"hidden md:inline dark:text-white"},Yt=T('
Source·RSS
',1),Zt={class:"max-w-3xl mx-auto px-4 sm:px-6 xl:max-w-5xl xl:px-0"},te=p({__name:"Layout",setup(s){const{page:e,frontmatter:n}=g();return(r,_)=>(o(),i("div",Wt,[t("div",qt,[t("nav",zt,[t("a",Jt,[Qt,a(n).index?l("",!0):(o(),i("span",Xt,"steve's blog"))]),Yt])]),t("main",Zt,[a(n).index?(o(),v(pt,{key:0})):a(e).isNotFound?(o(),v(Ut,{key:1})):(o(),v(Bt,{key:2}))])]))}}),ee={Layout:te};function w(s){if(s.extends){const e=w(s.extends);return{...e,...s,async enhanceApp(n){e.enhanceApp&&await e.enhanceApp(n),s.enhanceApp&&await s.enhanceApp(n)}}}return s}const u=w(ee),se=p({name:"VitePressApp",setup(){const{site:s}=g();return N(()=>{R(()=>{document.documentElement.lang=s.value.lang,document.documentElement.dir=s.value.dir})}),L(),V(),F(),u.setup&&u.setup(),()=>j(u.Layout)}});async function ae(){const s=oe(),e=ne();e.provide(E,s);const n=H(s.route);return e.provide(O,n),e.component("Content",B),e.component("ClientOnly",M),Object.defineProperties(e.config.globalProperties,{$frontmatter:{get(){return n.frontmatter.value}},$params:{get(){return n.page.value.params}}}),u.enhanceApp&&await u.enhanceApp({app:e,router:s,siteData:G}),{app:e,router:s,data:n}}function ne(){return K(se)}function oe(){let s=b,e;return U(n=>{let r=W(n);return s&&(e=r),(s||e===r)&&(r=r.replace(/\.js$/,".lean.js")),b&&(s=!1),q(()=>import(r),[])},u.NotFound)}b&&ae().then(({app:s,router:e,data:n})=>{e.go().then(()=>{I(e.route,n.site),s.mount("#app")})});export{ae as createApp}; diff --git a/assets/chunks/framework.f55bdc3d.js b/assets/chunks/framework.f55bdc3d.js new file mode 100644 index 0000000..ce0515b --- /dev/null +++ b/assets/chunks/framework.f55bdc3d.js @@ -0,0 +1,2 @@ +function Rn(e,t){const n=Object.create(null),s=e.split(",");for(let r=0;r!!n[r.toLowerCase()]:r=>!!n[r]}function vn(e){if(S(e)){const t={};for(let n=0;n{if(n){const s=n.split(Dr);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function An(e){let t="";if(se(e))t=e;else if(S(e))for(let n=0;nse(e)?e:e==null?"":S(e)||J(e)&&(e.toString===Us||!H(e.toString))?JSON.stringify(e,Ns,2):String(e),Ns=(e,t)=>t&&t.__v_isRef?Ns(e,t.value):nt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,r])=>(n[`${s} =>`]=r,n),{})}:$s(t)?{[`Set(${t.size})`]:[...t.values()]}:J(t)&&!S(t)&&!Bs(t)?String(t):t,Y={},tt=[],Ce=()=>{},Vr=()=>!1,zr=/^on[^a-z]/,Ot=e=>zr.test(e),On=e=>e.startsWith("onUpdate:"),fe=Object.assign,Pn=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},Yr=Object.prototype.hasOwnProperty,U=(e,t)=>Yr.call(e,t),S=Array.isArray,nt=e=>Yt(e)==="[object Map]",$s=e=>Yt(e)==="[object Set]",H=e=>typeof e=="function",se=e=>typeof e=="string",Mn=e=>typeof e=="symbol",J=e=>e!==null&&typeof e=="object",js=e=>J(e)&&H(e.then)&&H(e.catch),Us=Object.prototype.toString,Yt=e=>Us.call(e),Jr=e=>Yt(e).slice(8,-1),Bs=e=>Yt(e)==="[object Object]",In=e=>se(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,mt=Rn(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Jt=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Xr=/-(\w)/g,Ie=Jt(e=>e.replace(Xr,(t,n)=>n?n.toUpperCase():"")),Zr=/\B([A-Z])/g,ut=Jt(e=>e.replace(Zr,"-$1").toLowerCase()),Xt=Jt(e=>e.charAt(0).toUpperCase()+e.slice(1)),ln=Jt(e=>e?`on${Xt(e)}`:""),Et=(e,t)=>!Object.is(e,t),on=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},Qr=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let Gn;const Gr=()=>Gn||(Gn=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});let _e;class ei{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=_e,!t&&_e&&(this.index=(_e.scopes||(_e.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=_e;try{return _e=this,t()}finally{_e=n}}}on(){_e=this}off(){_e=this.parent}stop(t){if(this._active){let n,s;for(n=0,s=this.effects.length;n{const t=new Set(e);return t.w=0,t.n=0,t},Ds=e=>(e.w&De)>0,Ks=e=>(e.n&De)>0,si=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let s=0;s{(h==="length"||h>=f)&&o.push(a)})}else switch(n!==void 0&&o.push(l.get(n)),t){case"add":S(e)?In(n)&&o.push(l.get("length")):(o.push(l.get(Xe)),nt(e)&&o.push(l.get(mn)));break;case"delete":S(e)||(o.push(l.get(Xe)),nt(e)&&o.push(l.get(mn)));break;case"set":nt(e)&&o.push(l.get(Xe));break}if(o.length===1)o[0]&&_n(o[0]);else{const f=[];for(const a of o)a&&f.push(...a);_n(Fn(f))}}function _n(e,t){const n=S(e)?e:[...e];for(const s of n)s.computed&&ts(s);for(const s of n)s.computed||ts(s)}function ts(e,t){(e!==xe||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}const ii=Rn("__proto__,__v_isRef,__isVue"),ks=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Mn)),li=Hn(),oi=Hn(!1,!0),ci=Hn(!0),ns=fi();function fi(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const s=B(this);for(let i=0,l=this.length;i{e[t]=function(...n){at();const s=B(this)[t].apply(this,n);return dt(),s}}),e}function ui(e){const t=B(this);return ae(t,"has",e),t.hasOwnProperty(e)}function Hn(e=!1,t=!1){return function(s,r,i){if(r==="__v_isReactive")return!e;if(r==="__v_isReadonly")return e;if(r==="__v_isShallow")return t;if(r==="__v_raw"&&i===(e?t?vi:Xs:t?Js:Ys).get(s))return s;const l=S(s);if(!e){if(l&&U(ns,r))return Reflect.get(ns,r,i);if(r==="hasOwnProperty")return ui}const o=Reflect.get(s,r,i);return(Mn(r)?ks.has(r):ii(r))||(e||ae(s,"get",r),t)?o:le(o)?l&&In(r)?o:o.value:J(o)?e?Zs(o):Qt(o):o}}const ai=Vs(),di=Vs(!0);function Vs(e=!1){return function(n,s,r,i){let l=n[s];if(lt(l)&&le(l)&&!le(r))return!1;if(!e&&(!Wt(r)&&!lt(r)&&(l=B(l),r=B(r)),!S(n)&&le(l)&&!le(r)))return l.value=r,!0;const o=S(n)&&In(s)?Number(s)e,Zt=e=>Reflect.getPrototypeOf(e);function It(e,t,n=!1,s=!1){e=e.__v_raw;const r=B(e),i=B(t);n||(t!==i&&ae(r,"get",t),ae(r,"get",i));const{has:l}=Zt(r),o=s?Ln:n?jn:Ct;if(l.call(r,t))return o(e.get(t));if(l.call(r,i))return o(e.get(i));e!==r&&e.get(t)}function Ft(e,t=!1){const n=this.__v_raw,s=B(n),r=B(e);return t||(e!==r&&ae(s,"has",e),ae(s,"has",r)),e===r?n.has(e):n.has(e)||n.has(r)}function St(e,t=!1){return e=e.__v_raw,!t&&ae(B(e),"iterate",Xe),Reflect.get(e,"size",e)}function ss(e){e=B(e);const t=B(this);return Zt(t).has.call(t,e)||(t.add(e),He(t,"add",e,e)),this}function rs(e,t){t=B(t);const n=B(this),{has:s,get:r}=Zt(n);let i=s.call(n,e);i||(e=B(e),i=s.call(n,e));const l=r.call(n,e);return n.set(e,t),i?Et(t,l)&&He(n,"set",e,t):He(n,"add",e,t),this}function is(e){const t=B(this),{has:n,get:s}=Zt(t);let r=n.call(t,e);r||(e=B(e),r=n.call(t,e)),s&&s.call(t,e);const i=t.delete(e);return r&&He(t,"delete",e,void 0),i}function ls(){const e=B(this),t=e.size!==0,n=e.clear();return t&&He(e,"clear",void 0,void 0),n}function Ht(e,t){return function(s,r){const i=this,l=i.__v_raw,o=B(l),f=t?Ln:e?jn:Ct;return!e&&ae(o,"iterate",Xe),l.forEach((a,h)=>s.call(r,f(a),f(h),i))}}function Lt(e,t,n){return function(...s){const r=this.__v_raw,i=B(r),l=nt(i),o=e==="entries"||e===Symbol.iterator&&l,f=e==="keys"&&l,a=r[e](...s),h=n?Ln:t?jn:Ct;return!t&&ae(i,"iterate",f?mn:Xe),{next(){const{value:b,done:T}=a.next();return T?{value:b,done:T}:{value:o?[h(b[0]),h(b[1])]:h(b),done:T}},[Symbol.iterator](){return this}}}}function Ne(e){return function(...t){return e==="delete"?!1:this}}function bi(){const e={get(i){return It(this,i)},get size(){return St(this)},has:Ft,add:ss,set:rs,delete:is,clear:ls,forEach:Ht(!1,!1)},t={get(i){return It(this,i,!1,!0)},get size(){return St(this)},has:Ft,add:ss,set:rs,delete:is,clear:ls,forEach:Ht(!1,!0)},n={get(i){return It(this,i,!0)},get size(){return St(this,!0)},has(i){return Ft.call(this,i,!0)},add:Ne("add"),set:Ne("set"),delete:Ne("delete"),clear:Ne("clear"),forEach:Ht(!0,!1)},s={get(i){return It(this,i,!0,!0)},get size(){return St(this,!0)},has(i){return Ft.call(this,i,!0)},add:Ne("add"),set:Ne("set"),delete:Ne("delete"),clear:Ne("clear"),forEach:Ht(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(i=>{e[i]=Lt(i,!1,!1),n[i]=Lt(i,!0,!1),t[i]=Lt(i,!1,!0),s[i]=Lt(i,!0,!0)}),[e,n,t,s]}const[yi,xi,wi,Ei]=bi();function Nn(e,t){const n=t?e?Ei:wi:e?xi:yi;return(s,r,i)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?s:Reflect.get(U(n,r)&&r in s?n:s,r,i)}const Ci={get:Nn(!1,!1)},Ti={get:Nn(!1,!0)},Ri={get:Nn(!0,!1)},Ys=new WeakMap,Js=new WeakMap,Xs=new WeakMap,vi=new WeakMap;function Ai(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Oi(e){return e.__v_skip||!Object.isExtensible(e)?0:Ai(Jr(e))}function Qt(e){return lt(e)?e:$n(e,!1,zs,Ci,Ys)}function Pi(e){return $n(e,!1,_i,Ti,Js)}function Zs(e){return $n(e,!0,mi,Ri,Xs)}function $n(e,t,n,s,r){if(!J(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=r.get(e);if(i)return i;const l=Oi(e);if(l===0)return e;const o=new Proxy(e,l===2?s:n);return r.set(e,o),o}function st(e){return lt(e)?st(e.__v_raw):!!(e&&e.__v_isReactive)}function lt(e){return!!(e&&e.__v_isReadonly)}function Wt(e){return!!(e&&e.__v_isShallow)}function Qs(e){return st(e)||lt(e)}function B(e){const t=e&&e.__v_raw;return t?B(t):e}function _t(e){return Kt(e,"__v_skip",!0),e}const Ct=e=>J(e)?Qt(e):e,jn=e=>J(e)?Zs(e):e;function Gs(e){Ue&&xe&&(e=B(e),qs(e.dep||(e.dep=Fn())))}function er(e,t){e=B(e);const n=e.dep;n&&_n(n)}function le(e){return!!(e&&e.__v_isRef===!0)}function tr(e){return nr(e,!1)}function Mi(e){return nr(e,!0)}function nr(e,t){return le(e)?e:new Ii(e,t)}class Ii{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:B(t),this._value=n?t:Ct(t)}get value(){return Gs(this),this._value}set value(t){const n=this.__v_isShallow||Wt(t)||lt(t);t=n?t:B(t),Et(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:Ct(t),er(this))}}function Fi(e){return le(e)?e.value:e}const Si={get:(e,t,n)=>Fi(Reflect.get(e,t,n)),set:(e,t,n,s)=>{const r=e[t];return le(r)&&!le(n)?(r.value=n,!0):Reflect.set(e,t,n,s)}};function sr(e){return st(e)?e:new Proxy(e,Si)}var rr;class Hi{constructor(t,n,s,r){this._setter=n,this.dep=void 0,this.__v_isRef=!0,this[rr]=!1,this._dirty=!0,this.effect=new Sn(t,()=>{this._dirty||(this._dirty=!0,er(this))}),this.effect.computed=this,this.effect.active=this._cacheable=!r,this.__v_isReadonly=s}get value(){const t=B(this);return Gs(t),(t._dirty||!t._cacheable)&&(t._dirty=!1,t._value=t.effect.run()),t._value}set value(t){this._setter(t)}}rr="__v_isReadonly";function Li(e,t,n=!1){let s,r;const i=H(e);return i?(s=e,r=Ce):(s=e.get,r=e.set),new Hi(s,r,i||!r,n)}function Be(e,t,n,s){let r;try{r=s?e(...s):e()}catch(i){Gt(i,t,n)}return r}function Te(e,t,n,s){if(H(e)){const i=Be(e,t,n,s);return i&&js(i)&&i.catch(l=>{Gt(l,t,n)}),i}const r=[];for(let i=0;i>>1;Rt(ie[s])Me&&ie.splice(t,1)}function Ui(e){S(e)?rt.push(...e):(!Se||!Se.includes(e,e.allowRecurse?ze+1:ze))&&rt.push(e),or()}function os(e,t=Tt?Me+1:0){for(;tRt(n)-Rt(s)),ze=0;zee.id==null?1/0:e.id,Bi=(e,t)=>{const n=Rt(e)-Rt(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function cr(e){bn=!1,Tt=!0,ie.sort(Bi);const t=Ce;try{for(Me=0;Mese(P)?P.trim():P)),b&&(r=n.map(Qr))}let o,f=s[o=ln(t)]||s[o=ln(Ie(t))];!f&&i&&(f=s[o=ln(ut(t))]),f&&Te(f,e,6,r);const a=s[o+"Once"];if(a){if(!e.emitted)e.emitted={};else if(e.emitted[o])return;e.emitted[o]=!0,Te(a,e,6,r)}}function fr(e,t,n=!1){const s=t.emitsCache,r=s.get(e);if(r!==void 0)return r;const i=e.emits;let l={},o=!1;if(!H(e)){const f=a=>{const h=fr(a,t,!0);h&&(o=!0,fe(l,h))};!n&&t.mixins.length&&t.mixins.forEach(f),e.extends&&f(e.extends),e.mixins&&e.mixins.forEach(f)}return!i&&!o?(J(e)&&s.set(e,null),null):(S(i)?i.forEach(f=>l[f]=null):fe(l,i),J(e)&&s.set(e,l),l)}function en(e,t){return!e||!Ot(t)?!1:(t=t.slice(2).replace(/Once$/,""),U(e,t[0].toLowerCase()+t.slice(1))||U(e,ut(t))||U(e,t))}let we=null,ur=null;function kt(e){const t=we;return we=e,ur=e&&e.type.__scopeId||null,t}function Ki(e,t=we,n){if(!t||e._n)return e;const s=(...r)=>{s._d&&ms(-1);const i=kt(t);let l;try{l=e(...r)}finally{kt(i),s._d&&ms(1)}return l};return s._n=!0,s._c=!0,s._d=!0,s}function cn(e){const{type:t,vnode:n,proxy:s,withProxy:r,props:i,propsOptions:[l],slots:o,attrs:f,emit:a,render:h,renderCache:b,data:T,setupState:P,ctx:j,inheritAttrs:F}=e;let K,_;const C=kt(e);try{if(n.shapeFlag&4){const $=r||s;K=ye(h.call($,$,b,i,P,T,j)),_=f}else{const $=t;K=ye($.length>1?$(i,{attrs:f,slots:o,emit:a}):$(i,null)),_=t.props?f:Wi(f)}}catch($){wt.length=0,Gt($,e,1),K=oe(Ke)}let v=K;if(_&&F!==!1){const $=Object.keys(_),{shapeFlag:W}=v;$.length&&W&7&&(l&&$.some(On)&&(_=qi(_,l)),v=ct(v,_))}return n.dirs&&(v=ct(v),v.dirs=v.dirs?v.dirs.concat(n.dirs):n.dirs),n.transition&&(v.transition=n.transition),K=v,kt(C),K}const Wi=e=>{let t;for(const n in e)(n==="class"||n==="style"||Ot(n))&&((t||(t={}))[n]=e[n]);return t},qi=(e,t)=>{const n={};for(const s in e)(!On(s)||!(s.slice(9)in t))&&(n[s]=e[s]);return n};function ki(e,t,n){const{props:s,children:r,component:i}=e,{props:l,children:o,patchFlag:f}=t,a=i.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&f>=0){if(f&1024)return!0;if(f&16)return s?cs(s,l,a):!!l;if(f&8){const h=t.dynamicProps;for(let b=0;be.__isSuspense;function ar(e,t){t&&t.pendingBranch?S(e)?t.effects.push(...e):t.effects.push(e):Ui(e)}function Yi(e,t){if(te){let n=te.provides;const s=te.parent&&te.parent.provides;s===n&&(n=te.provides=Object.create(s)),n[e]=t}}function it(e,t,n=!1){const s=te||we;if(s){const r=s.parent==null?s.vnode.appContext&&s.vnode.appContext.provides:s.parent.provides;if(r&&e in r)return r[e];if(arguments.length>1)return n&&H(t)?t.call(s.proxy):t}}function Ji(e,t){return Dn(e,null,t)}const Nt={};function Bt(e,t,n){return Dn(e,t,n)}function Dn(e,t,{immediate:n,deep:s,flush:r,onTrack:i,onTrigger:l}=Y){const o=ni()===(te==null?void 0:te.scope)?te:null;let f,a=!1,h=!1;if(le(e)?(f=()=>e.value,a=Wt(e)):st(e)?(f=()=>e,s=!0):S(e)?(h=!0,a=e.some(v=>st(v)||Wt(v)),f=()=>e.map(v=>{if(le(v))return v.value;if(st(v))return et(v);if(H(v))return Be(v,o,2)})):H(e)?t?f=()=>Be(e,o,2):f=()=>{if(!(o&&o.isUnmounted))return b&&b(),Te(e,o,3,[T])}:f=Ce,t&&s){const v=f;f=()=>et(v())}let b,T=v=>{b=_.onStop=()=>{Be(v,o,4)}},P;if(At)if(T=Ce,t?n&&Te(t,o,3,[f(),h?[]:void 0,T]):f(),r==="sync"){const v=ql();P=v.__watcherHandles||(v.__watcherHandles=[])}else return Ce;let j=h?new Array(e.length).fill(Nt):Nt;const F=()=>{if(_.active)if(t){const v=_.run();(s||a||(h?v.some(($,W)=>Et($,j[W])):Et(v,j)))&&(b&&b(),Te(t,o,3,[v,j===Nt?void 0:h&&j[0]===Nt?[]:j,T]),j=v)}else _.run()};F.allowRecurse=!!t;let K;r==="sync"?K=F:r==="post"?K=()=>ue(F,o&&o.suspense):(F.pre=!0,o&&(F.id=o.uid),K=()=>Bn(F));const _=new Sn(f,K);t?n?F():j=_.run():r==="post"?ue(_.run.bind(_),o&&o.suspense):_.run();const C=()=>{_.stop(),o&&o.scope&&Pn(o.scope.effects,_)};return P&&P.push(C),C}function Xi(e,t,n){const s=this.proxy,r=se(e)?e.includes(".")?dr(s,e):()=>s[e]:e.bind(s,s);let i;H(t)?i=t:(i=t.handler,n=t);const l=te;ft(this);const o=Dn(r,i.bind(s),n);return l?ft(l):Ze(),o}function dr(e,t){const n=t.split(".");return()=>{let s=e;for(let r=0;r{et(n,t)});else if(Bs(e))for(const n in e)et(e[n],t);return e}function hr(e){return H(e)?{setup:e,name:e.name}:e}const bt=e=>!!e.type.__asyncLoader,pr=e=>e.type.__isKeepAlive;function Zi(e,t){gr(e,"a",t)}function Qi(e,t){gr(e,"da",t)}function gr(e,t,n=te){const s=e.__wdc||(e.__wdc=()=>{let r=n;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(tn(t,s,n),n){let r=n.parent;for(;r&&r.parent;)pr(r.parent.vnode)&&Gi(s,t,n,r),r=r.parent}}function Gi(e,t,n,s){const r=tn(t,e,s,!0);Wn(()=>{Pn(s[t],r)},n)}function tn(e,t,n=te,s=!1){if(n){const r=n[e]||(n[e]=[]),i=t.__weh||(t.__weh=(...l)=>{if(n.isUnmounted)return;at(),ft(n);const o=Te(t,n,e,l);return Ze(),dt(),o});return s?r.unshift(i):r.push(i),i}}const Le=e=>(t,n=te)=>(!At||e==="sp")&&tn(e,(...s)=>t(...s),n),el=Le("bm"),Kn=Le("m"),tl=Le("bu"),nl=Le("u"),sl=Le("bum"),Wn=Le("um"),rl=Le("sp"),il=Le("rtg"),ll=Le("rtc");function ol(e,t=te){tn("ec",e,t)}function Pe(e,t,n,s){const r=e.dirs,i=t&&t.dirs;for(let l=0;lt(l,o,void 0,i&&i[o]));else{const l=Object.keys(e);r=new Array(l.length);for(let o=0,f=l.length;oe?Pr(e)?zn(e)||e.proxy:yn(e.parent):null,yt=fe(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>yn(e.parent),$root:e=>yn(e.root),$emit:e=>e.emit,$options:e=>qn(e),$forceUpdate:e=>e.f||(e.f=()=>Bn(e.update)),$nextTick:e=>e.n||(e.n=lr.bind(e.proxy)),$watch:e=>Xi.bind(e)}),fn=(e,t)=>e!==Y&&!e.__isScriptSetup&&U(e,t),ul={get({_:e},t){const{ctx:n,setupState:s,data:r,props:i,accessCache:l,type:o,appContext:f}=e;let a;if(t[0]!=="$"){const P=l[t];if(P!==void 0)switch(P){case 1:return s[t];case 2:return r[t];case 4:return n[t];case 3:return i[t]}else{if(fn(s,t))return l[t]=1,s[t];if(r!==Y&&U(r,t))return l[t]=2,r[t];if((a=e.propsOptions[0])&&U(a,t))return l[t]=3,i[t];if(n!==Y&&U(n,t))return l[t]=4,n[t];xn&&(l[t]=0)}}const h=yt[t];let b,T;if(h)return t==="$attrs"&&ae(e,"get",t),h(e);if((b=o.__cssModules)&&(b=b[t]))return b;if(n!==Y&&U(n,t))return l[t]=4,n[t];if(T=f.config.globalProperties,U(T,t))return T[t]},set({_:e},t,n){const{data:s,setupState:r,ctx:i}=e;return fn(r,t)?(r[t]=n,!0):s!==Y&&U(s,t)?(s[t]=n,!0):U(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:s,appContext:r,propsOptions:i}},l){let o;return!!n[l]||e!==Y&&U(e,l)||fn(t,l)||(o=i[0])&&U(o,l)||U(s,l)||U(yt,l)||U(r.config.globalProperties,l)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:U(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};let xn=!0;function al(e){const t=qn(e),n=e.proxy,s=e.ctx;xn=!1,t.beforeCreate&&us(t.beforeCreate,e,"bc");const{data:r,computed:i,methods:l,watch:o,provide:f,inject:a,created:h,beforeMount:b,mounted:T,beforeUpdate:P,updated:j,activated:F,deactivated:K,beforeDestroy:_,beforeUnmount:C,destroyed:v,unmounted:$,render:W,renderTracked:Z,renderTriggered:q,errorCaptured:L,serverPrefetch:ne,expose:X,inheritAttrs:G,components:Re,directives:re,filters:M}=t;if(a&&dl(a,s,null,e.appContext.config.unwrapInjectedRef),l)for(const Q in l){const k=l[Q];H(k)&&(s[Q]=k.bind(n))}if(r){const Q=r.call(n,n);J(Q)&&(e.data=Qt(Q))}if(xn=!0,i)for(const Q in i){const k=i[Q],We=H(k)?k.bind(n,n):H(k.get)?k.get.bind(n,n):Ce,Pt=!H(k)&&H(k.set)?k.set.bind(n):Ce,qe=me({get:We,set:Pt});Object.defineProperty(s,Q,{enumerable:!0,configurable:!0,get:()=>qe.value,set:Ae=>qe.value=Ae})}if(o)for(const Q in o)_r(o[Q],s,n,Q);if(f){const Q=H(f)?f.call(n):f;Reflect.ownKeys(Q).forEach(k=>{Yi(k,Q[k])})}h&&us(h,e,"c");function z(Q,k){S(k)?k.forEach(We=>Q(We.bind(n))):k&&Q(k.bind(n))}if(z(el,b),z(Kn,T),z(tl,P),z(nl,j),z(Zi,F),z(Qi,K),z(ol,L),z(ll,Z),z(il,q),z(sl,C),z(Wn,$),z(rl,ne),S(X))if(X.length){const Q=e.exposed||(e.exposed={});X.forEach(k=>{Object.defineProperty(Q,k,{get:()=>n[k],set:We=>n[k]=We})})}else e.exposed||(e.exposed={});W&&e.render===Ce&&(e.render=W),G!=null&&(e.inheritAttrs=G),Re&&(e.components=Re),re&&(e.directives=re)}function dl(e,t,n=Ce,s=!1){S(e)&&(e=wn(e));for(const r in e){const i=e[r];let l;J(i)?"default"in i?l=it(i.from||r,i.default,!0):l=it(i.from||r):l=it(i),le(l)&&s?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>l.value,set:o=>l.value=o}):t[r]=l}}function us(e,t,n){Te(S(e)?e.map(s=>s.bind(t.proxy)):e.bind(t.proxy),t,n)}function _r(e,t,n,s){const r=s.includes(".")?dr(n,s):()=>n[s];if(se(e)){const i=t[e];H(i)&&Bt(r,i)}else if(H(e))Bt(r,e.bind(n));else if(J(e))if(S(e))e.forEach(i=>_r(i,t,n,s));else{const i=H(e.handler)?e.handler.bind(n):t[e.handler];H(i)&&Bt(r,i,e)}}function qn(e){const t=e.type,{mixins:n,extends:s}=t,{mixins:r,optionsCache:i,config:{optionMergeStrategies:l}}=e.appContext,o=i.get(t);let f;return o?f=o:!r.length&&!n&&!s?f=t:(f={},r.length&&r.forEach(a=>Vt(f,a,l,!0)),Vt(f,t,l)),J(t)&&i.set(t,f),f}function Vt(e,t,n,s=!1){const{mixins:r,extends:i}=t;i&&Vt(e,i,n,!0),r&&r.forEach(l=>Vt(e,l,n,!0));for(const l in t)if(!(s&&l==="expose")){const o=hl[l]||n&&n[l];e[l]=o?o(e[l],t[l]):t[l]}return e}const hl={data:as,props:Ve,emits:Ve,methods:Ve,computed:Ve,beforeCreate:ce,created:ce,beforeMount:ce,mounted:ce,beforeUpdate:ce,updated:ce,beforeDestroy:ce,beforeUnmount:ce,destroyed:ce,unmounted:ce,activated:ce,deactivated:ce,errorCaptured:ce,serverPrefetch:ce,components:Ve,directives:Ve,watch:gl,provide:as,inject:pl};function as(e,t){return t?e?function(){return fe(H(e)?e.call(this,this):e,H(t)?t.call(this,this):t)}:t:e}function pl(e,t){return Ve(wn(e),wn(t))}function wn(e){if(S(e)){const t={};for(let n=0;n0)&&!(l&16)){if(l&8){const h=e.vnode.dynamicProps;for(let b=0;b{f=!0;const[T,P]=yr(b,t,!0);fe(l,T),P&&o.push(...P)};!n&&t.mixins.length&&t.mixins.forEach(h),e.extends&&h(e.extends),e.mixins&&e.mixins.forEach(h)}if(!i&&!f)return J(e)&&s.set(e,tt),tt;if(S(i))for(let h=0;h-1,P[1]=F<0||j-1||U(P,"default"))&&o.push(b)}}}const a=[l,o];return J(e)&&s.set(e,a),a}function ds(e){return e[0]!=="$"}function hs(e){const t=e&&e.toString().match(/^\s*(function|class) (\w+)/);return t?t[2]:e===null?"null":""}function ps(e,t){return hs(e)===hs(t)}function gs(e,t){return S(t)?t.findIndex(n=>ps(n,e)):H(t)&&ps(t,e)?0:-1}const xr=e=>e[0]==="_"||e==="$stable",kn=e=>S(e)?e.map(ye):[ye(e)],bl=(e,t,n)=>{if(t._n)return t;const s=Ki((...r)=>kn(t(...r)),n);return s._c=!1,s},wr=(e,t,n)=>{const s=e._ctx;for(const r in e){if(xr(r))continue;const i=e[r];if(H(i))t[r]=bl(r,i,s);else if(i!=null){const l=kn(i);t[r]=()=>l}}},Er=(e,t)=>{const n=kn(t);e.slots.default=()=>n},yl=(e,t)=>{if(e.vnode.shapeFlag&32){const n=t._;n?(e.slots=B(t),Kt(t,"_",n)):wr(t,e.slots={})}else e.slots={},t&&Er(e,t);Kt(e.slots,nn,1)},xl=(e,t,n)=>{const{vnode:s,slots:r}=e;let i=!0,l=Y;if(s.shapeFlag&32){const o=t._;o?n&&o===1?i=!1:(fe(r,t),!n&&o===1&&delete r._):(i=!t.$stable,wr(t,r)),l=t}else t&&(Er(e,t),l={default:1});if(i)for(const o in r)!xr(o)&&!(o in l)&&delete r[o]};function Cr(){return{app:null,config:{isNativeTag:Vr,performance:!1,globalProperties:{},optionMergeStrategies:{},errorHandler:void 0,warnHandler:void 0,compilerOptions:{}},mixins:[],components:{},directives:{},provides:Object.create(null),optionsCache:new WeakMap,propsCache:new WeakMap,emitsCache:new WeakMap}}let wl=0;function El(e,t){return function(s,r=null){H(s)||(s=Object.assign({},s)),r!=null&&!J(r)&&(r=null);const i=Cr(),l=new Set;let o=!1;const f=i.app={_uid:wl++,_component:s,_props:r,_container:null,_context:i,_instance:null,version:kl,get config(){return i.config},set config(a){},use(a,...h){return l.has(a)||(a&&H(a.install)?(l.add(a),a.install(f,...h)):H(a)&&(l.add(a),a(f,...h))),f},mixin(a){return i.mixins.includes(a)||i.mixins.push(a),f},component(a,h){return h?(i.components[a]=h,f):i.components[a]},directive(a,h){return h?(i.directives[a]=h,f):i.directives[a]},mount(a,h,b){if(!o){const T=oe(s,r);return T.appContext=i,h&&t?t(T,a):e(T,a,b),o=!0,f._container=a,a.__vue_app__=f,zn(T.component)||T.component.proxy}},unmount(){o&&(e(null,f._container),delete f._container.__vue_app__)},provide(a,h){return i.provides[a]=h,f}};return f}}function zt(e,t,n,s,r=!1){if(S(e)){e.forEach((T,P)=>zt(T,t&&(S(t)?t[P]:t),n,s,r));return}if(bt(s)&&!r)return;const i=s.shapeFlag&4?zn(s.component)||s.component.proxy:s.el,l=r?null:i,{i:o,r:f}=e,a=t&&t.r,h=o.refs===Y?o.refs={}:o.refs,b=o.setupState;if(a!=null&&a!==f&&(se(a)?(h[a]=null,U(b,a)&&(b[a]=null)):le(a)&&(a.value=null)),H(f))Be(f,o,12,[l,h]);else{const T=se(f),P=le(f);if(T||P){const j=()=>{if(e.f){const F=T?U(b,f)?b[f]:h[f]:f.value;r?S(F)&&Pn(F,i):S(F)?F.includes(i)||F.push(i):T?(h[f]=[i],U(b,f)&&(b[f]=h[f])):(f.value=[i],e.k&&(h[e.k]=f.value))}else T?(h[f]=l,U(b,f)&&(b[f]=l)):P&&(f.value=l,e.k&&(h[e.k]=l))};l?(j.id=-1,ue(j,n)):j()}}}let $e=!1;const $t=e=>/svg/.test(e.namespaceURI)&&e.tagName!=="foreignObject",jt=e=>e.nodeType===8;function Cl(e){const{mt:t,p:n,o:{patchProp:s,createText:r,nextSibling:i,parentNode:l,remove:o,insert:f,createComment:a}}=e,h=(_,C)=>{if(!C.hasChildNodes()){n(null,_,C),qt(),C._vnode=_;return}$e=!1,b(C.firstChild,_,null,null,null),qt(),C._vnode=_,$e&&console.error("Hydration completed but contains mismatches.")},b=(_,C,v,$,W,Z=!1)=>{const q=jt(_)&&_.data==="[",L=()=>F(_,C,v,$,W,q),{type:ne,ref:X,shapeFlag:G,patchFlag:Re}=C;let re=_.nodeType;C.el=_,Re===-2&&(Z=!1,C.dynamicChildren=null);let M=null;switch(ne){case ot:re!==3?C.children===""?(f(C.el=r(""),l(_),_),M=_):M=L():(_.data!==C.children&&($e=!0,_.data=C.children),M=i(_));break;case Ke:re!==8||q?M=L():M=i(_);break;case xt:if(q&&(_=i(_),re=_.nodeType),re===1||re===3){M=_;const ve=!C.children.length;for(let z=0;z{Z=Z||!!C.dynamicChildren;const{type:q,props:L,patchFlag:ne,shapeFlag:X,dirs:G}=C,Re=q==="input"&&G||q==="option";if(Re||ne!==-1){if(G&&Pe(C,null,v,"created"),L)if(Re||!Z||ne&48)for(const M in L)(Re&&M.endsWith("value")||Ot(M)&&!mt(M))&&s(_,M,null,L[M],!1,void 0,v);else L.onClick&&s(_,"onClick",null,L.onClick,!1,void 0,v);let re;if((re=L&&L.onVnodeBeforeMount)&&he(re,v,C),G&&Pe(C,null,v,"beforeMount"),((re=L&&L.onVnodeMounted)||G)&&ar(()=>{re&&he(re,v,C),G&&Pe(C,null,v,"mounted")},$),X&16&&!(L&&(L.innerHTML||L.textContent))){let M=P(_.firstChild,C,_,v,$,W,Z);for(;M;){$e=!0;const ve=M;M=M.nextSibling,o(ve)}}else X&8&&_.textContent!==C.children&&($e=!0,_.textContent=C.children)}return _.nextSibling},P=(_,C,v,$,W,Z,q)=>{q=q||!!C.dynamicChildren;const L=C.children,ne=L.length;for(let X=0;X{const{slotScopeIds:q}=C;q&&(W=W?W.concat(q):q);const L=l(_),ne=P(i(_),C,L,v,$,W,Z);return ne&&jt(ne)&&ne.data==="]"?i(C.anchor=ne):($e=!0,f(C.anchor=a("]"),L,ne),ne)},F=(_,C,v,$,W,Z)=>{if($e=!0,C.el=null,Z){const ne=K(_);for(;;){const X=i(_);if(X&&X!==ne)o(X);else break}}const q=i(_),L=l(_);return o(_),n(null,C,L,q,v,$,$t(L),W),q},K=_=>{let C=0;for(;_;)if(_=i(_),_&&jt(_)&&(_.data==="["&&C++,_.data==="]")){if(C===0)return i(_);C--}return _};return[h,b]}const ue=ar;function Tl(e){return Rl(e,Cl)}function Rl(e,t){const n=Gr();n.__VUE__=!0;const{insert:s,remove:r,patchProp:i,createElement:l,createText:o,createComment:f,setText:a,setElementText:h,parentNode:b,nextSibling:T,setScopeId:P=Ce,insertStaticContent:j}=e,F=(c,u,d,g=null,p=null,x=null,E=!1,y=null,w=!!u.dynamicChildren)=>{if(c===u)return;c&&!pt(c,u)&&(g=Mt(c),Ae(c,p,x,!0),c=null),u.patchFlag===-2&&(w=!1,u.dynamicChildren=null);const{type:m,ref:A,shapeFlag:R}=u;switch(m){case ot:K(c,u,d,g);break;case Ke:_(c,u,d,g);break;case xt:c==null&&C(u,d,g,E);break;case be:Re(c,u,d,g,p,x,E,y,w);break;default:R&1?W(c,u,d,g,p,x,E,y,w):R&6?re(c,u,d,g,p,x,E,y,w):(R&64||R&128)&&m.process(c,u,d,g,p,x,E,y,w,Qe)}A!=null&&p&&zt(A,c&&c.ref,x,u||c,!u)},K=(c,u,d,g)=>{if(c==null)s(u.el=o(u.children),d,g);else{const p=u.el=c.el;u.children!==c.children&&a(p,u.children)}},_=(c,u,d,g)=>{c==null?s(u.el=f(u.children||""),d,g):u.el=c.el},C=(c,u,d,g)=>{[c.el,c.anchor]=j(c.children,u,d,g,c.el,c.anchor)},v=({el:c,anchor:u},d,g)=>{let p;for(;c&&c!==u;)p=T(c),s(c,d,g),c=p;s(u,d,g)},$=({el:c,anchor:u})=>{let d;for(;c&&c!==u;)d=T(c),r(c),c=d;r(u)},W=(c,u,d,g,p,x,E,y,w)=>{E=E||u.type==="svg",c==null?Z(u,d,g,p,x,E,y,w):ne(c,u,p,x,E,y,w)},Z=(c,u,d,g,p,x,E,y)=>{let w,m;const{type:A,props:R,shapeFlag:O,transition:I,dirs:N}=c;if(w=c.el=l(c.type,x,R&&R.is,R),O&8?h(w,c.children):O&16&&L(c.children,w,null,g,p,x&&A!=="foreignObject",E,y),N&&Pe(c,null,g,"created"),q(w,c,c.scopeId,E,g),R){for(const D in R)D!=="value"&&!mt(D)&&i(w,D,null,R[D],x,c.children,g,p,Fe);"value"in R&&i(w,"value",null,R.value),(m=R.onVnodeBeforeMount)&&he(m,g,c)}N&&Pe(c,null,g,"beforeMount");const V=(!p||p&&!p.pendingBranch)&&I&&!I.persisted;V&&I.beforeEnter(w),s(w,u,d),((m=R&&R.onVnodeMounted)||V||N)&&ue(()=>{m&&he(m,g,c),V&&I.enter(w),N&&Pe(c,null,g,"mounted")},p)},q=(c,u,d,g,p)=>{if(d&&P(c,d),g)for(let x=0;x{for(let m=w;m{const y=u.el=c.el;let{patchFlag:w,dynamicChildren:m,dirs:A}=u;w|=c.patchFlag&16;const R=c.props||Y,O=u.props||Y;let I;d&&ke(d,!1),(I=O.onVnodeBeforeUpdate)&&he(I,d,u,c),A&&Pe(u,c,d,"beforeUpdate"),d&&ke(d,!0);const N=p&&u.type!=="foreignObject";if(m?X(c.dynamicChildren,m,y,d,g,N,x):E||k(c,u,y,null,d,g,N,x,!1),w>0){if(w&16)G(y,u,R,O,d,g,p);else if(w&2&&R.class!==O.class&&i(y,"class",null,O.class,p),w&4&&i(y,"style",R.style,O.style,p),w&8){const V=u.dynamicProps;for(let D=0;D{I&&he(I,d,u,c),A&&Pe(u,c,d,"updated")},g)},X=(c,u,d,g,p,x,E)=>{for(let y=0;y{if(d!==g){if(d!==Y)for(const y in d)!mt(y)&&!(y in g)&&i(c,y,d[y],null,E,u.children,p,x,Fe);for(const y in g){if(mt(y))continue;const w=g[y],m=d[y];w!==m&&y!=="value"&&i(c,y,m,w,E,u.children,p,x,Fe)}"value"in g&&i(c,"value",d.value,g.value)}},Re=(c,u,d,g,p,x,E,y,w)=>{const m=u.el=c?c.el:o(""),A=u.anchor=c?c.anchor:o("");let{patchFlag:R,dynamicChildren:O,slotScopeIds:I}=u;I&&(y=y?y.concat(I):I),c==null?(s(m,d,g),s(A,d,g),L(u.children,d,A,p,x,E,y,w)):R>0&&R&64&&O&&c.dynamicChildren?(X(c.dynamicChildren,O,d,p,x,E,y),(u.key!=null||p&&u===p.subTree)&&Tr(c,u,!0)):k(c,u,d,A,p,x,E,y,w)},re=(c,u,d,g,p,x,E,y,w)=>{u.slotScopeIds=y,c==null?u.shapeFlag&512?p.ctx.activate(u,d,g,E,w):M(u,d,g,p,x,E,w):ve(c,u,w)},M=(c,u,d,g,p,x,E)=>{const y=c.component=Nl(c,g,p);if(pr(c)&&(y.ctx.renderer=Qe),$l(y),y.asyncDep){if(p&&p.registerDep(y,z),!c.el){const w=y.subTree=oe(Ke);_(null,w,u,d)}return}z(y,c,u,d,p,x,E)},ve=(c,u,d)=>{const g=u.component=c.component;if(ki(c,u,d))if(g.asyncDep&&!g.asyncResolved){Q(g,u,d);return}else g.next=u,ji(g.update),g.update();else u.el=c.el,g.vnode=u},z=(c,u,d,g,p,x,E)=>{const y=()=>{if(c.isMounted){let{next:A,bu:R,u:O,parent:I,vnode:N}=c,V=A,D;ke(c,!1),A?(A.el=N.el,Q(c,A,E)):A=N,R&&on(R),(D=A.props&&A.props.onVnodeBeforeUpdate)&&he(D,I,A,N),ke(c,!0);const ee=cn(c),ge=c.subTree;c.subTree=ee,F(ge,ee,b(ge.el),Mt(ge),c,p,x),A.el=ee.el,V===null&&Vi(c,ee.el),O&&ue(O,p),(D=A.props&&A.props.onVnodeUpdated)&&ue(()=>he(D,I,A,N),p)}else{let A;const{el:R,props:O}=u,{bm:I,m:N,parent:V}=c,D=bt(u);if(ke(c,!1),I&&on(I),!D&&(A=O&&O.onVnodeBeforeMount)&&he(A,V,u),ke(c,!0),R&&rn){const ee=()=>{c.subTree=cn(c),rn(R,c.subTree,c,p,null)};D?u.type.__asyncLoader().then(()=>!c.isUnmounted&&ee()):ee()}else{const ee=c.subTree=cn(c);F(null,ee,d,g,c,p,x),u.el=ee.el}if(N&&ue(N,p),!D&&(A=O&&O.onVnodeMounted)){const ee=u;ue(()=>he(A,V,ee),p)}(u.shapeFlag&256||V&&bt(V.vnode)&&V.vnode.shapeFlag&256)&&c.a&&ue(c.a,p),c.isMounted=!0,u=d=g=null}},w=c.effect=new Sn(y,()=>Bn(m),c.scope),m=c.update=()=>w.run();m.id=c.uid,ke(c,!0),m()},Q=(c,u,d)=>{u.component=c;const g=c.vnode.props;c.vnode=u,c.next=null,_l(c,u.props,g,d),xl(c,u.children,d),at(),os(),dt()},k=(c,u,d,g,p,x,E,y,w=!1)=>{const m=c&&c.children,A=c?c.shapeFlag:0,R=u.children,{patchFlag:O,shapeFlag:I}=u;if(O>0){if(O&128){Pt(m,R,d,g,p,x,E,y,w);return}else if(O&256){We(m,R,d,g,p,x,E,y,w);return}}I&8?(A&16&&Fe(m,p,x),R!==m&&h(d,R)):A&16?I&16?Pt(m,R,d,g,p,x,E,y,w):Fe(m,p,x,!0):(A&8&&h(d,""),I&16&&L(R,d,g,p,x,E,y,w))},We=(c,u,d,g,p,x,E,y,w)=>{c=c||tt,u=u||tt;const m=c.length,A=u.length,R=Math.min(m,A);let O;for(O=0;OA?Fe(c,p,x,!0,!1,R):L(u,d,g,p,x,E,y,w,R)},Pt=(c,u,d,g,p,x,E,y,w)=>{let m=0;const A=u.length;let R=c.length-1,O=A-1;for(;m<=R&&m<=O;){const I=c[m],N=u[m]=w?je(u[m]):ye(u[m]);if(pt(I,N))F(I,N,d,null,p,x,E,y,w);else break;m++}for(;m<=R&&m<=O;){const I=c[R],N=u[O]=w?je(u[O]):ye(u[O]);if(pt(I,N))F(I,N,d,null,p,x,E,y,w);else break;R--,O--}if(m>R){if(m<=O){const I=O+1,N=IO)for(;m<=R;)Ae(c[m],p,x,!0),m++;else{const I=m,N=m,V=new Map;for(m=N;m<=O;m++){const de=u[m]=w?je(u[m]):ye(u[m]);de.key!=null&&V.set(de.key,m)}let D,ee=0;const ge=O-N+1;let Ge=!1,Xn=0;const ht=new Array(ge);for(m=0;m=ge){Ae(de,p,x,!0);continue}let Oe;if(de.key!=null)Oe=V.get(de.key);else for(D=N;D<=O;D++)if(ht[D-N]===0&&pt(de,u[D])){Oe=D;break}Oe===void 0?Ae(de,p,x,!0):(ht[Oe-N]=m+1,Oe>=Xn?Xn=Oe:Ge=!0,F(de,u[Oe],d,null,p,x,E,y,w),ee++)}const Zn=Ge?vl(ht):tt;for(D=Zn.length-1,m=ge-1;m>=0;m--){const de=N+m,Oe=u[de],Qn=de+1{const{el:x,type:E,transition:y,children:w,shapeFlag:m}=c;if(m&6){qe(c.component.subTree,u,d,g);return}if(m&128){c.suspense.move(u,d,g);return}if(m&64){E.move(c,u,d,Qe);return}if(E===be){s(x,u,d);for(let R=0;Ry.enter(x),p);else{const{leave:R,delayLeave:O,afterLeave:I}=y,N=()=>s(x,u,d),V=()=>{R(x,()=>{N(),I&&I()})};O?O(x,N,V):V()}else s(x,u,d)},Ae=(c,u,d,g=!1,p=!1)=>{const{type:x,props:E,ref:y,children:w,dynamicChildren:m,shapeFlag:A,patchFlag:R,dirs:O}=c;if(y!=null&&zt(y,null,d,c,!0),A&256){u.ctx.deactivate(c);return}const I=A&1&&O,N=!bt(c);let V;if(N&&(V=E&&E.onVnodeBeforeUnmount)&&he(V,u,c),A&6)Ur(c.component,d,g);else{if(A&128){c.suspense.unmount(d,g);return}I&&Pe(c,null,u,"beforeUnmount"),A&64?c.type.remove(c,u,d,p,Qe,g):m&&(x!==be||R>0&&R&64)?Fe(m,u,d,!1,!0):(x===be&&R&384||!p&&A&16)&&Fe(w,u,d),g&&Yn(c)}(N&&(V=E&&E.onVnodeUnmounted)||I)&&ue(()=>{V&&he(V,u,c),I&&Pe(c,null,u,"unmounted")},d)},Yn=c=>{const{type:u,el:d,anchor:g,transition:p}=c;if(u===be){jr(d,g);return}if(u===xt){$(c);return}const x=()=>{r(d),p&&!p.persisted&&p.afterLeave&&p.afterLeave()};if(c.shapeFlag&1&&p&&!p.persisted){const{leave:E,delayLeave:y}=p,w=()=>E(d,x);y?y(c.el,x,w):w()}else x()},jr=(c,u)=>{let d;for(;c!==u;)d=T(c),r(c),c=d;r(u)},Ur=(c,u,d)=>{const{bum:g,scope:p,update:x,subTree:E,um:y}=c;g&&on(g),p.stop(),x&&(x.active=!1,Ae(E,c,u,d)),y&&ue(y,u),ue(()=>{c.isUnmounted=!0},u),u&&u.pendingBranch&&!u.isUnmounted&&c.asyncDep&&!c.asyncResolved&&c.suspenseId===u.pendingId&&(u.deps--,u.deps===0&&u.resolve())},Fe=(c,u,d,g=!1,p=!1,x=0)=>{for(let E=x;Ec.shapeFlag&6?Mt(c.component.subTree):c.shapeFlag&128?c.suspense.next():T(c.anchor||c.el),Jn=(c,u,d)=>{c==null?u._vnode&&Ae(u._vnode,null,null,!0):F(u._vnode||null,c,u,null,null,null,d),os(),qt(),u._vnode=c},Qe={p:F,um:Ae,m:qe,r:Yn,mt:M,mc:L,pc:k,pbc:X,n:Mt,o:e};let sn,rn;return t&&([sn,rn]=t(Qe)),{render:Jn,hydrate:sn,createApp:El(Jn,sn)}}function ke({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function Tr(e,t,n=!1){const s=e.children,r=t.children;if(S(s)&&S(r))for(let i=0;i>1,e[n[o]]0&&(t[s]=n[i-1]),n[i]=s)}}for(i=n.length,l=n[i-1];i-- >0;)n[i]=l,l=t[l];return n}const Al=e=>e.__isTeleport,be=Symbol(void 0),ot=Symbol(void 0),Ke=Symbol(void 0),xt=Symbol(void 0),wt=[];let Ee=null;function Ol(e=!1){wt.push(Ee=e?null:[])}function Pl(){wt.pop(),Ee=wt[wt.length-1]||null}let vt=1;function ms(e){vt+=e}function Rr(e){return e.dynamicChildren=vt>0?Ee||tt:null,Pl(),vt>0&&Ee&&Ee.push(e),e}function Do(e,t,n,s,r,i){return Rr(Ar(e,t,n,s,r,i,!0))}function Ml(e,t,n,s,r){return Rr(oe(e,t,n,s,r,!0))}function Cn(e){return e?e.__v_isVNode===!0:!1}function pt(e,t){return e.type===t.type&&e.key===t.key}const nn="__vInternal",vr=({key:e})=>e??null,Dt=({ref:e,ref_key:t,ref_for:n})=>e!=null?se(e)||le(e)||H(e)?{i:we,r:e,k:t,f:!!n}:e:null;function Ar(e,t=null,n=null,s=0,r=null,i=e===be?0:1,l=!1,o=!1){const f={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&vr(t),ref:t&&Dt(t),scopeId:ur,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:s,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:we};return o?(Vn(f,n),i&128&&e.normalize(f)):n&&(f.shapeFlag|=se(n)?8:16),vt>0&&!l&&Ee&&(f.patchFlag>0||i&6)&&f.patchFlag!==32&&Ee.push(f),f}const oe=Il;function Il(e,t=null,n=null,s=0,r=null,i=!1){if((!e||e===cl)&&(e=Ke),Cn(e)){const o=ct(e,t,!0);return n&&Vn(o,n),vt>0&&!i&&Ee&&(o.shapeFlag&6?Ee[Ee.indexOf(e)]=o:Ee.push(o)),o.patchFlag|=-2,o}if(Kl(e)&&(e=e.__vccOpts),t){t=Fl(t);let{class:o,style:f}=t;o&&!se(o)&&(t.class=An(o)),J(f)&&(Qs(f)&&!S(f)&&(f=fe({},f)),t.style=vn(f))}const l=se(e)?1:zi(e)?128:Al(e)?64:J(e)?4:H(e)?2:0;return Ar(e,t,n,s,r,l,i,!0)}function Fl(e){return e?Qs(e)||nn in e?fe({},e):e:null}function ct(e,t,n=!1){const{props:s,ref:r,patchFlag:i,children:l}=e,o=t?Sl(s||{},t):s;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:o,key:o&&vr(o),ref:t&&t.ref?n&&r?S(r)?r.concat(Dt(t)):[r,Dt(t)]:Dt(t):r,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==be?i===-1?16:i|16:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&ct(e.ssContent),ssFallback:e.ssFallback&&ct(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function Or(e=" ",t=0){return oe(ot,null,e,t)}function Ko(e,t){const n=oe(xt,null,e);return n.staticCount=t,n}function Wo(e="",t=!1){return t?(Ol(),Ml(Ke,null,e)):oe(Ke,null,e)}function ye(e){return e==null||typeof e=="boolean"?oe(Ke):S(e)?oe(be,null,e.slice()):typeof e=="object"?je(e):oe(ot,null,String(e))}function je(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:ct(e)}function Vn(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if(S(t))n=16;else if(typeof t=="object")if(s&65){const r=t.default;r&&(r._c&&(r._d=!1),Vn(e,r()),r._c&&(r._d=!0));return}else{n=32;const r=t._;!r&&!(nn in t)?t._ctx=we:r===3&&we&&(we.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else H(t)?(t={default:t,_ctx:we},n=32):(t=String(t),s&64?(n=16,t=[Or(t)]):n=8);e.children=t,e.shapeFlag|=n}function Sl(...e){const t={};for(let n=0;n{te=e,e.scope.on()},Ze=()=>{te&&te.scope.off(),te=null};function Pr(e){return e.vnode.shapeFlag&4}let At=!1;function $l(e,t=!1){At=t;const{props:n,children:s}=e.vnode,r=Pr(e);ml(e,n,r,t),yl(e,s);const i=r?jl(e,t):void 0;return At=!1,i}function jl(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=_t(new Proxy(e.ctx,ul));const{setup:s}=n;if(s){const r=e.setupContext=s.length>1?Bl(e):null;ft(e),at();const i=Be(s,e,0,[e.props,r]);if(dt(),Ze(),js(i)){if(i.then(Ze,Ze),t)return i.then(l=>{_s(e,l,t)}).catch(l=>{Gt(l,e,0)});e.asyncDep=i}else _s(e,i,t)}else Mr(e,t)}function _s(e,t,n){H(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:J(t)&&(e.setupState=sr(t)),Mr(e,n)}let bs;function Mr(e,t,n){const s=e.type;if(!e.render){if(!t&&bs&&!s.render){const r=s.template||qn(e).template;if(r){const{isCustomElement:i,compilerOptions:l}=e.appContext.config,{delimiters:o,compilerOptions:f}=s,a=fe(fe({isCustomElement:i,delimiters:o},l),f);s.render=bs(r,a)}}e.render=s.render||Ce}ft(e),at(),al(e),dt(),Ze()}function Ul(e){return new Proxy(e.attrs,{get(t,n){return ae(e,"get","$attrs"),t[n]}})}function Bl(e){const t=s=>{e.exposed=s||{}};let n;return{get attrs(){return n||(n=Ul(e))},slots:e.slots,emit:e.emit,expose:t}}function zn(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(sr(_t(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in yt)return yt[n](e)},has(t,n){return n in t||n in yt}}))}function Dl(e,t=!0){return H(e)?e.displayName||e.name:e.name||t&&e.__name}function Kl(e){return H(e)&&"__vccOpts"in e}const me=(e,t)=>Li(e,t,At);function ys(e,t,n){const s=arguments.length;return s===2?J(t)&&!S(t)?Cn(t)?oe(e,null,[t]):oe(e,t):oe(e,null,t):(s>3?n=Array.prototype.slice.call(arguments,2):s===3&&Cn(n)&&(n=[n]),oe(e,t,n))}const Wl=Symbol(""),ql=()=>it(Wl),kl="3.2.47",Vl="http://www.w3.org/2000/svg",Ye=typeof document<"u"?document:null,xs=Ye&&Ye.createElement("template"),zl={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const r=t?Ye.createElementNS(Vl,e):Ye.createElement(e,n?{is:n}:void 0);return e==="select"&&s&&s.multiple!=null&&r.setAttribute("multiple",s.multiple),r},createText:e=>Ye.createTextNode(e),createComment:e=>Ye.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Ye.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,r,i){const l=n?n.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===i||!(r=r.nextSibling)););else{xs.innerHTML=s?`${e}`:e;const o=xs.content;if(s){const f=o.firstChild;for(;f.firstChild;)o.appendChild(f.firstChild);o.removeChild(f)}t.insertBefore(o,n)}return[l?l.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}};function Yl(e,t,n){const s=e._vtc;s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}function Jl(e,t,n){const s=e.style,r=se(n);if(n&&!r){if(t&&!se(t))for(const i in t)n[i]==null&&Tn(s,i,"");for(const i in n)Tn(s,i,n[i])}else{const i=s.display;r?t!==n&&(s.cssText=n):t&&e.removeAttribute("style"),"_vod"in e&&(s.display=i)}}const ws=/\s*!important$/;function Tn(e,t,n){if(S(n))n.forEach(s=>Tn(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=Xl(e,t);ws.test(n)?e.setProperty(ut(s),n.replace(ws,""),"important"):e[s]=n}}const Es=["Webkit","Moz","ms"],un={};function Xl(e,t){const n=un[t];if(n)return n;let s=Ie(t);if(s!=="filter"&&s in e)return un[t]=s;s=Xt(s);for(let r=0;ran||(so.then(()=>an=0),an=Date.now());function io(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;Te(lo(s,n.value),t,5,[s])};return n.value=e,n.attached=ro(),n}function lo(e,t){if(S(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>r=>!r._stopped&&s&&s(r))}else return t}const Rs=/^on[a-z]/,oo=(e,t,n,s,r=!1,i,l,o,f)=>{t==="class"?Yl(e,s,r):t==="style"?Jl(e,n,s):Ot(t)?On(t)||to(e,t,n,s,l):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):co(e,t,s,r))?Ql(e,t,s,i,l,o,f):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),Zl(e,t,s,r))};function co(e,t,n,s){return s?!!(t==="innerHTML"||t==="textContent"||t in e&&Rs.test(t)&&H(n)):t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA"||Rs.test(t)&&se(n)?!1:t in e}const fo=fe({patchProp:oo},zl);let dn,vs=!1;function uo(){return dn=vs?dn:Tl(fo),vs=!0,dn}const qo=(...e)=>{const t=uo().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=ao(s);if(r)return n(r,!0,r instanceof SVGElement)},t};function ao(e){return se(e)?document.querySelector(e):e}const ko=(e,t)=>{const n=e.__vccOpts||e;for(const[s,r]of t)n[s]=r;return n},ho="modulepreload",po=function(e){return"/"+e},As={},Vo=function(t,n,s){if(!n||n.length===0)return t();const r=document.getElementsByTagName("link");return Promise.all(n.map(i=>{if(i=po(i),i in As)return;As[i]=!0;const l=i.endsWith(".css"),o=l?'[rel="stylesheet"]':"";if(!!s)for(let h=r.length-1;h>=0;h--){const b=r[h];if(b.href===i&&(!l||b.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${i}"]${o}`))return;const a=document.createElement("link");if(a.rel=l?"stylesheet":ho,l||(a.as="script",a.crossOrigin=""),a.href=i,document.head.appendChild(a),l)return new Promise((h,b)=>{a.addEventListener("load",h),a.addEventListener("error",()=>b(new Error(`Unable to preload CSS for ${i}`)))})})).then(()=>t())},go=window.__VP_SITE_DATA__,Ir=/^[a-z]+:/i,Fr=/#.*$/,mo=/(index)?\.(md|html)$/,pe=typeof document<"u",Sr={relativePath:"",filePath:"",title:"404",description:"Not Found",headers:[],frontmatter:{sidebar:!1,layout:"page"},lastUpdated:0,isNotFound:!0};function _o(e,t,n=!1){if(t===void 0)return!1;if(e=Os(`/${e}`),n)return new RegExp(t).test(e);if(Os(t)!==e)return!1;const s=t.match(Fr);return s?(pe?location.hash:"")===s[0]:!0}function Os(e){return decodeURI(e).replace(Fr,"").replace(mo,"")}function bo(e){return Ir.test(e)}function yo(e,t){var s,r,i,l,o,f,a;const n=Object.keys(e.locales).find(h=>h!=="root"&&!bo(h)&&_o(t,`/${h}/`,!0))||"root";return Object.assign({},e,{localeIndex:n,lang:((s=e.locales[n])==null?void 0:s.lang)??e.lang,dir:((r=e.locales[n])==null?void 0:r.dir)??e.dir,title:((i=e.locales[n])==null?void 0:i.title)??e.title,titleTemplate:((l=e.locales[n])==null?void 0:l.titleTemplate)??e.titleTemplate,description:((o=e.locales[n])==null?void 0:o.description)??e.description,head:Lr(e.head,((f=e.locales[n])==null?void 0:f.head)??[]),themeConfig:{...e.themeConfig,...(a=e.locales[n])==null?void 0:a.themeConfig}})}function Hr(e,t){const n=t.title||e.title,s=t.titleTemplate??e.titleTemplate;if(typeof s=="string"&&s.includes(":title"))return s.replace(/:title/g,n);const r=xo(e.title,s);return`${n}${r}`}function xo(e,t){return t===!1?"":t===!0||t===void 0?` | ${e}`:e===t?"":` | ${t}`}function wo(e,t){const[n,s]=t;if(n!=="meta")return!1;const r=Object.entries(s)[0];return r==null?!1:e.some(([i,l])=>i===n&&l[r[0]]===r[1])}function Lr(e,t){return[...e.filter(n=>!wo(t,n)),...t]}const Eo=/[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g,Co=/^[a-z]:/i;function Ps(e){const t=Co.exec(e),n=t?t[0]:"";return n+e.slice(n.length).replace(Eo,"_").replace(/(^|\/)_+(?=[^/]*$)/,"$1")}const To=Symbol(),Je=Mi(go);function zo(e){const t=me(()=>yo(Je.value,e.data.relativePath));return{site:t,theme:me(()=>t.value.themeConfig),page:me(()=>e.data),frontmatter:me(()=>e.data.frontmatter),params:me(()=>e.data.params),lang:me(()=>t.value.lang),dir:me(()=>t.value.dir),localeIndex:me(()=>t.value.localeIndex||"root"),title:me(()=>Hr(t.value,e.data)),description:me(()=>e.data.description||t.value.description),isDark:tr(!1)}}function Yo(){const e=it(To);if(!e)throw new Error("vitepress data not properly injected in app");return e}function Ro(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function Ms(e){return Ir.test(e)||e.startsWith(".")?e:Ro(Je.value.base,e)}function vo(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t=t.replace(/\/$/,"/index"),pe){const n="/";t=Ps(t.slice(n.length).replace(/\//g,"_")||"index")+".md";let s=__VP_HASH_MAP__[t.toLowerCase()];s||(t=t.endsWith("_index.md")?t.slice(0,-9)+".md":t.slice(0,-3)+"_index.md",s=__VP_HASH_MAP__[t.toLowerCase()]),t=`${n}assets/${t}.${s}.js`}else t=`./${Ps(t.slice(1).replace(/\//g,"_"))}.md.js`;return t}let Ao=[];const Oo=Symbol(),Is="http://a.com",Po=()=>({path:"/",component:null,data:Sr});function Jo(e,t){const n=Qt(Po()),s={route:n,go:r};async function r(o=pe?location.href:"/"){var a,h;await((a=s.onBeforeRouteChange)==null?void 0:a.call(s,o));const f=new URL(o,Is);Je.value.cleanUrls||!f.pathname.endsWith("/")&&!f.pathname.endsWith(".html")&&(f.pathname+=".html",o=f.pathname+f.search+f.hash),pe&&o!==location.href&&(history.replaceState({scrollPosition:window.scrollY},document.title),history.pushState(null,"",o)),await l(o),await((h=s.onAfterRouteChanged)==null?void 0:h.call(s,o))}let i=null;async function l(o,f=0,a=!1){const h=new URL(o,Is),b=i=h.pathname;try{let T=await e(b);if(i===b){i=null;const{default:P,__pageData:j}=T;if(!P)throw new Error(`Invalid route component: ${P}`);n.path=pe?b:Ms(b),n.component=_t(P),n.data=_t(j),pe&&lr(()=>{let F=Je.value.base+j.relativePath.replace(/(?:(^|\/)index)?\.md$/,"$1");if(!Je.value.cleanUrls&&!F.endsWith("/")&&(F+=".html"),F!==h.pathname&&(h.pathname=F,o=F+h.search+h.hash,history.replaceState(null,"",o)),h.hash&&!f){let K=null;try{K=document.querySelector(decodeURIComponent(h.hash))}catch(_){console.warn(_)}if(K){Fs(K,h.hash);return}}window.scrollTo(0,f)})}}catch(T){if(!/fetch/.test(T.message)&&!/^\/404(\.html|\/)?$/.test(o)&&console.error(T),!a)try{const P=await fetch(Je.value.base+"hashmap.json");window.__VP_HASH_MAP__=await P.json(),await l(o,f,!0);return}catch{}i===b&&(i=null,n.path=pe?b:Ms(b),n.component=t?_t(t):null,n.data=Sr)}}return pe&&(window.addEventListener("click",o=>{if(o.target.closest("button"))return;const a=o.target.closest("a");if(a&&!a.closest(".vp-raw")&&(a instanceof SVGElement||!a.download)){const{target:h}=a,{href:b,origin:T,pathname:P,hash:j,search:F}=new URL(a.href instanceof SVGAnimatedString?a.href.animVal:a.href,a.baseURI),K=window.location,_=P.match(/\.\w+$/);!o.ctrlKey&&!o.shiftKey&&!o.altKey&&!o.metaKey&&h!=="_blank"&&T===K.origin&&!(_&&_[0]!==".html")&&(o.preventDefault(),P===K.pathname&&F===K.search?j&&(j!==K.hash&&(history.pushState(null,"",j),window.dispatchEvent(new Event("hashchange"))),Fs(a,j,a.classList.contains("header-anchor"))):r(b))}},{capture:!0}),window.addEventListener("popstate",o=>{l(location.href,o.state&&o.state.scrollPosition||0)}),window.addEventListener("hashchange",o=>{o.preventDefault()})),s}function Mo(){const e=it(Oo);if(!e)throw new Error("useRouter() is called without provider.");return e}function Nr(){return Mo().route}function Fs(e,t,n=!1){let s=null;try{s=e.classList.contains("header-anchor")?e:document.querySelector(decodeURIComponent(t))}catch(r){console.warn(r)}if(s){const r=Je.value.scrollOffset;let i=0;if(typeof r=="number")i=r;else if(typeof r=="string")i=Ss(r);else if(Array.isArray(r))for(const f of r){const a=Ss(f);if(a){i=a;break}}const l=parseInt(window.getComputedStyle(s).paddingTop,10),o=window.scrollY+s.getBoundingClientRect().top-i+l;!n||Math.abs(o-window.scrollY)>window.innerHeight?window.scrollTo(0,o):window.scrollTo({left:0,top:o,behavior:"smooth"})}}function Ss(e){const t=document.querySelector(e);if(!t)return 0;const n=t.getBoundingClientRect().bottom;return n<0?0:n+24}const Hs=()=>Ao.forEach(e=>e()),Xo=hr({name:"VitePressContent",props:{as:{type:[Object,String],default:"div"}},setup(e){const t=Nr();return()=>ys(e.as,{style:{position:"relative"}},[t.component?ys(t.component,{onVnodeMounted:Hs,onVnodeUpdated:Hs}):"404 Page Not Found"])}});function Zo(e,t){let n=[],s=!0;const r=i=>{if(s){s=!1;return}n.forEach(l=>document.head.removeChild(l)),n=[],i.forEach(l=>{const o=Io(l);document.head.appendChild(o),n.push(o)})};Ji(()=>{const i=e.data,l=t.value,o=i&&i.description,f=i&&i.frontmatter.head||[];document.title=Hr(l,i),document.querySelector("meta[name=description]").setAttribute("content",o||l.description),r(Lr(l.head,So(f)))})}function Io([e,t,n]){const s=document.createElement(e);for(const r in t)s.setAttribute(r,t[r]);return n&&(s.innerHTML=n),s}function Fo(e){return e[0]==="meta"&&e[1]&&e[1].name==="description"}function So(e){return e.filter(t=>!Fo(t))}const hn=new Set,$r=()=>document.createElement("link"),Ho=e=>{const t=$r();t.rel="prefetch",t.href=e,document.head.appendChild(t)},Lo=e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};let Ut;const No=pe&&(Ut=$r())&&Ut.relList&&Ut.relList.supports&&Ut.relList.supports("prefetch")?Ho:Lo;function Qo(){if(!pe||!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const s=()=>{n&&n.disconnect(),n=new IntersectionObserver(i=>{i.forEach(l=>{if(l.isIntersecting){const o=l.target;n.unobserve(o);const{pathname:f}=o;if(!hn.has(f)){hn.add(f);const a=vo(f);No(a)}}})}),t(()=>{document.querySelectorAll("#app a").forEach(i=>{const{target:l}=i,{hostname:o,pathname:f}=new URL(i.href instanceof SVGAnimatedString?i.href.animVal:i.href,i.baseURI),a=f.match(/\.\w+$/);a&&a[0]!==".html"||l!=="_blank"&&o===location.hostname&&(f!==location.pathname?n.observe(i):hn.add(f))})})};Kn(s);const r=Nr();Bt(()=>r.path,s),Wn(()=>{n&&n.disconnect()})}const Go=hr({setup(e,{slots:t}){const n=tr(!1);return Kn(()=>{n.value=!0}),()=>n.value&&t.default?t.default():null}});function ec(){if(pe){const e=new Map;window.addEventListener("click",t=>{var s;const n=t.target;if(n.matches('div[class*="language-"] > button.copy')){const r=n.parentElement,i=(s=n.nextElementSibling)==null?void 0:s.nextElementSibling;if(!r||!i)return;const l=/language-(shellscript|shell|bash|sh|zsh)/.test(r.className);let o="";i.querySelectorAll("span.line:not(.diff.remove)").forEach(f=>o+=(f.textContent||"")+` +`),o=o.slice(0,-1),l&&(o=o.replace(/^ *(\$|>) /gm,"").trim()),$o(o).then(()=>{n.classList.add("copied"),clearTimeout(e.get(n));const f=setTimeout(()=>{n.classList.remove("copied"),n.blur(),e.delete(n)},2e3);e.set(n,f)})}})}}async function $o(e){try{return navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),n=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const s=document.getSelection(),r=s?s.rangeCount>0&&s.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),r&&(s.removeAllRanges(),s.addRange(r)),n&&n.focus()}}function tc(){pe&&window.addEventListener("click",e=>{var n,s;const t=e.target;if(t.matches(".vp-code-group input")){const r=(n=t.parentElement)==null?void 0:n.parentElement,i=Array.from((r==null?void 0:r.querySelectorAll("input"))||[]).indexOf(t),l=r==null?void 0:r.querySelector('div[class*="language-"].active'),o=(s=r==null?void 0:r.querySelectorAll('div[class*="language-"]:not(.language-id)'))==null?void 0:s[i];l&&o&&l!==o&&(l.classList.remove("active"),o.classList.add("active"))}})}export{Je as A,qo as B,Xo as C,Jo as D,vo as E,be as F,Vo as G,Oo as R,ko as _,Ko as a,Ar as b,Do as c,hr as d,Fi as e,oe as f,Wo as g,Nr as h,me as i,Uo as j,Ml as k,pe as l,Zo as m,Kn as n,Ol as o,Qo as p,ec as q,Bo as r,tc as s,jo as t,Yo as u,ys as v,Ji as w,zo as x,To as y,Go as z}; diff --git a/assets/index.md.627b3dc9.js b/assets/index.md.627b3dc9.js new file mode 100644 index 0000000..7d4e5b3 --- /dev/null +++ b/assets/index.md.627b3dc9.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as a}from"./chunks/framework.f55bdc3d.js";const _=JSON.parse(`{"title":"steve's blog","description":"","frontmatter":{"title":"steve's blog","subtext":"c#, sport, paragliding","index":true},"headers":[],"relativePath":"index.md","filePath":"index.md"}`),s={name:"index.md"};function n(r,o,i,c,d,p){return a(),t("div")}const m=e(s,[["render",n]]);export{_ as __pageData,m as default}; diff --git a/assets/index.md.627b3dc9.lean.js b/assets/index.md.627b3dc9.lean.js new file mode 100644 index 0000000..7d4e5b3 --- /dev/null +++ b/assets/index.md.627b3dc9.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as a}from"./chunks/framework.f55bdc3d.js";const _=JSON.parse(`{"title":"steve's blog","description":"","frontmatter":{"title":"steve's blog","subtext":"c#, sport, paragliding","index":true},"headers":[],"relativePath":"index.md","filePath":"index.md"}`),s={name:"index.md"};function n(r,o,i,c,d,p){return a(),t("div")}const m=e(s,[["render",n]]);export{_ as __pageData,m as default}; diff --git a/assets/posts_dotnet-kerberos.md.e7e9094f.js b/assets/posts_dotnet-kerberos.md.e7e9094f.js new file mode 100644 index 0000000..8db1863 --- /dev/null +++ b/assets/posts_dotnet-kerberos.md.e7e9094f.js @@ -0,0 +1,16 @@ +import{_ as e,c as a,o as s,a as t}from"./chunks/framework.f55bdc3d.js";const b=JSON.parse('{"title":"Creating Kerberos secured connections from Dotnet on Linux","description":"","frontmatter":{"title":"Creating Kerberos secured connections from Dotnet on Linux","date":"2023-08-05T00:00:00.000Z","author":"Stephen Brown"},"headers":[],"relativePath":"posts/dotnet-kerberos.md","filePath":"posts/dotnet-kerberos.md"}'),n={name:"posts/dotnet-kerberos.md"},o=t(`

For the last few years at my workplace we've been using Kerberos authentication to connect to the on-prem SQL Server databases. On Windows this just worked like magic, as the services ran as a Windows domain user and so could connect to the database without any additional configuration. However, when moving to Kubernetes and Linux systems, this didn't work out of the box.

Our initial solution was following this red hat blog post which details how to setup a sidecar which keeps a kerberos token valid. This worked for a number of years but did have some problems:

  • We ran 100+ containers and each needed a sidecar - this cost a few gigabytes of memory and some CPU allowance
  • When the token was being refreshed, it was not available for a few milliseconds
  • It was extra hassle and created slightly larger deployments and more complex Deployment configs.

When investigating a seperate issue (dotnet 7 caused some transient kerberos faults) we realised that actually the whole sidecar approach was completely unneccessary! This post will give the minimum you actually need to call Keberos services from Linux

  1. You'll need to add the keberos tools to your docker image. Maybe you have a base image you share, maybe not. For alpine images:

RUN apk add --no-cache krb5

  1. You'll need to have a krb5 configuration that points to your domain controller. Microsoft has an example

For example, ours looks like this:

[logging]
+default
+
+[libdefaults]
+...standard things here
+default_client_keytab_name=/krb5/client.keytab
+default_keytab_name=/krb5/krb5.keytab
+default_ccache_name=FILE:/dev/shm/ccache
+default_realm = EXAMPLE.COM
+
+[realms]
+EXAMPLE.COM = {
+    kdc = ADS.EXAMPLE.COM:88
+}

You can then add this to the docker image like

RUN mkdir /krb5 && mkdir /customkrb5
+COPY krb5.conf /customkrb5/krb5.conf
+ENV KRB5_CONFIG=/customkrb5/krb5.conf

We put it in a custom folder as some container systems have the limitation that mounting a directory overrides all files in that directory (we'll do this later)

  1. You then need a keytab added to the image. You will probably want this done at runtime. Get a keytab (maybe off your AD system administrators, maybe you create it yourself with ktutil) and make sure this ends up at /krb5/client.keytab. We use kubernetes secrets and volume mounts for this

  2. Connect to the database using Integrated Security=true. This should now just work. You can check by calling klist inside the container to see that it has a ticket

`,13),r=[o];function i(l,c,d,p,h,u){return s(),a("div",null,r)}const k=e(n,[["render",i]]);export{b as __pageData,k as default}; diff --git a/assets/posts_dotnet-kerberos.md.e7e9094f.lean.js b/assets/posts_dotnet-kerberos.md.e7e9094f.lean.js new file mode 100644 index 0000000..44ea9d6 --- /dev/null +++ b/assets/posts_dotnet-kerberos.md.e7e9094f.lean.js @@ -0,0 +1 @@ +import{_ as e,c as a,o as s,a as t}from"./chunks/framework.f55bdc3d.js";const b=JSON.parse('{"title":"Creating Kerberos secured connections from Dotnet on Linux","description":"","frontmatter":{"title":"Creating Kerberos secured connections from Dotnet on Linux","date":"2023-08-05T00:00:00.000Z","author":"Stephen Brown"},"headers":[],"relativePath":"posts/dotnet-kerberos.md","filePath":"posts/dotnet-kerberos.md"}'),n={name:"posts/dotnet-kerberos.md"},o=t("",13),r=[o];function i(l,c,d,p,h,u){return s(),a("div",null,r)}const k=e(n,[["render",i]]);export{b as __pageData,k as default}; diff --git a/assets/posts_github-actions-net7-docker.md.8901ce83.js b/assets/posts_github-actions-net7-docker.md.8901ce83.js new file mode 100644 index 0000000..9f08aaa --- /dev/null +++ b/assets/posts_github-actions-net7-docker.md.8901ce83.js @@ -0,0 +1,48 @@ +import{_ as s,c as a,o as n,a as o}from"./chunks/framework.f55bdc3d.js";const l="/images/github_actions_permission.png",A=JSON.parse('{"title":"Dockerless docker builds in Github Actions with Dotnet 7","description":"","frontmatter":{"title":"Dockerless docker builds in Github Actions with Dotnet 7","date":"2022-10-14T00:00:00.000Z","author":"Stephen Brown","twitter":"@evolvedlight"},"headers":[],"relativePath":"posts/github-actions-net7-docker.md","filePath":"posts/github-actions-net7-docker.md"}'),e={name:"posts/github-actions-net7-docker.md"},p=o(`

How to setup a github build action that uses the dockerless build in .Net 7


Background

For me, one of the slowest parts of the fast build-test-deploy cycle was always waiting for docker builds to run. Especially with the practise of docker-in-docker builds which without special magic wouldn't cache anything, it could often take several minutes to create a docker application. .Net 7 promises to offer an alternative to this for simple apps - let's take a dive in.

Setup

You'll need .Net 7 for this, which isn't currently released at time of writing. However, in theory you can take a .Net 6 application and use the .Net 7 SDK to build it already, however YMMV.

With this installed, let's create a new app and get started! We're following along with https://devblogs.microsoft.com/dotnet/announcing-builtin-container-support-for-the-dotnet-sdk/ but with some changes

Basic app

bash
dotnet new webapi -o dockerless-docker
+cd dockerless-docker

We also need to do a couple of things: because .Net 7 isn't out yet, we need a globals file to tell the github actions to use it

That gives us our basic app - let's add a github actions build for it now, by adding the following:

json
{
+    "sdk": {
+        "version": "7.0.100-rc.2.22477.23"
+    }
+}

We'll also need to add a reference to this tool:

bash
dotnet add package Microsoft.NET.Build.Containers

Now, add a github actions to .github/workflows/whatever.yml

yaml
name: Create and publish a Docker image
+
+on:
+  push:
+    branches: [ "*" ]
+  pull_request:
+    branches: [ "main" ]
+
+
+env:
+  REGISTRY: ghcr.io
+  IMAGE_NAME: \${{ github.repository }}
+
+jobs:
+  build-and-push-image:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+
+    steps:
+      - uses: actions/checkout@v3
+      - name: Setup .NET SDK
+        uses: actions/setup-dotnet@v2
+      # Package the app into a linux-x64 container based on the dotnet/aspnet image
+      - name: Publish
+        run: dotnet publish --os linux --arch x64 --configuration Release -p:PublishProfile=DefaultContainer
+      - name: Checkout repository
+        uses: actions/checkout@v3
+      - name: Login to GitHub Container Registry
+        uses: docker/login-action@v2
+        with:
+          registry: ghcr.io
+          username: \${{ github.actor }}
+          password: \${{ secrets.GITHUB_TOKEN }}
+      - name: Tag built container with Github thing
+        run: |
+          docker tag dockerless-docker:1.0.0 ghcr.io/evolvedlight/dockerless-docker:1.0.0
+      - name: Push built container to Github Package Repo
+        run: |
+          docker push ghcr.io/evolvedlight/dockerless-docker:1.0.0

Here's now where the weird bit comes - on github the process to get a repository seems rather backward. You need to assign permissions for the repo to write images, but there's no way to do that until you've pushed an image manually!

So let's go around the hoops - create the docker image locally, tag it, login to the Github repository and upload it:

dotnet publish --os linux --arch x64 --configuration Release -p:PublishProfile=DefaultContainer
+docker tag dockerless-docker:1.0.0 ghcr.io/evolvedlight/dockerless-docker:1.0.0
+docker login ghcr.io -u <your username>

Enter a Personal access token that you can create on your github profile page, and finally push the image:

docker push ghcr.io/evolvedlight/dockerless-docker:1.0.0

Now go to your packages page, for me that's https://github.com/evolvedlight?tab=packages. Click on the new package page that was created, and then go to the settings for it (for me https://github.com/users/evolvedlight/packages/container/dockerless-docker/settings)

Under "Manage Actions access" add your repository with access.

In the end it'll look like this:

github permissions

Finally, rerun the action and it should work.

In the next blob post we'll look at fixing the above docker build to push with the right version numbers and tags

',27),t=[p];function c(r,i,y,D,C,d){return n(),a("div",null,t)}const u=s(e,[["render",c]]);export{A as __pageData,u as default}; diff --git a/assets/posts_github-actions-net7-docker.md.8901ce83.lean.js b/assets/posts_github-actions-net7-docker.md.8901ce83.lean.js new file mode 100644 index 0000000..143ae6d --- /dev/null +++ b/assets/posts_github-actions-net7-docker.md.8901ce83.lean.js @@ -0,0 +1 @@ +import{_ as s,c as a,o as n,a as o}from"./chunks/framework.f55bdc3d.js";const l="/images/github_actions_permission.png",A=JSON.parse('{"title":"Dockerless docker builds in Github Actions with Dotnet 7","description":"","frontmatter":{"title":"Dockerless docker builds in Github Actions with Dotnet 7","date":"2022-10-14T00:00:00.000Z","author":"Stephen Brown","twitter":"@evolvedlight"},"headers":[],"relativePath":"posts/github-actions-net7-docker.md","filePath":"posts/github-actions-net7-docker.md"}'),e={name:"posts/github-actions-net7-docker.md"},p=o("",27),t=[p];function c(r,i,y,D,C,d){return n(),a("div",null,t)}const u=s(e,[["render",c]]);export{A as __pageData,u as default}; diff --git a/assets/posts_init7-tv7.md.63fe95a3.js b/assets/posts_init7-tv7.md.63fe95a3.js new file mode 100644 index 0000000..cfdf851 --- /dev/null +++ b/assets/posts_init7-tv7.md.63fe95a3.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as a,a as r}from"./chunks/framework.f55bdc3d.js";const o="/images/network-iptv.png",n="/images/vlc-tv7.png",i="/images/udpxy.png",w=JSON.parse('{"title":"TV7 (IPTV from Init7) with Dream Machine and Unity Dream machine","description":"","frontmatter":{"title":"TV7 (IPTV from Init7) with Dream Machine and Unity Dream machine","date":"2023-08-04T00:00:00.000Z","author":"Stephen Brown"},"headers":[],"relativePath":"posts/init7-tv7.md","filePath":"posts/init7-tv7.md"}'),s={name:"posts/init7-tv7.md"},p=r('

If you're an Init7 TV customer and you want to use the Unify Dream Machine as your IPTV receiver, I figured it out for you! Here's a step-by-step guide to help you get started.

Step 1: Update to the Newest Dream Machine

Before you begin, make sure your Unify Dream Machine is updated to the latest firmware version. You can check for updates by logging into the Unify Dream Machine web interface and navigating to the "Settings" page. Your version needs to be higher than "UniFi Network Application 7.4.156".

Step 2: Enable the feature

You now have a new setup that you can do on the Network application:

udpxy

You can also see the basic unifi docs

You should now already be able to watch TV7 though VLC using this link: https://www.init7.net/en/support/faq/TV-andere-Geraete/

udpxy

However, I wanted to watch on a Samsung TV and none of the apps supported these multicast IPTV streams. So, I had to:

Step 3: Install agrrh-udpxy.

I installed this as a docker image on a syntology NAS.

The following settings worked for me: image: agrrh/udpxy:latest network: host cmd: -v -T -p 4022

You can then go to your nas drive on port 4022, URL /status. For example mine is http://nasdrive:4022/status and I see:

udpxy

Step 4: Create your own playlist that forwards to your udprxy instance instead of the original network stream

You can do this by opening up the m3u file and relacing the URLs with the link to your local instance. For example, mine is here: https://gist.github.com/evolvedlight/e9b2a0145840ba5385478a3075aa815a

Step 5: Open this in a IPTV app.

Personally I used one called "m3u player" but there are many out there. You'll then also be able to go to your udpxy status page and see the running stream there.

',19),h=[p];function u(l,d,c,m,y,f){return a(),t("div",null,h)}const b=e(s,[["render",u]]);export{w as __pageData,b as default}; diff --git a/assets/posts_init7-tv7.md.63fe95a3.lean.js b/assets/posts_init7-tv7.md.63fe95a3.lean.js new file mode 100644 index 0000000..94dab14 --- /dev/null +++ b/assets/posts_init7-tv7.md.63fe95a3.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as a,a as r}from"./chunks/framework.f55bdc3d.js";const o="/images/network-iptv.png",n="/images/vlc-tv7.png",i="/images/udpxy.png",w=JSON.parse('{"title":"TV7 (IPTV from Init7) with Dream Machine and Unity Dream machine","description":"","frontmatter":{"title":"TV7 (IPTV from Init7) with Dream Machine and Unity Dream machine","date":"2023-08-04T00:00:00.000Z","author":"Stephen Brown"},"headers":[],"relativePath":"posts/init7-tv7.md","filePath":"posts/init7-tv7.md"}'),s={name:"posts/init7-tv7.md"},p=r("",19),h=[p];function u(l,d,c,m,y,f){return a(),t("div",null,h)}const b=e(s,[["render",u]]);export{w as __pageData,b as default}; diff --git a/assets/posts_scaling-masstransit-jobs.md.6bbd5c48.js b/assets/posts_scaling-masstransit-jobs.md.6bbd5c48.js new file mode 100644 index 0000000..eb54e4e --- /dev/null +++ b/assets/posts_scaling-masstransit-jobs.md.6bbd5c48.js @@ -0,0 +1,93 @@ +import{_ as s,c as a,o as n,a as l}from"./chunks/framework.f55bdc3d.js";const A=JSON.parse('{"title":"Running MassTransit queues with KEDA","description":"","frontmatter":{"title":"Running MassTransit queues with KEDA","date":"2023-09-04T00:00:00.000Z","author":"Stephen Brown"},"headers":[],"relativePath":"posts/scaling-masstransit-jobs.md","filePath":"posts/scaling-masstransit-jobs.md"}'),o={name:"posts/scaling-masstransit-jobs.md"},p=l(`

We have an interesting challenge in some of our microservices - we run lots and lots of microservices, all using a relatively small amount of memory, but the usage patterns may be unusual. All these microservices talk MassTransit - some also have external triggers, API endpoints or scheduled jobs.

They run like this:

  • Most are processing constantly lots of small messages. These are no problem.
  • Some process only a few messages a day, but we have to keep the service running all day. This is annoying.
  • Some process only a few messages per day, but those small messages take a lot of CPU and memory. For example they may need to deal with files in memory in formats that don't yet easily support iteratively processing the file. This is a big problem for us as the services need to be sized to handle these large requirement, but are actually only running for a few minutes a day with this load.

This post will show one approach to fix the third problem. The same tooling (KEDA) can also be used to improve the other situations but that's for another blog post. What we'll do to address the final problem is to use KEDA Job based scaling.

First, we have the publisher. This could be anything and in our example it's normally from a cron job. The send will look like this, all normal MassTransit code:

c#
var endpoint = app.Services.GetRequiredService<IPublishEndpoint>();
+endpoint.Publish(new ProcessVeryLargeFileTrigger(fileName)

ProcessVeryLargeFileTrigger is defined in an assembly that both producer and consumer can use.

Now, we need an entrypoint that allows us to read one message from the MassTransit queue (rabbit is used here), give it to MassTransit, then exit.

First, our entrypoint needs to have a masstransit consumer, in memory.

c#
# Setup 
+HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
+builder.Services.AddMassTransit(x =>
+{
+    x.AddConsumer<ProcessVeryLargeFileConsumer>();
+
+    x.UsingInMemory();
+    x.AddConfigureEndpointsCallback((_, cfg) =>
+    {
+        # Optional, we like this format
+        cfg.UseRawJsonSerializer(isDefault: true);
+    });
+});

Next, we need to connect and bind to a rabbit queue:

c#
ConnectionFactory factory = new ConnectionFactory
+{
+  UserName = "<get this from appsettings>",
+  Password = "<get this from appsettings>",
+  VirtualHost = "/",
+  HostName = "example.default"
+};
+
+IConnection conn = factory.CreateConnection();
+IModel channel = conn.CreateModel();
+
+# Your exchange and queues are likely different
+var exchangeName = "LongRunningJobs";
+var queueName = "LongRunningJobs";
+var routingKey = "LongRunningJobs";
+channel.ExchangeDeclare(exchangeName, ExchangeType.Direct);
+channel.QueueDeclare(queueName, false, false, false, null);
+channel.QueueBind(queueName, exchangeName, routingKey, null);

After this, we need to try and get a single message from Rabbit. If there's nothing here, we exit.

c#
BasicGetResult message = channel.BasicGet("LongRunningJobs", false);
+if (message == null) {
+  // No message to pickup
+  return;
+}

We can then build our normal app and inject the RabbitMQ message into it:

c#
var app = builder.Build();
+await app.StartAsync();
+using var scope = app.Services.CreateScope();
+var ep = scope.ServiceProvider.GetRequiredService<ProcessVeryLargeFileConsumer<SubmitOrderConsumer>>();

Next, inject the message:

c#
try
+{
+    var messageBytes = message.Body.ToArray()
+    var headers = result.BasicProperties.Headers.AsReadOnly();
+    await ep.Dispatch(result.Body.ToArray(), headers, CancellationToken.None);
+    channel.BasicAck(result.DeliveryTag, false);
+} 
+catch 
+{
+    channel.BasicNack(result.DeliveryTag, false, true);
+}

Finally, we can cleanup:

c#
channel.Close();
+conn.Close();
+await app.StopAsync();

Of course, this is just the basics, your real solution would include logging and some other error handling (poison messages and so on should be handled, otherwise it will continue processing bad messages over and over).

This post now assumes you can build this into a docker image and push it to a registry you use. For this example, we'll assume the image name is example-long:latest

At this point, you have a working application that will pick up a single message, handle it, and finish. We need some way to trigger this to happen for each single message in the queue - for this comes KEDA. First, you need a secret containing the RabbitMQ connection string:

yaml
apiVersion: v1
+kind: Secret
+metadata:
+  name: keda-rabbitmq-secret
+  namespace: limited
+data:
+  host: >-
+    <your base 64 encoded connection string>
+type: Opaque

You need a trigger authentication:

yaml
apiVersion: keda.sh/v1alpha1
+kind: TriggerAuthentication
+metadata:
+  name: keda-trigger-auth-rabbitmq-conn
+  namespace: limited
+spec:
+  secretTargetRef:
+    - parameter: host
+      name: keda-rabbitmq-secret
+      key: host

And the important bit: you need a ScaledJob spec that runs your docker image

yaml
apiVersion: keda.sh/v1alpha1
+kind: ScaledJob
+metadata:
+  name: rabbitmq-consumer
+  namespace: limited
+spec:
+  jobTargetRef:
+    template:
+      spec:
+        containers:
+        - name: example-long
+          image: example-long:latest
+          imagePullPolicy: Never
+        restartPolicy: Never
+    backoffLimit: 4
+  pollingInterval: 2             # Optional. Default: 30 seconds
+  maxReplicaCount: 30             # Optional. Default: 100
+  successfulJobsHistoryLimit: 30   # Optional. Default: 100. How many completed jobs should be kept.
+  failedJobsHistoryLimit: 20       # Optional. Default: 100. How many failed jobs should be kept.
+  triggers:
+  - type: rabbitmq
+    metadata:
+      queueName: LongRunningJobs
+      queueLength: '1'
+    authenticationRef:
+      name: keda-trigger-auth-rabbitmq-conn

Now, when you put a message into the queue, it'll be processed as a MassTransit message in a Kubernetes Job, allowing you to run your normal worker pods with lower memory and CPU requirements.

You also have a few other benefits:

  • Deploys of the service don't kill a long running job
  • Jobs can be scheduled on other nodes
  • Jobs can be scheduled as lower priority
`,31),e=[p];function t(c,r,y,D,F,i){return n(),a("div",null,e)}const u=s(o,[["render",t]]);export{A as __pageData,u as default}; diff --git a/assets/posts_scaling-masstransit-jobs.md.6bbd5c48.lean.js b/assets/posts_scaling-masstransit-jobs.md.6bbd5c48.lean.js new file mode 100644 index 0000000..2b8c94a --- /dev/null +++ b/assets/posts_scaling-masstransit-jobs.md.6bbd5c48.lean.js @@ -0,0 +1 @@ +import{_ as s,c as a,o as n,a as l}from"./chunks/framework.f55bdc3d.js";const A=JSON.parse('{"title":"Running MassTransit queues with KEDA","description":"","frontmatter":{"title":"Running MassTransit queues with KEDA","date":"2023-09-04T00:00:00.000Z","author":"Stephen Brown"},"headers":[],"relativePath":"posts/scaling-masstransit-jobs.md","filePath":"posts/scaling-masstransit-jobs.md"}'),o={name:"posts/scaling-masstransit-jobs.md"},p=l("",31),e=[p];function t(c,r,y,D,F,i){return n(),a("div",null,e)}const u=s(o,[["render",t]]);export{A as __pageData,u as default}; diff --git a/assets/style.07528792.css b/assets/style.07528792.css new file mode 100644 index 0000000..f3ac18c --- /dev/null +++ b/assets/style.07528792.css @@ -0,0 +1 @@ +*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where([class~="lead"]):not(:where([class~="not-prose"] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose :where(a):not(:where([class~="not-prose"] *)){color:var(--tw-prose-links);text-decoration:underline;font-weight:500}.prose :where(strong):not(:where([class~="not-prose"] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(thead th strong):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(ol):not(:where([class~="not-prose"] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol[type="A"]):not(:where([class~="not-prose"] *)){list-style-type:upper-alpha}.prose :where(ol[type="a"]):not(:where([class~="not-prose"] *)){list-style-type:lower-alpha}.prose :where(ol[type="A" s]):not(:where([class~="not-prose"] *)){list-style-type:upper-alpha}.prose :where(ol[type="a" s]):not(:where([class~="not-prose"] *)){list-style-type:lower-alpha}.prose :where(ol[type="I"]):not(:where([class~="not-prose"] *)){list-style-type:upper-roman}.prose :where(ol[type="i"]):not(:where([class~="not-prose"] *)){list-style-type:lower-roman}.prose :where(ol[type="I" s]):not(:where([class~="not-prose"] *)){list-style-type:upper-roman}.prose :where(ol[type="i" s]):not(:where([class~="not-prose"] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~="not-prose"] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~="not-prose"] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol > li):not(:where([class~="not-prose"] *))::marker{font-weight:400;color:var(--tw-prose-counters)}.prose :where(ul > li):not(:where([class~="not-prose"] *))::marker{color:var(--tw-prose-bullets)}.prose :where(hr):not(:where([class~="not-prose"] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~="not-prose"] *)){font-weight:500;font-style:italic;color:var(--tw-prose-quotes);border-left-width:.25rem;border-left-color:var(--tw-prose-quote-borders);quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose :where(blockquote p:first-of-type):not(:where([class~="not-prose"] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~="not-prose"] *)):after{content:close-quote}.prose :where(h1):not(:where([class~="not-prose"] *)){color:var(--tw-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose :where(h1 strong):not(:where([class~="not-prose"] *)){font-weight:900;color:inherit}.prose :where(h2):not(:where([class~="not-prose"] *)){color:var(--tw-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose :where(h2 strong):not(:where([class~="not-prose"] *)){font-weight:800;color:inherit}.prose :where(h3):not(:where([class~="not-prose"] *)){color:var(--tw-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose :where(h3 strong):not(:where([class~="not-prose"] *)){font-weight:700;color:inherit}.prose :where(h4):not(:where([class~="not-prose"] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose :where(h4 strong):not(:where([class~="not-prose"] *)){font-weight:700;color:inherit}.prose :where(img):not(:where([class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure > *):not(:where([class~="not-prose"] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~="not-prose"] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose :where(code):not(:where([class~="not-prose"] *)){color:var(--tw-prose-code);font-weight:600;font-size:.875em}.prose :where(code):not(:where([class~="not-prose"] *)):before{content:"`"}.prose :where(code):not(:where([class~="not-prose"] *)):after{content:"`"}.prose :where(a code):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(h1 code):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(h2 code):not(:where([class~="not-prose"] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~="not-prose"] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(blockquote code):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(thead th code):not(:where([class~="not-prose"] *)){color:inherit}.prose :where(pre):not(:where([class~="not-prose"] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding:.8571429em 1.1428571em}.prose :where(pre code):not(:where([class~="not-prose"] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose :where(pre code):not(:where([class~="not-prose"] *)):before{content:none}.prose :where(pre code):not(:where([class~="not-prose"] *)):after{content:none}.prose :where(table):not(:where([class~="not-prose"] *)){width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose :where(thead):not(:where([class~="not-prose"] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~="not-prose"] *)){color:var(--tw-prose-headings);font-weight:600;vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose :where(tbody tr):not(:where([class~="not-prose"] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~="not-prose"] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~="not-prose"] *)){vertical-align:baseline}.prose :where(tfoot):not(:where([class~="not-prose"] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.prose :where(tfoot td):not(:where([class~="not-prose"] *)){vertical-align:top}.prose{--tw-prose-body: #374151;--tw-prose-headings: #111827;--tw-prose-lead: #4b5563;--tw-prose-links: #111827;--tw-prose-bold: #111827;--tw-prose-counters: #6b7280;--tw-prose-bullets: #d1d5db;--tw-prose-hr: #e5e7eb;--tw-prose-quotes: #111827;--tw-prose-quote-borders: #e5e7eb;--tw-prose-captions: #6b7280;--tw-prose-code: #111827;--tw-prose-pre-code: #e5e7eb;--tw-prose-pre-bg: #1f2937;--tw-prose-th-borders: #d1d5db;--tw-prose-td-borders: #e5e7eb;--tw-prose-invert-body: #d1d5db;--tw-prose-invert-headings: #fff;--tw-prose-invert-lead: #9ca3af;--tw-prose-invert-links: #fff;--tw-prose-invert-bold: #fff;--tw-prose-invert-counters: #9ca3af;--tw-prose-invert-bullets: #4b5563;--tw-prose-invert-hr: #374151;--tw-prose-invert-quotes: #f3f4f6;--tw-prose-invert-quote-borders: #374151;--tw-prose-invert-captions: #9ca3af;--tw-prose-invert-code: #fff;--tw-prose-invert-pre-code: #d1d5db;--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);--tw-prose-invert-th-borders: #4b5563;--tw-prose-invert-td-borders: #374151;font-size:1rem;line-height:1.75}.prose :where(p):not(:where([class~="not-prose"] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(video):not(:where([class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}.prose :where(figure):not(:where([class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}.prose :where(li):not(:where([class~="not-prose"] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol > li):not(:where([class~="not-prose"] *)){padding-left:.375em}.prose :where(ul > li):not(:where([class~="not-prose"] *)){padding-left:.375em}.prose :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.25em}.prose :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.25em}.prose :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.25em}.prose :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~="not-prose"] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(hr + *):not(:where([class~="not-prose"] *)){margin-top:0}.prose :where(h2 + *):not(:where([class~="not-prose"] *)){margin-top:0}.prose :where(h3 + *):not(:where([class~="not-prose"] *)){margin-top:0}.prose :where(h4 + *):not(:where([class~="not-prose"] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~="not-prose"] *)){padding-left:0}.prose :where(thead th:last-child):not(:where([class~="not-prose"] *)){padding-right:0}.prose :where(tbody td,tfoot td):not(:where([class~="not-prose"] *)){padding:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~="not-prose"] *)){padding-left:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~="not-prose"] *)){padding-right:0}.prose :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.prose-sm :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.5714286em;margin-bottom:.5714286em}.prose-sm :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.1428571em}.prose-sm :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.1428571em}.prose-sm :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.1428571em}.prose-sm :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose-sm :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.prose-base :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.75em;margin-bottom:.75em}.prose-base :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.25em}.prose-base :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.25em}.prose-base :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.25em}.prose-base :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.25em}.prose-base :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose-base :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.prose-lg :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.8888889em;margin-bottom:.8888889em}.prose-lg :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.3333333em}.prose-lg :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.3333333em}.prose-lg :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.3333333em}.prose-lg :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose-lg :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.prose-xl :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.8em;margin-bottom:.8em}.prose-xl :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.2em}.prose-xl :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.2em}.prose-xl :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.2em}.prose-xl :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.2em}.prose-xl :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose-xl :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.prose-2xl :where(.prose > ul > li p):not(:where([class~="not-prose"] *)){margin-top:.8333333em;margin-bottom:.8333333em}.prose-2xl :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.3333333em}.prose-2xl :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.3333333em}.prose-2xl :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"] *)){margin-top:1.3333333em}.prose-2xl :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"] *)){margin-bottom:1.3333333em}.prose-2xl :where(.prose > :first-child):not(:where([class~="not-prose"] *)){margin-top:0}.prose-2xl :where(.prose > :last-child):not(:where([class~="not-prose"] *)){margin-bottom:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.mx-auto{margin-left:auto;margin-right:auto}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.inline-block{display:inline-block}.flex{display:flex}.hidden{display:none}.h-10{height:2.5rem}.w-10{width:2.5rem}.max-w-none{max-width:none}.max-w-3xl{max-width:48rem}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(2rem * var(--tw-space-x-reverse));margin-left:calc(2rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(229 231 235 / var(--tw-divide-opacity))}.whitespace-nowrap{white-space:nowrap}.rounded-full{border-radius:9999px}.py-8{padding-top:2rem;padding-bottom:2rem}.py-12{padding-top:3rem;padding-bottom:3rem}.px-4{padding-left:1rem;padding-right:1rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.pt-6{padding-top:1.5rem}.pb-16{padding-bottom:4rem}.pt-10{padding-top:2.5rem}.pb-8{padding-bottom:2rem}.pt-8{padding-top:2rem}.pb-10{padding-bottom:2.5rem}.text-center{text-align:center}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-extrabold{font-weight:800}.font-medium{font-weight:500}.font-bold{font-weight:700}.uppercase{text-transform:uppercase}.leading-9{line-height:2.25rem}.leading-5{line-height:1.25rem}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.leading-8{line-height:2rem}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}:root{--c-brand: #3eaf7c;--c-brand-light: #4abf8a}nav img{vertical-align:middle}p img{margin:0 auto}.prose hr{border-top:1px solid #e5e7eb}.link{color:var(--c-brand)}.link:hover{color:var(--c-brand-light)}.header-anchor{display:none}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#999}.token.punctuation{color:#ccc}.token.tag,.token.attr-name,.token.namespace,.token.deleted{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.number,.token.function{color:#f08d49}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd}.token.string,.token.char,.token.attr-value,.token.regex,.token.variable{color:#7ec699}.token.operator,.token.entity,.token.url{color:#67cdcc}.token.important,.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:#67cdcc}button.copy{display:none}span.lang{position:absolute;right:.5em;font-size:.75em;color:#999;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.custom-block{margin:28px 0;padding:0 24px 2px;border-radius:8px;overflow-x:auto;position:relative;font-size:14px;line-height:1.3;font-weight:500;color:#444;background-color:#f9f9f9}.custom-block .custom-block-title{margin-bottom:8px;font-size:15px;font-weight:700}.custom-block.tip{border:1px solid #42b883}.custom-block.tip:before{color:#42b883}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}@media (prefers-color-scheme: dark){.dark\:prose-invert{--tw-prose-body: var(--tw-prose-invert-body);--tw-prose-headings: var(--tw-prose-invert-headings);--tw-prose-lead: var(--tw-prose-invert-lead);--tw-prose-links: var(--tw-prose-invert-links);--tw-prose-bold: var(--tw-prose-invert-bold);--tw-prose-counters: var(--tw-prose-invert-counters);--tw-prose-bullets: var(--tw-prose-invert-bullets);--tw-prose-hr: var(--tw-prose-invert-hr);--tw-prose-quotes: var(--tw-prose-invert-quotes);--tw-prose-quote-borders: var(--tw-prose-invert-quote-borders);--tw-prose-captions: var(--tw-prose-invert-captions);--tw-prose-code: var(--tw-prose-invert-code);--tw-prose-pre-code: var(--tw-prose-invert-pre-code);--tw-prose-pre-bg: var(--tw-prose-invert-pre-bg);--tw-prose-th-borders: var(--tw-prose-invert-th-borders);--tw-prose-td-borders: var(--tw-prose-invert-td-borders)}.dark\:divide-slate-200\/5>:not([hidden])~:not([hidden]){border-color:#e2e8f00d}.dark\:bg-slate-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.dark\:text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.dark\:text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity))}.dark\:hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity))}}@media (min-width: 640px){.sm\:inline{display:inline}.sm\:space-x-12>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(3rem * var(--tw-space-x-reverse));margin-left:calc(3rem * calc(1 - var(--tw-space-x-reverse)))}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:leading-10{line-height:2.5rem}}@media (min-width: 768px){.md\:inline{display:inline}.md\:space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.md\:text-5xl{font-size:3rem;line-height:1}.md\:text-6xl{font-size:3.75rem;line-height:1}}@media (min-width: 1280px){.xl\:col-span-3{grid-column:span 3 / span 3}.xl\:col-start-1{grid-column-start:1}.xl\:row-span-2{grid-row:span 2 / span 2}.xl\:row-start-2{grid-row-start:2}.xl\:block{display:block}.xl\:grid{display:grid}.xl\:max-w-5xl{max-width:64rem}.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:items-baseline{align-items:baseline}.xl\:gap-x-10{column-gap:2.5rem}.xl\:space-x-0>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(0px * var(--tw-space-x-reverse));margin-left:calc(0px * calc(1 - var(--tw-space-x-reverse)))}.xl\:space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem * var(--tw-space-y-reverse))}.xl\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.xl\:divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.xl\:divide-y-0>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(0px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(0px * var(--tw-divide-y-reverse))}.xl\:divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(229 231 235 / var(--tw-divide-opacity))}.xl\:border-b{border-bottom-width:1px}.xl\:border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity))}.xl\:px-0{padding-left:0;padding-right:0}.xl\:pb-10{padding-bottom:2.5rem}.xl\:pb-20{padding-bottom:5rem}.xl\:pb-0{padding-bottom:0}.xl\:pt-11{padding-top:2.75rem}}@media (prefers-color-scheme: dark){@media (min-width: 1280px){.dark\:xl\:divide-slate-200\/5>:not([hidden])~:not([hidden]){border-color:#e2e8f00d}.dark\:xl\:border-slate-200\/5{border-color:#e2e8f00d}}} diff --git a/feed.rss b/feed.rss new file mode 100644 index 0000000..c53e7b1 --- /dev/null +++ b/feed.rss @@ -0,0 +1,332 @@ + + + + Steve@brown.bg + https://brown.bg + Steve's blog + Fri, 22 Sep 2023 21:13:34 GMT + https://validator.w3.org/feed/docs/rss2.html + https://github.com/jpmonette/feed + en + + Steve@brown.bg + https://brown.bg/logo.webp + https://brown.bg + + Copyright (c) 2021-present, Steve Brown + + <![CDATA[Running MassTransit queues with KEDA]]> + https://brown.bg/posts/scaling-masstransit-jobs + https://brown.bg/posts/scaling-masstransit-jobs + Mon, 04 Sep 2023 00:00:00 GMT + We have an interesting challenge in some of our microservices - we run lots and lots of microservices, all using a relatively small amount of memory, but the usage patterns may be unusual. +All these microservices talk MassTransit - some also have external triggers, API endpoints or scheduled jobs.

+]]>
+ We have an interesting challenge in some of our microservices - we run lots and lots of microservices, all using a relatively small amount of memory, but the usage patterns may be unusual. +All these microservices talk MassTransit - some also have external triggers, API endpoints or scheduled jobs. +

They run like this:

+
    +
  • Most are processing constantly lots of small messages. These are no problem.
  • +
  • Some process only a few messages a day, but we have to keep the service running all day. This is annoying.
  • +
  • Some process only a few messages per day, but those small messages take a lot of CPU and memory. For example they may need to deal with files in memory in formats that don't yet easily support iteratively processing the file. This is a big problem for us as the services need to be sized to handle these large requirement, but are actually only running for a few minutes a day with this load.
  • +
+

This post will show one approach to fix the third problem. The same tooling (KEDA) can also be used to improve the other situations but that's for another blog post. +What we'll do to address the final problem is to use KEDA Job based scaling.

+

First, we have the publisher. This could be anything and in our example it's normally from a cron job. The send will look like this, all normal MassTransit code:

+
c#
var endpoint = app.Services.GetRequiredService<IPublishEndpoint>();
+endpoint.Publish(new ProcessVeryLargeFileTrigger(fileName)
+

ProcessVeryLargeFileTrigger is defined in an assembly that both producer and consumer can use.

+

Now, we need an entrypoint that allows us to read one message from the MassTransit queue (rabbit is used here), give it to MassTransit, then exit.

+

First, our entrypoint needs to have a masstransit consumer, in memory.

+
c#
# Setup 
+HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
+builder.Services.AddMassTransit(x =>
+{
+    x.AddConsumer<ProcessVeryLargeFileConsumer>();
+
+    x.UsingInMemory();
+    x.AddConfigureEndpointsCallback((_, cfg) =>
+    {
+        # Optional, we like this format
+        cfg.UseRawJsonSerializer(isDefault: true);
+    });
+});
+

Next, we need to connect and bind to a rabbit queue:

+
c#
ConnectionFactory factory = new ConnectionFactory
+{
+  UserName = "<get this from appsettings>",
+  Password = "<get this from appsettings>",
+  VirtualHost = "/",
+  HostName = "example.default"
+};
+
+IConnection conn = factory.CreateConnection();
+IModel channel = conn.CreateModel();
+
+# Your exchange and queues are likely different
+var exchangeName = "LongRunningJobs";
+var queueName = "LongRunningJobs";
+var routingKey = "LongRunningJobs";
+channel.ExchangeDeclare(exchangeName, ExchangeType.Direct);
+channel.QueueDeclare(queueName, false, false, false, null);
+channel.QueueBind(queueName, exchangeName, routingKey, null);
+

After this, we need to try and get a single message from Rabbit. If there's nothing here, we exit.

+
c#
BasicGetResult message = channel.BasicGet("LongRunningJobs", false);
+if (message == null) {
+  // No message to pickup
+  return;
+}
+

We can then build our normal app and inject the RabbitMQ message into it:

+
c#
var app = builder.Build();
+await app.StartAsync();
+using var scope = app.Services.CreateScope();
+var ep = scope.ServiceProvider.GetRequiredService<ProcessVeryLargeFileConsumer<SubmitOrderConsumer>>();
+

Next, inject the message:

+
c#
try
+{
+    var messageBytes = message.Body.ToArray()
+    var headers = result.BasicProperties.Headers.AsReadOnly();
+    await ep.Dispatch(result.Body.ToArray(), headers, CancellationToken.None);
+    channel.BasicAck(result.DeliveryTag, false);
+} 
+catch 
+{
+    channel.BasicNack(result.DeliveryTag, false, true);
+}
+

Finally, we can cleanup:

+
c#
channel.Close();
+conn.Close();
+await app.StopAsync();
+

Of course, this is just the basics, your real solution would include logging and some other error handling (poison messages and so on should be handled, otherwise it will continue processing bad messages over and over).

+

This post now assumes you can build this into a docker image and push it to a registry you use. For this example, we'll assume the image name is example-long:latest

+

At this point, you have a working application that will pick up a single message, handle it, and finish. We need some way to trigger this to happen for each single message in the queue - for this comes KEDA. +First, you need a secret containing the RabbitMQ connection string:

+
yaml
apiVersion: v1
+kind: Secret
+metadata:
+  name: keda-rabbitmq-secret
+  namespace: limited
+data:
+  host: >-
+    <your base 64 encoded connection string>
+type: Opaque
+

You need a trigger authentication:

+
yaml
apiVersion: keda.sh/v1alpha1
+kind: TriggerAuthentication
+metadata:
+  name: keda-trigger-auth-rabbitmq-conn
+  namespace: limited
+spec:
+  secretTargetRef:
+    - parameter: host
+      name: keda-rabbitmq-secret
+      key: host
+

And the important bit: you need a ScaledJob spec that runs your docker image

+
yaml
apiVersion: keda.sh/v1alpha1
+kind: ScaledJob
+metadata:
+  name: rabbitmq-consumer
+  namespace: limited
+spec:
+  jobTargetRef:
+    template:
+      spec:
+        containers:
+        - name: example-long
+          image: example-long:latest
+          imagePullPolicy: Never
+        restartPolicy: Never
+    backoffLimit: 4
+  pollingInterval: 2             # Optional. Default: 30 seconds
+  maxReplicaCount: 30             # Optional. Default: 100
+  successfulJobsHistoryLimit: 30   # Optional. Default: 100. How many completed jobs should be kept.
+  failedJobsHistoryLimit: 20       # Optional. Default: 100. How many failed jobs should be kept.
+  triggers:
+  - type: rabbitmq
+    metadata:
+      queueName: LongRunningJobs
+      queueLength: '1'
+    authenticationRef:
+      name: keda-trigger-auth-rabbitmq-conn
+

Now, when you put a message into the queue, it'll be processed as a MassTransit message in a Kubernetes Job, allowing you to run your normal worker pods with lower memory and CPU requirements.

+

You also have a few other benefits:

+
    +
  • Deploys of the service don't kill a long running job
  • +
  • Jobs can be scheduled on other nodes
  • +
  • Jobs can be scheduled as lower priority
  • +
+]]>
+
+ + <![CDATA[Creating Kerberos secured connections from Dotnet on Linux]]> + https://brown.bg/posts/dotnet-kerberos + https://brown.bg/posts/dotnet-kerberos + Sat, 05 Aug 2023 00:00:00 GMT + For the last few years at my workplace we've been using Kerberos authentication to connect to the on-prem SQL Server databases. On Windows this just worked like magic, as the services ran as a Windows domain user and so could connect to the database without any additional configuration. However, when moving to Kubernetes and Linux systems, this didn't work out of the box.

+]]>
+ For the last few years at my workplace we've been using Kerberos authentication to connect to the on-prem SQL Server databases. On Windows this just worked like magic, as the services ran as a Windows domain user and so could connect to the database without any additional configuration. However, when moving to Kubernetes and Linux systems, this didn't work out of the box. +

Our initial solution was following this red hat blog post which details how to setup a sidecar which keeps a kerberos token valid. This worked for a number of years but did have some problems:

+
    +
  • We ran 100+ containers and each needed a sidecar - this cost a few gigabytes of memory and some CPU allowance
  • +
  • When the token was being refreshed, it was not available for a few milliseconds
  • +
  • It was extra hassle and created slightly larger deployments and more complex Deployment configs.
  • +
+

When investigating a seperate issue (dotnet 7 caused some transient kerberos faults) we realised that actually the whole sidecar approach was completely unneccessary! This post will give the minimum you actually need to call Keberos services from Linux

+
    +
  1. You'll need to add the keberos tools to your docker image. Maybe you have a base image you share, maybe not. For alpine images:
  2. +
+

RUN apk add --no-cache krb5

+
    +
  1. You'll need to have a krb5 configuration that points to your domain controller. Microsoft has an example
  2. +
+

For example, ours looks like this:

+
[logging]
+default
+
+[libdefaults]
+...standard things here
+default_client_keytab_name=/krb5/client.keytab
+default_keytab_name=/krb5/krb5.keytab
+default_ccache_name=FILE:/dev/shm/ccache
+default_realm = EXAMPLE.COM
+
+[realms]
+EXAMPLE.COM = {
+    kdc = ADS.EXAMPLE.COM:88
+}
+

You can then add this to the docker image like

+
RUN mkdir /krb5 && mkdir /customkrb5
+COPY krb5.conf /customkrb5/krb5.conf
+ENV KRB5_CONFIG=/customkrb5/krb5.conf
+

We put it in a custom folder as some container systems have the limitation that mounting a directory overrides all files in that directory (we'll do this later)

+
    +
  1. +

    You then need a keytab added to the image. You will probably want this done at runtime. Get a keytab (maybe off your AD system administrators, maybe you create it yourself with ktutil) and make sure this ends up at /krb5/client.keytab. We use kubernetes secrets and volume mounts for this

    +
  2. +
  3. +

    Connect to the database using Integrated Security=true. This should now just work. You can check by calling klist inside the container to see that it has a ticket

    +
  4. +
+]]>
+
+ + <![CDATA[TV7 (IPTV from Init7) with Dream Machine and Unity Dream machine]]> + https://brown.bg/posts/init7-tv7 + https://brown.bg/posts/init7-tv7 + Fri, 04 Aug 2023 00:00:00 GMT + If you're an Init7 TV customer and you want to use the Unify Dream Machine as your IPTV receiver, I figured it out for you! Here's a step-by-step guide to help you get started.

+]]>
+ If you're an Init7 TV customer and you want to use the Unify Dream Machine as your IPTV receiver, I figured it out for you! Here's a step-by-step guide to help you get started. +

Step 1: Update to the Newest Dream Machine

+

Before you begin, make sure your Unify Dream Machine is updated to the latest firmware version. You can check for updates by logging into the Unify Dream Machine web interface and navigating to the "Settings" page. +Your version needs to be higher than "UniFi Network Application 7.4.156".

+

Step 2: Enable the feature

+

You now have a new setup that you can do on the Network application:

+

udpxy

+

You can also see the basic unifi docs

+

You should now already be able to watch TV7 though VLC using this link: https://www.init7.net/en/support/faq/TV-andere-Geraete/

+

udpxy

+

However, I wanted to watch on a Samsung TV and none of the apps supported these multicast IPTV streams. So, I had to:

+

Step 3: Install agrrh-udpxy.

+

I installed this as a docker image on a syntology NAS.

+

The following settings worked for me: +image: agrrh/udpxy:latest +network: host +cmd: -v -T -p 4022

+

You can then go to your nas drive on port 4022, URL /status. For example mine is http://nasdrive:4022/status and I see:

+

udpxy

+

Step 4: Create your own playlist that forwards to your udprxy instance instead of the original network stream

+

You can do this by opening up the m3u file and relacing the URLs with the link to your local instance. For example, mine is here: +https://gist.github.com/evolvedlight/e9b2a0145840ba5385478a3075aa815a

+

Step 5: Open this in a IPTV app.

+

Personally I used one called "m3u player" but there are many out there. You'll then also be able to go to your udpxy status page and see the running stream there.

+]]>
+
+ + <![CDATA[Dockerless docker builds in Github Actions with Dotnet 7]]> + https://brown.bg/posts/github-actions-net7-docker + https://brown.bg/posts/github-actions-net7-docker + Fri, 14 Oct 2022 00:00:00 GMT + How to setup a github build action that uses the dockerless build in .Net 7

+]]>
+ How to setup a github build action that uses the dockerless build in .Net 7

+
+

Background

+

For me, one of the slowest parts of the fast build-test-deploy cycle was always waiting for docker builds to run. Especially with the practise of docker-in-docker builds which without special magic wouldn't cache anything, it could often take several minutes to create a docker application. +.Net 7 promises to offer an alternative to this for simple apps - let's take a dive in.

+

Setup

+

You'll need .Net 7 for this, which isn't currently released at time of writing. However, in theory you can take a .Net 6 application and use the .Net 7 SDK to build it already, however YMMV.

+

With this installed, let's create a new app and get started! We're following along with https://devblogs.microsoft.com/dotnet/announcing-builtin-container-support-for-the-dotnet-sdk/ but with some changes

+

Basic app

+
bash
dotnet new webapi -o dockerless-docker
+cd dockerless-docker
+

We also need to do a couple of things: because .Net 7 isn't out yet, we need a globals file to tell the github actions to use it

+

That gives us our basic app - let's add a github actions build for it now, by adding the following:

+
json
{
+    "sdk": {
+        "version": "7.0.100-rc.2.22477.23"
+    }
+}
+

We'll also need to add a reference to this tool:

+
bash
dotnet add package Microsoft.NET.Build.Containers
+

Now, add a github actions to .github/workflows/whatever.yml

+
yaml
name: Create and publish a Docker image
+
+on:
+  push:
+    branches: [ "*" ]
+  pull_request:
+    branches: [ "main" ]
+
+
+env:
+  REGISTRY: ghcr.io
+  IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+  build-and-push-image:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+
+    steps:
+      - uses: actions/checkout@v3
+      - name: Setup .NET SDK
+        uses: actions/setup-dotnet@v2
+      # Package the app into a linux-x64 container based on the dotnet/aspnet image
+      - name: Publish
+        run: dotnet publish --os linux --arch x64 --configuration Release -p:PublishProfile=DefaultContainer
+      - name: Checkout repository
+        uses: actions/checkout@v3
+      - name: Login to GitHub Container Registry
+        uses: docker/login-action@v2
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      - name: Tag built container with Github thing
+        run: |
+          docker tag dockerless-docker:1.0.0 ghcr.io/evolvedlight/dockerless-docker:1.0.0
+      - name: Push built container to Github Package Repo
+        run: |
+          docker push ghcr.io/evolvedlight/dockerless-docker:1.0.0
+

Here's now where the weird bit comes - on github the process to get a repository seems rather backward. You need to assign permissions for the repo to write images, but there's no way to do that until you've pushed an image manually!

+

So let's go around the hoops - create the docker image locally, tag it, login to the Github repository and upload it:

+
dotnet publish --os linux --arch x64 --configuration Release -p:PublishProfile=DefaultContainer
+docker tag dockerless-docker:1.0.0 ghcr.io/evolvedlight/dockerless-docker:1.0.0
+docker login ghcr.io -u <your username>
+

Enter a Personal access token that you can create on your github profile page, and finally push the image:

+
docker push ghcr.io/evolvedlight/dockerless-docker:1.0.0
+

Now go to your packages page, for me that's https://github.com/evolvedlight?tab=packages. +Click on the new package page that was created, and then go to the settings for it (for me https://github.com/users/evolvedlight/packages/container/dockerless-docker/settings)

+

Under "Manage Actions access" add your repository with access.

+

In the end it'll look like this:

+

github permissions

+

Finally, rerun the action and it should work.

+

In the next blob post we'll look at fixing the above docker build to push with the right version numbers and tags

+]]>
+
+
+
\ No newline at end of file diff --git a/hashmap.json b/hashmap.json new file mode 100644 index 0000000..fd8d871 --- /dev/null +++ b/hashmap.json @@ -0,0 +1 @@ +{"posts_dotnet-kerberos.md":"e7e9094f","index.md":"627b3dc9","posts_github-actions-net7-docker.md":"8901ce83","posts_scaling-masstransit-jobs.md":"6bbd5c48","posts_init7-tv7.md":"63fe95a3"} diff --git a/public/images/github_actions_permission.png b/images/github_actions_permission.png similarity index 100% rename from public/images/github_actions_permission.png rename to images/github_actions_permission.png diff --git a/public/images/network-iptv.png b/images/network-iptv.png similarity index 100% rename from public/images/network-iptv.png rename to images/network-iptv.png diff --git a/public/images/udpxy.png b/images/udpxy.png similarity index 100% rename from public/images/udpxy.png rename to images/udpxy.png diff --git a/images/vlc-tv7.png b/images/vlc-tv7.png new file mode 100644 index 0000000..44fb707 Binary files /dev/null and b/images/vlc-tv7.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..eb36f63 --- /dev/null +++ b/index.html @@ -0,0 +1,25 @@ + + + + + + steve's blog | steve's blog + + + + + + + + +

steve's blog

c#, sport, paragliding

+ + + + \ No newline at end of file diff --git a/index.md b/index.md deleted file mode 100644 index b0d0a91..0000000 --- a/index.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: steve's blog -subtext: c#, sport, paragliding -index: true ---- \ No newline at end of file diff --git a/public/logo.webp b/logo.webp similarity index 100% rename from public/logo.webp rename to logo.webp diff --git a/package.json b/package.json deleted file mode 100644 index c07ea0a..0000000 --- a/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "brown-blog", - "version": "1.0.0", - "private": true, - "type": "module", - "packageManager": "pnpm@10.32.1", - "scripts": { - "dev": "vitepress", - "build": "vitepress build", - "serve": "vitepress serve" - }, - "devDependencies": { - "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.3.0", - "@types/markdown-it": "14.1.2", - "@types/node": "^25.9.1", - "feed": "^5.2.1", - "gray-matter": "^4.0.3", - "tailwindcss": "^4.3.0", - "vitepress": "1.6.4", - "vue": "^3.5.34" - }, - "pnpm": { - "overrides": { - "vite": "^6.4.2", - "esbuild": "^0.25.0" - } - } -} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 4141a36..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,2215 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -overrides: - vite: ^6.4.2 - esbuild: ^0.25.0 - -importers: - - .: - devDependencies: - '@tailwindcss/typography': - specifier: ^0.5.19 - version: 0.5.19(tailwindcss@4.3.0) - '@tailwindcss/vite': - specifier: ^4.3.0 - version: 4.3.0(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)) - '@types/markdown-it': - specifier: 14.1.2 - version: 14.1.2 - '@types/node': - specifier: ^25.9.1 - version: 25.9.1 - feed: - specifier: ^5.2.1 - version: 5.2.1 - gray-matter: - specifier: ^4.0.3 - version: 4.0.3 - tailwindcss: - specifier: ^4.3.0 - version: 4.3.0 - vitepress: - specifier: 1.6.4 - version: 1.6.4(@algolia/client-search@5.52.1)(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(postcss@8.5.14)(search-insights@2.17.3) - vue: - specifier: ^3.5.34 - version: 3.5.34 - -packages: - - '@algolia/abtesting@1.18.1': - resolution: {integrity: sha512-aehCadlWOGvrT91KUIZpC0MbB8KBW9yUuvTJFd2xesR7le/IsT4nJUnjCCZ4ZqZCeTcPHPV5mo//fZ5oxcSVYw==} - engines: {node: '>= 14.0.0'} - - '@algolia/autocomplete-core@1.17.7': - resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} - - '@algolia/autocomplete-plugin-algolia-insights@1.17.7': - resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} - peerDependencies: - search-insights: '>= 1 < 3' - - '@algolia/autocomplete-preset-algolia@1.17.7': - resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} - peerDependencies: - '@algolia/client-search': '>= 4.9.1 < 6' - algoliasearch: '>= 4.9.1 < 6' - - '@algolia/autocomplete-shared@1.17.7': - resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} - peerDependencies: - '@algolia/client-search': '>= 4.9.1 < 6' - algoliasearch: '>= 4.9.1 < 6' - - '@algolia/client-abtesting@5.52.1': - resolution: {integrity: sha512-HmXOGBOAOJPounpBzBpuY0zDYeiCpxgHnQmuA7JO6ScukcBdGp3/XM9zJk5pJx/xNGD68mbPGXWpDxGtl6BwDQ==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-analytics@5.52.1': - resolution: {integrity: sha512-5oo4+I8iixie9vXhCyNFCzeIr8pqA3FQ//VsLHTDvZAV4ttYOPGvYHGQq5NSalrLx5Jc3dRro/5uDOlnUMcBJg==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-common@5.52.1': - resolution: {integrity: sha512-qCDoZfx5MpX7XQzvQ3bC4tSEMkQWQMaF/ABtLuoze03Y/flR563CCSws02qIJ23oX7lxl92LsilZjINVyTdtLw==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-insights@5.52.1': - resolution: {integrity: sha512-hnGs0/lsFJ2PWDxNBz7pxreXo/Xz7gxYRcfePBUjsH26ad0kU/sgnVZd9LwWBpsQv65z2jlb5dkyaB9WE9M9FQ==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-personalization@5.52.1': - resolution: {integrity: sha512-2VxxNc/uBysyKvGeBdSM5n9eIDKH8kWD7wd9/yqbJAiVwU4Yv6tU1LSJusHKrXV/aCu1KW7t9Gug9QyeEmtn/Q==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-query-suggestions@5.52.1': - resolution: {integrity: sha512-O6mPtsw3xEfNOe6gWFpYLeAZAIljNa4Hgna3bq15PwyN7nbjTY0wXJFRbzs/0YVf75Br+SbOQUmjKxXYjDiSiQ==} - engines: {node: '>= 14.0.0'} - - '@algolia/client-search@5.52.1': - resolution: {integrity: sha512-gA8oJOV1LnQQkDf91iebNnFInHuW0gRPEgLSOQ7EfipCEjYTHm5swm1DlH9H5RaRw4RrHuzHBegnlzc0MAstcg==} - engines: {node: '>= 14.0.0'} - - '@algolia/ingestion@1.52.1': - resolution: {integrity: sha512-U9zZfc5xIu9wRxZkt+HceJUAD4VKHKbAyLSloJdEyMRmphXeibfrY9cxqIXBcmPeZzGhn3Imb35Dq8l19PkJhw==} - engines: {node: '>= 14.0.0'} - - '@algolia/monitoring@1.52.1': - resolution: {integrity: sha512-a3SGNceHmkQfq77iG8Ka+w1pvwfZa/0lzEIgse30fL0kD+yKnd/dg0dQvSfFPAEt2f21DMcGkDSSeJlO3KdQjQ==} - engines: {node: '>= 14.0.0'} - - '@algolia/recommend@5.52.1': - resolution: {integrity: sha512-z98QEguCFDpxb4S/PyrUK1igqF8tPsdbqOUUO6ON91vJ58w+Gwa6ncrI0oNXSFcrkxA5EqPKPQ2A1PBCn08TYQ==} - engines: {node: '>= 14.0.0'} - - '@algolia/requester-browser-xhr@5.52.1': - resolution: {integrity: sha512-CI7+/0I11QeZM59Uc8whd2or0kqzFVjpaPn9Qpwll/krHcBAxk24WkAQ6WX+IwDVMfpont4YGbKwAmCre3vE8Q==} - engines: {node: '>= 14.0.0'} - - '@algolia/requester-fetch@5.52.1': - resolution: {integrity: sha512-S6bDuw9byfOvm3T71cgdoZgrgnZq6hpdMLkx52Louh57nUAmvGQESz2aojOynQHjbTiV55smvAFbgn0qT4tJrg==} - engines: {node: '>= 14.0.0'} - - '@algolia/requester-node-http@5.52.1': - resolution: {integrity: sha512-tqZXM+54rWo4mk5jL5Z/flE11nPmNEdXwFBM5py9DkOmbjeCNemfVd45FyM97XdzfZ0dl9uOJC6PYn1FpkeyQg==} - engines: {node: '>= 14.0.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.3': - resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - - '@docsearch/css@3.8.2': - resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} - - '@docsearch/js@3.8.2': - resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==} - - '@docsearch/react@3.8.2': - resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} - peerDependencies: - '@types/react': '>= 16.8.0 < 19.0.0' - react: '>= 16.8.0 < 19.0.0' - react-dom: '>= 16.8.0 < 19.0.0' - search-insights: '>= 1 < 3' - peerDependenciesMeta: - '@types/react': - optional: true - react: - optional: true - react-dom: - optional: true - search-insights: - optional: true - - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@iconify-json/simple-icons@1.2.82': - resolution: {integrity: sha512-4p978qHx8eD/QBOhgBzp/p7uS3OO2KCnVpFPJTUvuhuDXv1Hr4RcxcZ5MWc6ptkf/3Dlb1xb23068OtPyx10mA==} - - '@iconify/types@2.0.0': - resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@rollup/rollup-android-arm-eabi@4.60.3': - resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.60.3': - resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.60.3': - resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.60.3': - resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.60.3': - resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.3': - resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.60.3': - resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm-musleabihf@4.60.3': - resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.60.3': - resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm64-musl@4.60.3': - resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-loong64-gnu@4.60.3': - resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.60.3': - resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.60.3': - resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-musl@4.60.3': - resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.60.3': - resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.60.3': - resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.60.3': - resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-gnu@4.60.3': - resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-musl@4.60.3': - resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rollup/rollup-openbsd-x64@4.60.3': - resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.3': - resolution: {integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.60.3': - resolution: {integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.60.3': - resolution: {integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.60.3': - resolution: {integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.60.3': - resolution: {integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==} - cpu: [x64] - os: [win32] - - '@shikijs/core@2.5.0': - resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} - - '@shikijs/engine-javascript@2.5.0': - resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} - - '@shikijs/engine-oniguruma@2.5.0': - resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} - - '@shikijs/langs@2.5.0': - resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} - - '@shikijs/themes@2.5.0': - resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} - - '@shikijs/transformers@2.5.0': - resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==} - - '@shikijs/types@2.5.0': - resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} - - '@shikijs/vscode-textmate@10.0.2': - resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - - '@tailwindcss/node@4.3.0': - resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} - - '@tailwindcss/oxide-android-arm64@4.3.0': - resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.3.0': - resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.3.0': - resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.3.0': - resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': - resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': - resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.3.0': - resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.3.0': - resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.3.0': - resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.3.0': - resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': - resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.3.0': - resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.3.0': - resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} - engines: {node: '>= 20'} - - '@tailwindcss/typography@0.5.19': - resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} - peerDependencies: - tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' - - '@tailwindcss/vite@4.3.0': - resolution: {integrity: sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==} - peerDependencies: - vite: ^6.4.2 - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/hast@3.0.4': - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - - '@types/linkify-it@5.0.0': - resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} - - '@types/markdown-it@14.1.2': - resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} - - '@types/mdast@4.0.4': - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - - '@types/mdurl@2.0.0': - resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} - - '@types/node@25.9.1': - resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} - - '@types/unist@3.0.3': - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - - '@types/web-bluetooth@0.0.21': - resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} - - '@ungap/structured-clone@1.3.1': - resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} - - '@vitejs/plugin-vue@5.2.4': - resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} - engines: {node: ^18.0.0 || >=20.0.0} - peerDependencies: - vite: ^6.4.2 - vue: ^3.2.25 - - '@vue/compiler-core@3.5.34': - resolution: {integrity: sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==} - - '@vue/compiler-dom@3.5.34': - resolution: {integrity: sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==} - - '@vue/compiler-sfc@3.5.34': - resolution: {integrity: sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==} - - '@vue/compiler-ssr@3.5.34': - resolution: {integrity: sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==} - - '@vue/devtools-api@7.7.9': - resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} - - '@vue/devtools-kit@7.7.9': - resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} - - '@vue/devtools-shared@7.7.9': - resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} - - '@vue/reactivity@3.5.34': - resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==} - - '@vue/runtime-core@3.5.34': - resolution: {integrity: sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==} - - '@vue/runtime-dom@3.5.34': - resolution: {integrity: sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==} - - '@vue/server-renderer@3.5.34': - resolution: {integrity: sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==} - peerDependencies: - vue: 3.5.34 - - '@vue/shared@3.5.34': - resolution: {integrity: sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==} - - '@vueuse/core@12.8.2': - resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==} - - '@vueuse/integrations@12.8.2': - resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==} - peerDependencies: - async-validator: ^4 - axios: ^1 - change-case: ^5 - drauu: ^0.4 - focus-trap: ^7 - fuse.js: ^7 - idb-keyval: ^6 - jwt-decode: ^4 - nprogress: ^0.2 - qrcode: ^1.5 - sortablejs: ^1 - universal-cookie: ^7 - peerDependenciesMeta: - async-validator: - optional: true - axios: - optional: true - change-case: - optional: true - drauu: - optional: true - focus-trap: - optional: true - fuse.js: - optional: true - idb-keyval: - optional: true - jwt-decode: - optional: true - nprogress: - optional: true - qrcode: - optional: true - sortablejs: - optional: true - universal-cookie: - optional: true - - '@vueuse/metadata@12.8.2': - resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==} - - '@vueuse/shared@12.8.2': - resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==} - - algoliasearch@5.52.1: - resolution: {integrity: sha512-fHA8+kXTbjagw3jkLiaS7KKrH8qe2DyOsiUhGlN4cdT77PEsfqXZl7ewDk1hsg+pJnPlnE50XtLxjR91iJOpmg==} - engines: {node: '>= 14.0.0'} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - birpc@2.9.0: - resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} - - ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - - character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - - character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - - comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - - copy-anything@4.0.5: - resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} - engines: {node: '>=18'} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - - emoji-regex-xs@1.0.0: - resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} - - enhanced-resolve@5.21.3: - resolution: {integrity: sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==} - engines: {node: '>=10.13.0'} - - entities@7.0.1: - resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} - engines: {node: '>=0.12'} - - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} - hasBin: true - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - extend-shallow@2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - feed@5.2.1: - resolution: {integrity: sha512-jTynzYPWs9ALjro0GW8j7sv9y7cJBeOdD4Y88kVqYy/eyusIX3g+499JiTDIlD9Ge/unebx57T4Uzo6vpYvMtA==} - engines: {node: '>=20', pnpm: '>=10'} - - focus-trap@7.8.0: - resolution: {integrity: sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - gray-matter@4.0.3: - resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} - engines: {node: '>=6.0'} - - hast-util-to-html@9.0.5: - resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} - - hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - - hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - - html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - - is-extendable@0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} - - is-what@5.5.0: - resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} - engines: {node: '>=18'} - - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} - hasBin: true - - js-yaml@3.14.2: - resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} - hasBin: true - - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - mark.js@8.11.1: - resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} - - mdast-util-to-hast@13.2.1: - resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} - - micromark-util-character@2.1.1: - resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - - micromark-util-encode@2.0.1: - resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - - micromark-util-sanitize-uri@2.0.1: - resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - - micromark-util-symbol@2.0.1: - resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - - micromark-util-types@2.0.2: - resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} - - minisearch@7.2.0: - resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==} - - mitt@3.0.1: - resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - oniguruma-to-es@3.1.1: - resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} - - perfect-debounce@1.0.0: - resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - - postcss-selector-parser@6.0.10: - resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} - engines: {node: '>=4'} - - postcss@8.5.14: - resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} - engines: {node: ^10 || ^12 || >=14} - - preact@10.29.1: - resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} - - property-information@7.1.0: - resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - - regex-recursion@6.0.2: - resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} - - regex-utilities@2.3.0: - resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} - - regex@6.1.0: - resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} - - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - - rollup@4.60.3: - resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - sax@1.6.0: - resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} - engines: {node: '>=11.0.0'} - - search-insights@2.17.3: - resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} - - section-matter@1.0.0: - resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} - engines: {node: '>=4'} - - shiki@2.5.0: - resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - - speakingurl@14.0.1: - resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} - engines: {node: '>=0.10.0'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - - strip-bom-string@1.0.0: - resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} - engines: {node: '>=0.10.0'} - - superjson@2.2.6: - resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} - engines: {node: '>=16'} - - tabbable@6.4.0: - resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} - - tailwindcss@4.3.0: - resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} - - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} - engines: {node: '>=6'} - - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - - trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - - unist-util-is@6.0.1: - resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} - - unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - - unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - - unist-util-visit-parents@6.0.2: - resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - - unist-util-visit@5.1.0: - resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - vfile-message@4.0.3: - resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - - vfile@6.0.3: - resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - - vite@6.4.2: - resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitepress@1.6.4: - resolution: {integrity: sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==} - hasBin: true - peerDependencies: - markdown-it-mathjax3: ^4 - postcss: ^8 - peerDependenciesMeta: - markdown-it-mathjax3: - optional: true - postcss: - optional: true - - vue@3.5.34: - resolution: {integrity: sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - xml-js@1.6.11: - resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} - hasBin: true - - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - -snapshots: - - '@algolia/abtesting@1.18.1': - dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 - - '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': - dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) - transitivePeerDependencies: - - '@algolia/client-search' - - algoliasearch - - search-insights - - '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3)': - dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) - search-insights: 2.17.3 - transitivePeerDependencies: - - '@algolia/client-search' - - algoliasearch - - '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)': - dependencies: - '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) - '@algolia/client-search': 5.52.1 - algoliasearch: 5.52.1 - - '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)': - dependencies: - '@algolia/client-search': 5.52.1 - algoliasearch: 5.52.1 - - '@algolia/client-abtesting@5.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 - - '@algolia/client-analytics@5.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 - - '@algolia/client-common@5.52.1': {} - - '@algolia/client-insights@5.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 - - '@algolia/client-personalization@5.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 - - '@algolia/client-query-suggestions@5.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 - - '@algolia/client-search@5.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 - - '@algolia/ingestion@1.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 - - '@algolia/monitoring@1.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 - - '@algolia/recommend@5.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 - - '@algolia/requester-browser-xhr@5.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - - '@algolia/requester-fetch@5.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - - '@algolia/requester-node-http@5.52.1': - dependencies: - '@algolia/client-common': 5.52.1 - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/parser@7.29.3': - dependencies: - '@babel/types': 7.29.0 - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@docsearch/css@3.8.2': {} - - '@docsearch/js@3.8.2(@algolia/client-search@5.52.1)(search-insights@2.17.3)': - dependencies: - '@docsearch/react': 3.8.2(@algolia/client-search@5.52.1)(search-insights@2.17.3) - preact: 10.29.1 - transitivePeerDependencies: - - '@algolia/client-search' - - '@types/react' - - react - - react-dom - - search-insights - - '@docsearch/react@3.8.2(@algolia/client-search@5.52.1)(search-insights@2.17.3)': - dependencies: - '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1)(search-insights@2.17.3) - '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.52.1)(algoliasearch@5.52.1) - '@docsearch/css': 3.8.2 - algoliasearch: 5.52.1 - optionalDependencies: - search-insights: 2.17.3 - transitivePeerDependencies: - - '@algolia/client-search' - - '@esbuild/aix-ppc64@0.25.12': - optional: true - - '@esbuild/android-arm64@0.25.12': - optional: true - - '@esbuild/android-arm@0.25.12': - optional: true - - '@esbuild/android-x64@0.25.12': - optional: true - - '@esbuild/darwin-arm64@0.25.12': - optional: true - - '@esbuild/darwin-x64@0.25.12': - optional: true - - '@esbuild/freebsd-arm64@0.25.12': - optional: true - - '@esbuild/freebsd-x64@0.25.12': - optional: true - - '@esbuild/linux-arm64@0.25.12': - optional: true - - '@esbuild/linux-arm@0.25.12': - optional: true - - '@esbuild/linux-ia32@0.25.12': - optional: true - - '@esbuild/linux-loong64@0.25.12': - optional: true - - '@esbuild/linux-mips64el@0.25.12': - optional: true - - '@esbuild/linux-ppc64@0.25.12': - optional: true - - '@esbuild/linux-riscv64@0.25.12': - optional: true - - '@esbuild/linux-s390x@0.25.12': - optional: true - - '@esbuild/linux-x64@0.25.12': - optional: true - - '@esbuild/netbsd-arm64@0.25.12': - optional: true - - '@esbuild/netbsd-x64@0.25.12': - optional: true - - '@esbuild/openbsd-arm64@0.25.12': - optional: true - - '@esbuild/openbsd-x64@0.25.12': - optional: true - - '@esbuild/openharmony-arm64@0.25.12': - optional: true - - '@esbuild/sunos-x64@0.25.12': - optional: true - - '@esbuild/win32-arm64@0.25.12': - optional: true - - '@esbuild/win32-ia32@0.25.12': - optional: true - - '@esbuild/win32-x64@0.25.12': - optional: true - - '@iconify-json/simple-icons@1.2.82': - dependencies: - '@iconify/types': 2.0.0 - - '@iconify/types@2.0.0': {} - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@rollup/rollup-android-arm-eabi@4.60.3': - optional: true - - '@rollup/rollup-android-arm64@4.60.3': - optional: true - - '@rollup/rollup-darwin-arm64@4.60.3': - optional: true - - '@rollup/rollup-darwin-x64@4.60.3': - optional: true - - '@rollup/rollup-freebsd-arm64@4.60.3': - optional: true - - '@rollup/rollup-freebsd-x64@4.60.3': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.60.3': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.60.3': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.60.3': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.60.3': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.60.3': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.60.3': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.60.3': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.60.3': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.60.3': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.60.3': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.60.3': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.60.3': - optional: true - - '@rollup/rollup-linux-x64-musl@4.60.3': - optional: true - - '@rollup/rollup-openbsd-x64@4.60.3': - optional: true - - '@rollup/rollup-openharmony-arm64@4.60.3': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.60.3': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.60.3': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.60.3': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.60.3': - optional: true - - '@shikijs/core@2.5.0': - dependencies: - '@shikijs/engine-javascript': 2.5.0 - '@shikijs/engine-oniguruma': 2.5.0 - '@shikijs/types': 2.5.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - hast-util-to-html: 9.0.5 - - '@shikijs/engine-javascript@2.5.0': - dependencies: - '@shikijs/types': 2.5.0 - '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 3.1.1 - - '@shikijs/engine-oniguruma@2.5.0': - dependencies: - '@shikijs/types': 2.5.0 - '@shikijs/vscode-textmate': 10.0.2 - - '@shikijs/langs@2.5.0': - dependencies: - '@shikijs/types': 2.5.0 - - '@shikijs/themes@2.5.0': - dependencies: - '@shikijs/types': 2.5.0 - - '@shikijs/transformers@2.5.0': - dependencies: - '@shikijs/core': 2.5.0 - '@shikijs/types': 2.5.0 - - '@shikijs/types@2.5.0': - dependencies: - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - - '@shikijs/vscode-textmate@10.0.2': {} - - '@tailwindcss/node@4.3.0': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.21.3 - jiti: 2.7.0 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.3.0 - - '@tailwindcss/oxide-android-arm64@4.3.0': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.3.0': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.3.0': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.3.0': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.3.0': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.3.0': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.3.0': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.3.0': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.3.0': - optional: true - - '@tailwindcss/oxide@4.3.0': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.3.0 - '@tailwindcss/oxide-darwin-arm64': 4.3.0 - '@tailwindcss/oxide-darwin-x64': 4.3.0 - '@tailwindcss/oxide-freebsd-x64': 4.3.0 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.0 - '@tailwindcss/oxide-linux-arm64-gnu': 4.3.0 - '@tailwindcss/oxide-linux-arm64-musl': 4.3.0 - '@tailwindcss/oxide-linux-x64-gnu': 4.3.0 - '@tailwindcss/oxide-linux-x64-musl': 4.3.0 - '@tailwindcss/oxide-wasm32-wasi': 4.3.0 - '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 - '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 - - '@tailwindcss/typography@0.5.19(tailwindcss@4.3.0)': - dependencies: - postcss-selector-parser: 6.0.10 - tailwindcss: 4.3.0 - - '@tailwindcss/vite@4.3.0(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0))': - dependencies: - '@tailwindcss/node': 4.3.0 - '@tailwindcss/oxide': 4.3.0 - tailwindcss: 4.3.0 - vite: 6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0) - - '@types/estree@1.0.8': {} - - '@types/hast@3.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/linkify-it@5.0.0': {} - - '@types/markdown-it@14.1.2': - dependencies: - '@types/linkify-it': 5.0.0 - '@types/mdurl': 2.0.0 - - '@types/mdast@4.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/mdurl@2.0.0': {} - - '@types/node@25.9.1': - dependencies: - undici-types: 7.24.6 - - '@types/unist@3.0.3': {} - - '@types/web-bluetooth@0.0.21': {} - - '@ungap/structured-clone@1.3.1': {} - - '@vitejs/plugin-vue@5.2.4(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0))(vue@3.5.34)': - dependencies: - vite: 6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0) - vue: 3.5.34 - - '@vue/compiler-core@3.5.34': - dependencies: - '@babel/parser': 7.29.3 - '@vue/shared': 3.5.34 - entities: 7.0.1 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - - '@vue/compiler-dom@3.5.34': - dependencies: - '@vue/compiler-core': 3.5.34 - '@vue/shared': 3.5.34 - - '@vue/compiler-sfc@3.5.34': - dependencies: - '@babel/parser': 7.29.3 - '@vue/compiler-core': 3.5.34 - '@vue/compiler-dom': 3.5.34 - '@vue/compiler-ssr': 3.5.34 - '@vue/shared': 3.5.34 - estree-walker: 2.0.2 - magic-string: 0.30.21 - postcss: 8.5.14 - source-map-js: 1.2.1 - - '@vue/compiler-ssr@3.5.34': - dependencies: - '@vue/compiler-dom': 3.5.34 - '@vue/shared': 3.5.34 - - '@vue/devtools-api@7.7.9': - dependencies: - '@vue/devtools-kit': 7.7.9 - - '@vue/devtools-kit@7.7.9': - dependencies: - '@vue/devtools-shared': 7.7.9 - birpc: 2.9.0 - hookable: 5.5.3 - mitt: 3.0.1 - perfect-debounce: 1.0.0 - speakingurl: 14.0.1 - superjson: 2.2.6 - - '@vue/devtools-shared@7.7.9': - dependencies: - rfdc: 1.4.1 - - '@vue/reactivity@3.5.34': - dependencies: - '@vue/shared': 3.5.34 - - '@vue/runtime-core@3.5.34': - dependencies: - '@vue/reactivity': 3.5.34 - '@vue/shared': 3.5.34 - - '@vue/runtime-dom@3.5.34': - dependencies: - '@vue/reactivity': 3.5.34 - '@vue/runtime-core': 3.5.34 - '@vue/shared': 3.5.34 - csstype: 3.2.3 - - '@vue/server-renderer@3.5.34(vue@3.5.34)': - dependencies: - '@vue/compiler-ssr': 3.5.34 - '@vue/shared': 3.5.34 - vue: 3.5.34 - - '@vue/shared@3.5.34': {} - - '@vueuse/core@12.8.2': - dependencies: - '@types/web-bluetooth': 0.0.21 - '@vueuse/metadata': 12.8.2 - '@vueuse/shared': 12.8.2 - vue: 3.5.34 - transitivePeerDependencies: - - typescript - - '@vueuse/integrations@12.8.2(focus-trap@7.8.0)': - dependencies: - '@vueuse/core': 12.8.2 - '@vueuse/shared': 12.8.2 - vue: 3.5.34 - optionalDependencies: - focus-trap: 7.8.0 - transitivePeerDependencies: - - typescript - - '@vueuse/metadata@12.8.2': {} - - '@vueuse/shared@12.8.2': - dependencies: - vue: 3.5.34 - transitivePeerDependencies: - - typescript - - algoliasearch@5.52.1: - dependencies: - '@algolia/abtesting': 1.18.1 - '@algolia/client-abtesting': 5.52.1 - '@algolia/client-analytics': 5.52.1 - '@algolia/client-common': 5.52.1 - '@algolia/client-insights': 5.52.1 - '@algolia/client-personalization': 5.52.1 - '@algolia/client-query-suggestions': 5.52.1 - '@algolia/client-search': 5.52.1 - '@algolia/ingestion': 1.52.1 - '@algolia/monitoring': 1.52.1 - '@algolia/recommend': 5.52.1 - '@algolia/requester-browser-xhr': 5.52.1 - '@algolia/requester-fetch': 5.52.1 - '@algolia/requester-node-http': 5.52.1 - - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - - birpc@2.9.0: {} - - ccount@2.0.1: {} - - character-entities-html4@2.1.0: {} - - character-entities-legacy@3.0.0: {} - - comma-separated-tokens@2.0.3: {} - - copy-anything@4.0.5: - dependencies: - is-what: 5.5.0 - - cssesc@3.0.0: {} - - csstype@3.2.3: {} - - dequal@2.0.3: {} - - detect-libc@2.1.2: {} - - devlop@1.1.0: - dependencies: - dequal: 2.0.3 - - emoji-regex-xs@1.0.0: {} - - enhanced-resolve@5.21.3: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - - entities@7.0.1: {} - - esbuild@0.25.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 - - esprima@4.0.1: {} - - estree-walker@2.0.2: {} - - extend-shallow@2.0.1: - dependencies: - is-extendable: 0.1.1 - - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - - feed@5.2.1: - dependencies: - xml-js: 1.6.11 - - focus-trap@7.8.0: - dependencies: - tabbable: 6.4.0 - - fsevents@2.3.3: - optional: true - - graceful-fs@4.2.11: {} - - gray-matter@4.0.3: - dependencies: - js-yaml: 3.14.2 - kind-of: 6.0.3 - section-matter: 1.0.0 - strip-bom-string: 1.0.0 - - hast-util-to-html@9.0.5: - dependencies: - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - comma-separated-tokens: 2.0.3 - hast-util-whitespace: 3.0.0 - html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.1 - property-information: 7.1.0 - space-separated-tokens: 2.0.2 - stringify-entities: 4.0.4 - zwitch: 2.0.4 - - hast-util-whitespace@3.0.0: - dependencies: - '@types/hast': 3.0.4 - - hookable@5.5.3: {} - - html-void-elements@3.0.0: {} - - is-extendable@0.1.1: {} - - is-what@5.5.0: {} - - jiti@2.7.0: {} - - js-yaml@3.14.2: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - - kind-of@6.0.3: {} - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - mark.js@8.11.1: {} - - mdast-util-to-hast@13.2.1: - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.1 - devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.1 - trim-lines: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - - micromark-util-character@2.1.1: - dependencies: - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-encode@2.0.1: {} - - micromark-util-sanitize-uri@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-encode: 2.0.1 - micromark-util-symbol: 2.0.1 - - micromark-util-symbol@2.0.1: {} - - micromark-util-types@2.0.2: {} - - minisearch@7.2.0: {} - - mitt@3.0.1: {} - - nanoid@3.3.12: {} - - oniguruma-to-es@3.1.1: - dependencies: - emoji-regex-xs: 1.0.0 - regex: 6.1.0 - regex-recursion: 6.0.2 - - perfect-debounce@1.0.0: {} - - picocolors@1.1.1: {} - - picomatch@4.0.4: {} - - postcss-selector-parser@6.0.10: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss@8.5.14: - dependencies: - nanoid: 3.3.12 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - preact@10.29.1: {} - - property-information@7.1.0: {} - - regex-recursion@6.0.2: - dependencies: - regex-utilities: 2.3.0 - - regex-utilities@2.3.0: {} - - regex@6.1.0: - dependencies: - regex-utilities: 2.3.0 - - rfdc@1.4.1: {} - - rollup@4.60.3: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.3 - '@rollup/rollup-android-arm64': 4.60.3 - '@rollup/rollup-darwin-arm64': 4.60.3 - '@rollup/rollup-darwin-x64': 4.60.3 - '@rollup/rollup-freebsd-arm64': 4.60.3 - '@rollup/rollup-freebsd-x64': 4.60.3 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.3 - '@rollup/rollup-linux-arm-musleabihf': 4.60.3 - '@rollup/rollup-linux-arm64-gnu': 4.60.3 - '@rollup/rollup-linux-arm64-musl': 4.60.3 - '@rollup/rollup-linux-loong64-gnu': 4.60.3 - '@rollup/rollup-linux-loong64-musl': 4.60.3 - '@rollup/rollup-linux-ppc64-gnu': 4.60.3 - '@rollup/rollup-linux-ppc64-musl': 4.60.3 - '@rollup/rollup-linux-riscv64-gnu': 4.60.3 - '@rollup/rollup-linux-riscv64-musl': 4.60.3 - '@rollup/rollup-linux-s390x-gnu': 4.60.3 - '@rollup/rollup-linux-x64-gnu': 4.60.3 - '@rollup/rollup-linux-x64-musl': 4.60.3 - '@rollup/rollup-openbsd-x64': 4.60.3 - '@rollup/rollup-openharmony-arm64': 4.60.3 - '@rollup/rollup-win32-arm64-msvc': 4.60.3 - '@rollup/rollup-win32-ia32-msvc': 4.60.3 - '@rollup/rollup-win32-x64-gnu': 4.60.3 - '@rollup/rollup-win32-x64-msvc': 4.60.3 - fsevents: 2.3.3 - - sax@1.6.0: {} - - search-insights@2.17.3: {} - - section-matter@1.0.0: - dependencies: - extend-shallow: 2.0.1 - kind-of: 6.0.3 - - shiki@2.5.0: - dependencies: - '@shikijs/core': 2.5.0 - '@shikijs/engine-javascript': 2.5.0 - '@shikijs/engine-oniguruma': 2.5.0 - '@shikijs/langs': 2.5.0 - '@shikijs/themes': 2.5.0 - '@shikijs/types': 2.5.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - - source-map-js@1.2.1: {} - - space-separated-tokens@2.0.2: {} - - speakingurl@14.0.1: {} - - sprintf-js@1.0.3: {} - - stringify-entities@4.0.4: - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - - strip-bom-string@1.0.0: {} - - superjson@2.2.6: - dependencies: - copy-anything: 4.0.5 - - tabbable@6.4.0: {} - - tailwindcss@4.3.0: {} - - tapable@2.3.3: {} - - tinyglobby@0.2.16: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - - trim-lines@3.0.1: {} - - undici-types@7.24.6: {} - - unist-util-is@6.0.1: - dependencies: - '@types/unist': 3.0.3 - - unist-util-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-stringify-position@4.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-visit-parents@6.0.2: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - - unist-util-visit@5.1.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - - util-deprecate@1.0.2: {} - - vfile-message@4.0.3: - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - - vfile@6.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile-message: 4.0.3 - - vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0): - dependencies: - esbuild: 0.25.12 - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - postcss: 8.5.14 - rollup: 4.60.3 - tinyglobby: 0.2.16 - optionalDependencies: - '@types/node': 25.9.1 - fsevents: 2.3.3 - jiti: 2.7.0 - lightningcss: 1.32.0 - - vitepress@1.6.4(@algolia/client-search@5.52.1)(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(postcss@8.5.14)(search-insights@2.17.3): - dependencies: - '@docsearch/css': 3.8.2 - '@docsearch/js': 3.8.2(@algolia/client-search@5.52.1)(search-insights@2.17.3) - '@iconify-json/simple-icons': 1.2.82 - '@shikijs/core': 2.5.0 - '@shikijs/transformers': 2.5.0 - '@shikijs/types': 2.5.0 - '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.4(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0))(vue@3.5.34) - '@vue/devtools-api': 7.7.9 - '@vue/shared': 3.5.34 - '@vueuse/core': 12.8.2 - '@vueuse/integrations': 12.8.2(focus-trap@7.8.0) - focus-trap: 7.8.0 - mark.js: 8.11.1 - minisearch: 7.2.0 - shiki: 2.5.0 - vite: 6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0) - vue: 3.5.34 - optionalDependencies: - postcss: 8.5.14 - transitivePeerDependencies: - - '@algolia/client-search' - - '@types/node' - - '@types/react' - - async-validator - - axios - - change-case - - drauu - - fuse.js - - idb-keyval - - jiti - - jwt-decode - - less - - lightningcss - - nprogress - - qrcode - - react - - react-dom - - sass - - sass-embedded - - search-insights - - sortablejs - - stylus - - sugarss - - terser - - tsx - - typescript - - universal-cookie - - yaml - - vue@3.5.34: - dependencies: - '@vue/compiler-dom': 3.5.34 - '@vue/compiler-sfc': 3.5.34 - '@vue/runtime-dom': 3.5.34 - '@vue/server-renderer': 3.5.34(vue@3.5.34) - '@vue/shared': 3.5.34 - - xml-js@1.6.11: - dependencies: - sax: 1.6.0 - - zwitch@2.0.4: {} diff --git a/posts/blazor-wasm-github.md b/posts/blazor-wasm-github.md deleted file mode 100644 index 45e3fa8..0000000 --- a/posts/blazor-wasm-github.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Running Blazor WASM on GitHub Pages -date: 2024-03-09 -author: Stephen Brown ---- - -Create a new Blazor WASM project - -dotnet workload install wasm-tools - -Add to git - -Add these: - -```xml -true -true -``` \ No newline at end of file diff --git a/posts/dotnet-kerberos.html b/posts/dotnet-kerberos.html new file mode 100644 index 0000000..8f0177a --- /dev/null +++ b/posts/dotnet-kerberos.html @@ -0,0 +1,35 @@ + + + + + + Creating Kerberos secured connections from Dotnet on Linux | steve's blog + + + + + + + + +
Published on

Creating Kerberos secured connections from Dotnet on Linux

Authors
  • Name
    Stephen Brown

For the last few years at my workplace we've been using Kerberos authentication to connect to the on-prem SQL Server databases. On Windows this just worked like magic, as the services ran as a Windows domain user and so could connect to the database without any additional configuration. However, when moving to Kubernetes and Linux systems, this didn't work out of the box.

Our initial solution was following this red hat blog post which details how to setup a sidecar which keeps a kerberos token valid. This worked for a number of years but did have some problems:

  • We ran 100+ containers and each needed a sidecar - this cost a few gigabytes of memory and some CPU allowance
  • When the token was being refreshed, it was not available for a few milliseconds
  • It was extra hassle and created slightly larger deployments and more complex Deployment configs.

When investigating a seperate issue (dotnet 7 caused some transient kerberos faults) we realised that actually the whole sidecar approach was completely unneccessary! This post will give the minimum you actually need to call Keberos services from Linux

  1. You'll need to add the keberos tools to your docker image. Maybe you have a base image you share, maybe not. For alpine images:

RUN apk add --no-cache krb5

  1. You'll need to have a krb5 configuration that points to your domain controller. Microsoft has an example

For example, ours looks like this:

[logging]
+default
+
+[libdefaults]
+...standard things here
+default_client_keytab_name=/krb5/client.keytab
+default_keytab_name=/krb5/krb5.keytab
+default_ccache_name=FILE:/dev/shm/ccache
+default_realm = EXAMPLE.COM
+
+[realms]
+EXAMPLE.COM = {
+    kdc = ADS.EXAMPLE.COM:88
+}

You can then add this to the docker image like

RUN mkdir /krb5 && mkdir /customkrb5
+COPY krb5.conf /customkrb5/krb5.conf
+ENV KRB5_CONFIG=/customkrb5/krb5.conf

We put it in a custom folder as some container systems have the limitation that mounting a directory overrides all files in that directory (we'll do this later)

  1. You then need a keytab added to the image. You will probably want this done at runtime. Get a keytab (maybe off your AD system administrators, maybe you create it yourself with ktutil) and make sure this ends up at /krb5/client.keytab. We use kubernetes secrets and volume mounts for this

  2. Connect to the database using Integrated Security=true. This should now just work. You can check by calling klist inside the container to see that it has a ticket

+ + + + \ No newline at end of file diff --git a/posts/dotnet-kerberos.md b/posts/dotnet-kerberos.md deleted file mode 100644 index 38b27d0..0000000 --- a/posts/dotnet-kerberos.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Creating Kerberos secured connections from Dotnet on Linux -date: 2023-08-05 -author: Stephen Brown ---- - -For the last few years at my workplace we've been using Kerberos authentication to connect to the on-prem SQL Server databases. On Windows this just worked like magic, as the services ran as a Windows domain user and so could connect to the database without any additional configuration. However, when moving to Kubernetes and Linux systems, this didn't work out of the box. - ---- - -Our initial solution was following this [Red Hat blog post](https://cloud.redhat.com/blog/kerberos-sidecar-container) which details how to set up a sidecar which keeps a kerberos token valid. This worked for a number of years but did have some problems: - - - We ran 100+ containers and each needed a sidecar - this cost a few gigabytes of memory and some CPU allowance - - When the token was being refreshed, it was not available for a few milliseconds - - It was extra hassle and created slightly larger deployments and more complex Deployment configs. - -When investigating a separate issue (dotnet 7 caused some transient kerberos faults) we realised that actually the whole sidecar approach was completely unnecessary! This post will give the minimum you actually need to call Kerberos services from Linux - -1) You'll need to add the kerberos tools to your docker image. Maybe you have a base image you share, maybe not. For alpine images: - -```RUN apk add --no-cache krb5``` - -2) You'll need to have a krb5 configuration that points to your domain controller. Microsoft has an [example](https://learn.microsoft.com/en-us/sql/connect/jdbc/using-kerberos-integrated-authentication-to-connect-to-sql-server?view=sql-server-ver16#creating-a-kerberos-configuration-file) - -For example, ours looks like this: - -``` -[logging] -default - -[libdefaults] -...standard things here -default_client_keytab_name=/krb5/client.keytab -default_keytab_name=/krb5/krb5.keytab -default_ccache_name=FILE:/dev/shm/ccache -default_realm = EXAMPLE.COM - -[realms] -EXAMPLE.COM = { - kdc = ADS.EXAMPLE.COM:88 -} -``` - -You can then add this to the docker image like -``` -RUN mkdir /krb5 && mkdir /customkrb5 -COPY krb5.conf /customkrb5/krb5.conf -ENV KRB5_CONFIG=/customkrb5/krb5.conf -``` - -We put it in a custom folder as some container systems have the limitation that mounting a directory overrides all files in that directory (we'll do this later) - -3) You then need a keytab added to the image. You will probably want this done at runtime. Get a keytab (maybe off your AD system administrators, maybe you create it yourself with ktutil) and make sure this ends up at /krb5/client.keytab. We use kubernetes secrets and volume mounts for this - -4) Connect to the database using `Integrated Security=true`. This should now just work. You can check by calling `klist` inside the container to see that it has a ticket \ No newline at end of file diff --git a/posts/github-actions-net7-docker.html b/posts/github-actions-net7-docker.html new file mode 100644 index 0000000..7ef4c6a --- /dev/null +++ b/posts/github-actions-net7-docker.html @@ -0,0 +1,67 @@ + + + + + + Dockerless docker builds in Github Actions with Dotnet 7 | steve's blog + + + + + + + + +
Published on

Dockerless docker builds in Github Actions with Dotnet 7

Authors

How to setup a github build action that uses the dockerless build in .Net 7


Background

For me, one of the slowest parts of the fast build-test-deploy cycle was always waiting for docker builds to run. Especially with the practise of docker-in-docker builds which without special magic wouldn't cache anything, it could often take several minutes to create a docker application. .Net 7 promises to offer an alternative to this for simple apps - let's take a dive in.

Setup

You'll need .Net 7 for this, which isn't currently released at time of writing. However, in theory you can take a .Net 6 application and use the .Net 7 SDK to build it already, however YMMV.

With this installed, let's create a new app and get started! We're following along with https://devblogs.microsoft.com/dotnet/announcing-builtin-container-support-for-the-dotnet-sdk/ but with some changes

Basic app

bash
dotnet new webapi -o dockerless-docker
+cd dockerless-docker

We also need to do a couple of things: because .Net 7 isn't out yet, we need a globals file to tell the github actions to use it

That gives us our basic app - let's add a github actions build for it now, by adding the following:

json
{
+    "sdk": {
+        "version": "7.0.100-rc.2.22477.23"
+    }
+}

We'll also need to add a reference to this tool:

bash
dotnet add package Microsoft.NET.Build.Containers

Now, add a github actions to .github/workflows/whatever.yml

yaml
name: Create and publish a Docker image
+
+on:
+  push:
+    branches: [ "*" ]
+  pull_request:
+    branches: [ "main" ]
+
+
+env:
+  REGISTRY: ghcr.io
+  IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+  build-and-push-image:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+
+    steps:
+      - uses: actions/checkout@v3
+      - name: Setup .NET SDK
+        uses: actions/setup-dotnet@v2
+      # Package the app into a linux-x64 container based on the dotnet/aspnet image
+      - name: Publish
+        run: dotnet publish --os linux --arch x64 --configuration Release -p:PublishProfile=DefaultContainer
+      - name: Checkout repository
+        uses: actions/checkout@v3
+      - name: Login to GitHub Container Registry
+        uses: docker/login-action@v2
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      - name: Tag built container with Github thing
+        run: |
+          docker tag dockerless-docker:1.0.0 ghcr.io/evolvedlight/dockerless-docker:1.0.0
+      - name: Push built container to Github Package Repo
+        run: |
+          docker push ghcr.io/evolvedlight/dockerless-docker:1.0.0

Here's now where the weird bit comes - on github the process to get a repository seems rather backward. You need to assign permissions for the repo to write images, but there's no way to do that until you've pushed an image manually!

So let's go around the hoops - create the docker image locally, tag it, login to the Github repository and upload it:

dotnet publish --os linux --arch x64 --configuration Release -p:PublishProfile=DefaultContainer
+docker tag dockerless-docker:1.0.0 ghcr.io/evolvedlight/dockerless-docker:1.0.0
+docker login ghcr.io -u <your username>

Enter a Personal access token that you can create on your github profile page, and finally push the image:

docker push ghcr.io/evolvedlight/dockerless-docker:1.0.0

Now go to your packages page, for me that's https://github.com/evolvedlight?tab=packages. Click on the new package page that was created, and then go to the settings for it (for me https://github.com/users/evolvedlight/packages/container/dockerless-docker/settings)

Under "Manage Actions access" add your repository with access.

In the end it'll look like this:

github permissions

Finally, rerun the action and it should work.

In the next blob post we'll look at fixing the above docker build to push with the right version numbers and tags

+ + + + \ No newline at end of file diff --git a/posts/github-actions-net7-docker.md b/posts/github-actions-net7-docker.md deleted file mode 100644 index 33e2764..0000000 --- a/posts/github-actions-net7-docker.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -title: Dockerless docker builds in Github Actions with Dotnet 7 -date: 2022-10-14 -author: Stephen Brown -twitter: '@evolvedlight' ---- - -How to set up a GitHub build action that uses the dockerless build in .NET 7 - ---- - -## Background - -For me, one of the slowest parts of the fast build-test-deploy cycle was always waiting for docker builds to run. Especially with the practice of docker-in-docker builds which without special magic wouldn't cache anything, it could often take several minutes to create a docker application. -.NET 7 promises to offer an alternative to this for simple apps - let's take a dive in. - -### Setup - -You'll need .NET 7 for this, which isn't currently released at time of writing. However, in theory you can take a .NET 6 application and use the .NET 7 SDK to build it already; however, YMMV. - -With this installed, let's create a new app and get started! We're following along with https://devblogs.microsoft.com/dotnet/announcing-builtin-container-support-for-the-dotnet-sdk/ but with some changes - -### Basic app - -```bash -dotnet new webapi -o dockerless-docker -cd dockerless-docker -``` - -We also need to do a couple of things: because .NET 7 isn't out yet, we need a global.json file to tell GitHub Actions to use it. - -That gives us our basic app - let's add a GitHub Actions build for it now, by adding the following: -```json -{ - "sdk": { - "version": "7.0.100-rc.2.22477.23" - } -} -``` - -We'll also need to add a reference to this tool: -```bash -dotnet add package Microsoft.NET.Build.Containers -``` - -Now, add a GitHub Actions workflow to ```.github/workflows/whatever.yml``` - -```yaml -name: Create and publish a Docker image - -on: - push: - branches: [ "*" ] - pull_request: - branches: [ "main" ] - - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - build-and-push-image: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - uses: actions/checkout@v3 - - name: Setup .NET SDK - uses: actions/setup-dotnet@v2 - # Package the app into a linux-x64 container based on the dotnet/aspnet image - - name: Publish - run: dotnet publish --os linux --arch x64 --configuration Release -p:PublishProfile=DefaultContainer - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Tag built container with GitHub metadata - run: | - docker tag dockerless-docker:1.0.0 ghcr.io/evolvedlight/dockerless-docker:1.0.0 - - name: Push built container to GitHub Package Repo - run: | - docker push ghcr.io/evolvedlight/dockerless-docker:1.0.0 -``` - -Here's now where the weird bit comes - on GitHub the process to get a repository seems rather backward. You need to assign permissions for the repo to write images, but there's no way to do that _until_ you've pushed an image manually! - -So let's go around the hoops - create the docker image locally, tag it, login to the GitHub repository and upload it: - -``` -dotnet publish --os linux --arch x64 --configuration Release -p:PublishProfile=DefaultContainer -docker tag dockerless-docker:1.0.0 ghcr.io/evolvedlight/dockerless-docker:1.0.0 -docker login ghcr.io -u -``` - -Enter a Personal access token that you can create on your GitHub profile page, and finally push the image: - -``` -docker push ghcr.io/evolvedlight/dockerless-docker:1.0.0 -``` - -Now go to your packages page, for me that's https://github.com/evolvedlight?tab=packages. -Click on the new package page that was created, and then go to the settings for it (for me https://github.com/users/evolvedlight/packages/container/dockerless-docker/settings) - -Under "Manage Actions access" add your repository with access. - -In the end it'll look like this: - -![GitHub permissions](/images/github_actions_permission.png) - -Finally, rerun the action and it should work. - -In the next blog post we'll look at fixing the above docker build to push with the right version numbers and tags \ No newline at end of file diff --git a/posts/init7-tv7.html b/posts/init7-tv7.html new file mode 100644 index 0000000..e76a910 --- /dev/null +++ b/posts/init7-tv7.html @@ -0,0 +1,20 @@ + + + + + + TV7 (IPTV from Init7) with Dream Machine and Unity Dream machine | steve's blog + + + + + + + + +
Published on

TV7 (IPTV from Init7) with Dream Machine and Unity Dream machine

Authors
  • Name
    Stephen Brown

If you're an Init7 TV customer and you want to use the Unify Dream Machine as your IPTV receiver, I figured it out for you! Here's a step-by-step guide to help you get started.

Step 1: Update to the Newest Dream Machine

Before you begin, make sure your Unify Dream Machine is updated to the latest firmware version. You can check for updates by logging into the Unify Dream Machine web interface and navigating to the "Settings" page. Your version needs to be higher than "UniFi Network Application 7.4.156".

Step 2: Enable the feature

You now have a new setup that you can do on the Network application:

udpxy

You can also see the basic unifi docs

You should now already be able to watch TV7 though VLC using this link: https://www.init7.net/en/support/faq/TV-andere-Geraete/

udpxy

However, I wanted to watch on a Samsung TV and none of the apps supported these multicast IPTV streams. So, I had to:

Step 3: Install agrrh-udpxy.

I installed this as a docker image on a syntology NAS.

The following settings worked for me: image: agrrh/udpxy:latest network: host cmd: -v -T -p 4022

You can then go to your nas drive on port 4022, URL /status. For example mine is http://nasdrive:4022/status and I see:

udpxy

Step 4: Create your own playlist that forwards to your udprxy instance instead of the original network stream

You can do this by opening up the m3u file and relacing the URLs with the link to your local instance. For example, mine is here: https://gist.github.com/evolvedlight/e9b2a0145840ba5385478a3075aa815a

Step 5: Open this in a IPTV app.

Personally I used one called "m3u player" but there are many out there. You'll then also be able to go to your udpxy status page and see the running stream there.

+ + + + \ No newline at end of file diff --git a/posts/init7-tv7.md b/posts/init7-tv7.md deleted file mode 100644 index 3950436..0000000 --- a/posts/init7-tv7.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: TV7 (IPTV from Init7) with Dream Machine and UniFi Dream Machine -date: 2023-08-04 -author: Stephen Brown ---- - -If you're an Init7 TV customer and you want to use the UniFi Dream Machine as your IPTV receiver, I figured it out for you! Here's a step-by-step guide to help you get started. - ---- - -## Step 1: Update to the Newest Dream Machine - -Before you begin, make sure your UniFi Dream Machine is updated to the latest firmware version. You can check for updates by logging into the UniFi Dream Machine web interface and navigating to the "Settings" page. -Your version needs to be higher than ["UniFi Network Application 7.4.156"](https://community.ui.com/releases/UniFi-Network-Application-7-4-156/15ac6260-9cd1-4ac3-a91c-4880c1c87882). - -## Step 2: Enable the feature - -You now have a new setup that you can do on the Network application: - -![udpxy](/images/network-iptv.png) - -You can also see the [basic unifi docs](https://help.ui.com/hc/en-us/articles/14957610078615-UniFi-Gateway-IGMP-Proxy-IPTV-#:~:text=How%20Does%20it%20Work%3F,box%20that%20receives%20the%20traffic.) - -You should now already be able to watch TV7 through VLC using this link: https://www.init7.net/en/support/faq/TV-andere-Geraete/ - -![udpxy](/images/vlc-tv7.jpg) - -However, I wanted to watch on a Samsung TV and none of the apps supported these multicast IPTV streams. So, I had to: - -## Step 3: Install agrrh-udpxy. - -I installed this as a docker image on a Synology NAS. - -The following settings worked for me: -image: agrrh/udpxy:latest -network: host -cmd: -v -T -p 4022 - -You can then go to your nas drive on port 4022, URL /status. For example mine is http://nasdrive:4022/status and I see: - -![udpxy](/images/udpxy.png) - -## Step 4: Create your own playlist that forwards to your udpxy instance instead of the original network stream - -You can do this by opening up the m3u file and replacing the URLs with the link to your local instance. For example, mine is here: -https://gist.github.com/evolvedlight/e9b2a0145840ba5385478a3075aa815a - -## Step 5: Open this in an IPTV app. - -Personally I used one called "m3u player" but there are many out there. You'll then also be able to go to your udpxy status page and see the running stream there. \ No newline at end of file diff --git a/posts/scaling-masstransit-jobs.html b/posts/scaling-masstransit-jobs.html new file mode 100644 index 0000000..393989c --- /dev/null +++ b/posts/scaling-masstransit-jobs.html @@ -0,0 +1,112 @@ + + + + + + Running MassTransit queues with KEDA | steve's blog + + + + + + + + +
Published on

Running MassTransit queues with KEDA

Authors
  • Name
    Stephen Brown

We have an interesting challenge in some of our microservices - we run lots and lots of microservices, all using a relatively small amount of memory, but the usage patterns may be unusual. All these microservices talk MassTransit - some also have external triggers, API endpoints or scheduled jobs.

They run like this:

  • Most are processing constantly lots of small messages. These are no problem.
  • Some process only a few messages a day, but we have to keep the service running all day. This is annoying.
  • Some process only a few messages per day, but those small messages take a lot of CPU and memory. For example they may need to deal with files in memory in formats that don't yet easily support iteratively processing the file. This is a big problem for us as the services need to be sized to handle these large requirement, but are actually only running for a few minutes a day with this load.

This post will show one approach to fix the third problem. The same tooling (KEDA) can also be used to improve the other situations but that's for another blog post. What we'll do to address the final problem is to use KEDA Job based scaling.

First, we have the publisher. This could be anything and in our example it's normally from a cron job. The send will look like this, all normal MassTransit code:

c#
var endpoint = app.Services.GetRequiredService<IPublishEndpoint>();
+endpoint.Publish(new ProcessVeryLargeFileTrigger(fileName)

ProcessVeryLargeFileTrigger is defined in an assembly that both producer and consumer can use.

Now, we need an entrypoint that allows us to read one message from the MassTransit queue (rabbit is used here), give it to MassTransit, then exit.

First, our entrypoint needs to have a masstransit consumer, in memory.

c#
# Setup 
+HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
+builder.Services.AddMassTransit(x =>
+{
+    x.AddConsumer<ProcessVeryLargeFileConsumer>();
+
+    x.UsingInMemory();
+    x.AddConfigureEndpointsCallback((_, cfg) =>
+    {
+        # Optional, we like this format
+        cfg.UseRawJsonSerializer(isDefault: true);
+    });
+});

Next, we need to connect and bind to a rabbit queue:

c#
ConnectionFactory factory = new ConnectionFactory
+{
+  UserName = "<get this from appsettings>",
+  Password = "<get this from appsettings>",
+  VirtualHost = "/",
+  HostName = "example.default"
+};
+
+IConnection conn = factory.CreateConnection();
+IModel channel = conn.CreateModel();
+
+# Your exchange and queues are likely different
+var exchangeName = "LongRunningJobs";
+var queueName = "LongRunningJobs";
+var routingKey = "LongRunningJobs";
+channel.ExchangeDeclare(exchangeName, ExchangeType.Direct);
+channel.QueueDeclare(queueName, false, false, false, null);
+channel.QueueBind(queueName, exchangeName, routingKey, null);

After this, we need to try and get a single message from Rabbit. If there's nothing here, we exit.

c#
BasicGetResult message = channel.BasicGet("LongRunningJobs", false);
+if (message == null) {
+  // No message to pickup
+  return;
+}

We can then build our normal app and inject the RabbitMQ message into it:

c#
var app = builder.Build();
+await app.StartAsync();
+using var scope = app.Services.CreateScope();
+var ep = scope.ServiceProvider.GetRequiredService<ProcessVeryLargeFileConsumer<SubmitOrderConsumer>>();

Next, inject the message:

c#
try
+{
+    var messageBytes = message.Body.ToArray()
+    var headers = result.BasicProperties.Headers.AsReadOnly();
+    await ep.Dispatch(result.Body.ToArray(), headers, CancellationToken.None);
+    channel.BasicAck(result.DeliveryTag, false);
+} 
+catch 
+{
+    channel.BasicNack(result.DeliveryTag, false, true);
+}

Finally, we can cleanup:

c#
channel.Close();
+conn.Close();
+await app.StopAsync();

Of course, this is just the basics, your real solution would include logging and some other error handling (poison messages and so on should be handled, otherwise it will continue processing bad messages over and over).

This post now assumes you can build this into a docker image and push it to a registry you use. For this example, we'll assume the image name is example-long:latest

At this point, you have a working application that will pick up a single message, handle it, and finish. We need some way to trigger this to happen for each single message in the queue - for this comes KEDA. First, you need a secret containing the RabbitMQ connection string:

yaml
apiVersion: v1
+kind: Secret
+metadata:
+  name: keda-rabbitmq-secret
+  namespace: limited
+data:
+  host: >-
+    <your base 64 encoded connection string>
+type: Opaque

You need a trigger authentication:

yaml
apiVersion: keda.sh/v1alpha1
+kind: TriggerAuthentication
+metadata:
+  name: keda-trigger-auth-rabbitmq-conn
+  namespace: limited
+spec:
+  secretTargetRef:
+    - parameter: host
+      name: keda-rabbitmq-secret
+      key: host

And the important bit: you need a ScaledJob spec that runs your docker image

yaml
apiVersion: keda.sh/v1alpha1
+kind: ScaledJob
+metadata:
+  name: rabbitmq-consumer
+  namespace: limited
+spec:
+  jobTargetRef:
+    template:
+      spec:
+        containers:
+        - name: example-long
+          image: example-long:latest
+          imagePullPolicy: Never
+        restartPolicy: Never
+    backoffLimit: 4
+  pollingInterval: 2             # Optional. Default: 30 seconds
+  maxReplicaCount: 30             # Optional. Default: 100
+  successfulJobsHistoryLimit: 30   # Optional. Default: 100. How many completed jobs should be kept.
+  failedJobsHistoryLimit: 20       # Optional. Default: 100. How many failed jobs should be kept.
+  triggers:
+  - type: rabbitmq
+    metadata:
+      queueName: LongRunningJobs
+      queueLength: '1'
+    authenticationRef:
+      name: keda-trigger-auth-rabbitmq-conn

Now, when you put a message into the queue, it'll be processed as a MassTransit message in a Kubernetes Job, allowing you to run your normal worker pods with lower memory and CPU requirements.

You also have a few other benefits:

  • Deploys of the service don't kill a long running job
  • Jobs can be scheduled on other nodes
  • Jobs can be scheduled as lower priority
+ + + + \ No newline at end of file diff --git a/posts/scaling-masstransit-jobs.md b/posts/scaling-masstransit-jobs.md deleted file mode 100644 index cdc319a..0000000 --- a/posts/scaling-masstransit-jobs.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -title: Running MassTransit queues with KEDA -date: 2023-09-04 -author: Stephen Brown ---- - -We have an interesting challenge in some of our microservices - we run lots and lots of microservices, all using a relatively small amount of memory, but the usage patterns may be unusual. -All these microservices talk MassTransit - some also have external triggers, API endpoints or scheduled jobs. - ---- - -They run like this: - - Most are processing constantly lots of small messages. These are no problem. - - Some process only a few messages a day, but we have to keep the service running all day. This is annoying. - - Some process only a few messages per day, but those small messages take a lot of CPU and memory. For example they may need to deal with files in memory in formats that don't yet easily support iteratively processing the file. This is a big problem for us as the services need to be sized to handle these large requirement, but are actually only running for a few minutes a day with this load. - -This post will show one approach to fix the third problem. The same tooling (KEDA) can also be used to improve the other situations but that's for another blog post. -What we'll do to address the final problem is to use KEDA Job based scaling. - -First, we have the publisher. This could be anything and in our example it's normally from a cron job. The send will look like this, all normal MassTransit code: - -```c# -var endpoint = app.Services.GetRequiredService(); -await endpoint.Publish(new ProcessVeryLargeFileTrigger(fileName)); -``` - -`ProcessVeryLargeFileTrigger` is defined in an assembly that both producer and consumer can use. - -Now, we need an entrypoint that allows us to read one message from the MassTransit queue (RabbitMQ is used here), give it to MassTransit, then exit. - -First, our entrypoint needs to have a MassTransit consumer, in memory. - -```c# - -// Setup -HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); -builder.Services.AddMassTransit(x => -{ - x.AddConsumer(); - - x.UsingInMemory(); - x.AddConfigureEndpointsCallback((_, cfg) => - { - // Optional, we like this format - cfg.UseRawJsonSerializer(isDefault: true); - }); -}); -``` - -Next, we need to connect and bind to a RabbitMQ queue: - -```c# -ConnectionFactory factory = new ConnectionFactory -{ - UserName = "", - Password = "", - VirtualHost = "/", - HostName = "example.default" -}; - -IConnection conn = factory.CreateConnection(); -IModel channel = conn.CreateModel(); - -# Your exchange and queues are likely different -var exchangeName = "LongRunningJobs"; -var queueName = "LongRunningJobs"; -var routingKey = "LongRunningJobs"; -channel.ExchangeDeclare(exchangeName, ExchangeType.Direct); -channel.QueueDeclare(queueName, false, false, false, null); -channel.QueueBind(queueName, exchangeName, routingKey, null); -``` - -After this, we need to try and get a single message from RabbitMQ. If there's nothing here, we exit. - -```c# -BasicGetResult message = channel.BasicGet("LongRunningJobs", false); -if (message == null) { - // No message to pickup - return; -} - -``` - -We can then build our normal app and inject the RabbitMQ message into it: - -```c# -var app = builder.Build(); -await app.StartAsync(); -using var scope = app.Services.CreateScope(); -var ep = scope.ServiceProvider.GetRequiredService(); -``` - -Next, inject the message: -```c# -try -{ - var messageBytes = message.Body.ToArray(); - var headers = message.BasicProperties.Headers; - await ep.Dispatch(messageBytes, headers, CancellationToken.None); - channel.BasicAck(message.DeliveryTag, false); -} -catch -{ - channel.BasicNack(message.DeliveryTag, false, true); -} -``` - -Finally, we can clean up: -```c# -channel.Close(); -conn.Close(); -await app.StopAsync(); -``` - -Of course, this is just the basics, your real solution would include logging and some other error handling (poison messages and so on should be handled, otherwise it will continue processing bad messages over and over). - -This post now assumes you can build this into a docker image and push it to a registry you use. For this example, we'll assume the image name is `example-long:latest` - -At this point, you have a working application that will pick up a single message, handle it, and finish. We need some way to trigger this to happen for each single message in the queue - for this comes KEDA. -First, you need a secret containing the RabbitMQ connection string: - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: keda-rabbitmq-secret - namespace: limited -data: - host: >- - -type: Opaque -``` - -You need a trigger authentication: -```yaml -apiVersion: keda.sh/v1alpha1 -kind: TriggerAuthentication -metadata: - name: keda-trigger-auth-rabbitmq-conn - namespace: limited -spec: - secretTargetRef: - - parameter: host - name: keda-rabbitmq-secret - key: host -``` - -And the important bit: you need a ScaledJob spec that runs your docker image -```yaml -apiVersion: keda.sh/v1alpha1 -kind: ScaledJob -metadata: - name: rabbitmq-consumer - namespace: limited -spec: - jobTargetRef: - template: - spec: - containers: - - name: example-long - image: example-long:latest - imagePullPolicy: Never - restartPolicy: Never - backoffLimit: 4 - pollingInterval: 2 # Optional. Default: 30 seconds - maxReplicaCount: 30 # Optional. Default: 100 - successfulJobsHistoryLimit: 30 # Optional. Default: 100. How many completed jobs should be kept. - failedJobsHistoryLimit: 20 # Optional. Default: 100. How many failed jobs should be kept. - triggers: - - type: rabbitmq - metadata: - queueName: LongRunningJobs - queueLength: '1' - authenticationRef: - name: keda-trigger-auth-rabbitmq-conn -``` - -Now, when you put a message into the queue, it'll be processed as a MassTransit message in a Kubernetes Job, allowing you to run your normal worker pods with lower memory and CPU requirements. - -You also have a few other benefits: - - Deploys of the service don't kill a long running job - - Jobs can be scheduled on other nodes - - Jobs can be scheduled as lower priority \ No newline at end of file diff --git a/posts/swiss-citizenship-timeline.md b/posts/swiss-citizenship-timeline.md deleted file mode 100644 index a5c8e36..0000000 --- a/posts/swiss-citizenship-timeline.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Zurich 2025 Citizenship Timeline -date: 2025-08-16 -author: Stephen Brown ---- - -This is the timeline of my journey towards obtaining Swiss citizenship. I will update this document as I progress through the various phases of the application process. -The process is different per Kanton, and changed a couple of years ago, so I document it here so people can see what the normal timeline is like. -I'm a UK citizen right now, but the process is relatively similar for other nationalities, with some differences in the required documents for the Zivilstandsregister (Civil Registry). -The official process and website is here for Zurich: [Ordentliche Einbürgerung](https://www.zh.ch/de/migration-integration/einbuergerung/ordentliche-einbuergerung.html). - -## My Timeline So Far - -| Phase | German name of Phase | Date / Period | Notes | -| ----- | --------------------- | --------------| ------| -| C Permit | C-Bewilligung| 2020 | I could apply for a C Permit after 5 years of residence in Switzerland. | -| UK Birth Certificate | | 2025-05-26 | I ordered a copy of my birth certificate from the UK government website | -| Filled in form for entry in Civil Register | Eintrag im Zivilstandsregister | 2025-06-03 | Don't be fooled to fill in the manual PDF that's linked on the main website - you can start the process electronically at [online form](https://www.stadt-zuerich.ch/de/lebenslagen/einwohner-services/einbuergerung/registrierung-im-zivilstandsregister.html) | -| Got email reply back from application form | | 2025-06-04 | I received an email confirming that my application was received - they asked to provide a copy of the passport and of the birth certificate | -| UK Birth Certificate received | | 2025-06-13 | Took about 2-3 weeks to arrive but can take longer | -| Sent documents by post to Zurich Civil registry office | | 2025-06-16 | Must be via post, they will confirm by email a day or so later | -| Confirmation form from Civil Registry office | | ~2025-08-01 | They ask you by post to sign and confirm the details. I was on holiday so only sent it back on the 12th | -| Civil status certificate received by Post | Personenstandsausweis | 2025-08-16 | Confirmation of entry in Civil registry | -| Created application at [naturalization.services.zh.ch](https://naturalization.services.zh.ch/) | eEinbürgerung | 2025-10-17 | This was a bit later than the 10 year mark, as I was waiting for my partner | -| Finished filling in, and submitted eEinbürgerung | eEinbürgerung eingereicht | 2025-10-28 | Most was very easy, but needed to write a page about hobbies, social interactions, clubs and so on | -| Update online: Moved into pre-examination | Vorprüfung | 2026-02-05 | Nothing for me to do - just a text/email notification | -| Update online: Moved into Municipal Naturalization Test | Prüfung Gemeinde | 2026-05-06 | Nothing for me to do - just a text/email notification | -| Letter from Canton and letter from city | Zustellung an Ihre Wohngeminde, Wann werden auch Sie Schweizer*in | 2026-05-18 | Notification that the Canton has transferred the application to the Gemeinde. Nothing for me to do | -| Test booklet and invitation | Ihr Einbürgerungsverfahren, Einladung zum Grundkenntnistest | 2026-06-24 | Test booklet and invitation to test - with available slots being earliest in August | - - -Now it's time to wait for the next steps, which could be 2-3 months for the Grundkenntnistest invitation. - -(Will update as progress happens.) diff --git a/posts/worklog-assistant.md b/posts/worklog-assistant.md deleted file mode 100644 index e469971..0000000 --- a/posts/worklog-assistant.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Creating Worklog Assistant - the background -date: 2024-10-05 -author: Stephen Brown ---- - -Creating Worklog Assistant - the background of creating a new tool as a replacement for an existing but now unavailable tool. - ---- - -This project started as a replacement for an existing tool called Worklog Assistant. This is a tool that can log time spent on JIRA tasks and was used quite intensively by my company. It cost some reasonable amount of money, and did a relatively simple job quite well. However, this year, everything related to this company disappeared from the internet. The JIRA confluence pages disappeared, the Twitter/X feed is gone, the website is gone, and the tool is gone. I have no idea what happened, but all the license payers are left with a tool that will stop working at some point in the future as the licenses expire. - -So - I set about creating my own tool - and the result is https://worklogassistant.app/ -It's written in Flutter for cross-platform compatibility and is installed or run from your desktop without needing any dependencies installed (for ease of use in corporate environments). -It's _blazingly_ fast, and is catching up with what the old platform did. It's not quite there yet, but it's getting there. - -The next series of blog posts will be about the journey of creating this tool. - -I expect to cover the following: - -- The initial design -- The implementation and flutter learning experience -- The GitHub Actions build pipeline -- The public website - -Hopefully this will show the process of creating a tool from scratch, and the setup needed to ensure that it's maintainable and can be built and deployed easily. - -Currently the tool isn't open source, and the billing isn't implemented - the billing will come soon. -Open source - likely not; however the process for building it will be documented here. - -![Worklog Assistant Dark Mode](/images/worklog-assistant-darkmode.png) \ No newline at end of file diff --git a/prettierrc b/prettierrc deleted file mode 100644 index 154465a..0000000 --- a/prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -semi: false -singleQuote: true -printWidth: 80 -trailingComma: none \ No newline at end of file diff --git a/public/images/vlc-tv7.jpg b/public/images/vlc-tv7.jpg deleted file mode 100644 index 618a1c2..0000000 Binary files a/public/images/vlc-tv7.jpg and /dev/null differ diff --git a/public/images/worklog-assistant-darkmode.png b/public/images/worklog-assistant-darkmode.png deleted file mode 100644 index 33897f6..0000000 Binary files a/public/images/worklog-assistant-darkmode.png and /dev/null differ diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index f078101..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "module": "NodeNext", - "allowJs": true, - "strict": true, - "jsx": "preserve" - }, - "include": ["./.vitepress/**/*"] -} \ No newline at end of file