diff --git a/packages/arg-completer/.gitattributes b/packages/arg-completer/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/packages/arg-completer/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/packages/arg-completer/.gitignore b/packages/arg-completer/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/packages/arg-completer/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/arg-completer/CLAUDE.md b/packages/arg-completer/CLAUDE.md new file mode 100644 index 0000000..1ee6890 --- /dev/null +++ b/packages/arg-completer/CLAUDE.md @@ -0,0 +1,106 @@ + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; + +// import .css files directly and it works +import './index.css'; + +import { createRoot } from "react-dom/client"; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`. diff --git a/packages/arg-completer/bun-env.d.ts b/packages/arg-completer/bun-env.d.ts new file mode 100644 index 0000000..72f1c26 --- /dev/null +++ b/packages/arg-completer/bun-env.d.ts @@ -0,0 +1,17 @@ +// Generated by `bun init` + +declare module "*.svg" { + /** + * A path to the SVG file + */ + const path: `${string}.svg`; + export = path; +} + +declare module "*.module.css" { + /** + * A record of class names to their corresponding CSS module classes + */ + const classes: { readonly [key: string]: string }; + export = classes; +} diff --git a/packages/arg-completer/bun.lock b/packages/arg-completer/bun.lock new file mode 100644 index 0000000..f743215 --- /dev/null +++ b/packages/arg-completer/bun.lock @@ -0,0 +1,29 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "bun-react-template", + "dependencies": { + "hono": "^4.9.7", + }, + "devDependencies": { + "@types/bun": "latest", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="], + + "@types/node": ["@types/node@24.5.0", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg=="], + + "@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="], + + "bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "hono": ["hono@4.9.7", "", {}, "sha512-t4Te6ERzIaC48W3x4hJmBwgNlLhmiEdEE5ViYb02ffw4ignHNHa5IBtPjmbKstmtKa8X6C35iWwK4HaqvrzG9w=="], + + "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="], + } +} diff --git a/packages/arg-completer/bunfig.toml b/packages/arg-completer/bunfig.toml new file mode 100644 index 0000000..9819bf6 --- /dev/null +++ b/packages/arg-completer/bunfig.toml @@ -0,0 +1,2 @@ +[serve.static] +env = "BUN_PUBLIC_*" \ No newline at end of file diff --git a/packages/arg-completer/package.json b/packages/arg-completer/package.json new file mode 100644 index 0000000..56eca19 --- /dev/null +++ b/packages/arg-completer/package.json @@ -0,0 +1,19 @@ +{ + "name": "complate", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "bun --hot src/server.tsx" + }, + "dependencies": { + "hono": "^4.9.7" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "prettier": { + "semi": false, + "printWidth": 100 + } +} \ No newline at end of file diff --git a/packages/arg-completer/public/img/cow.jpeg b/packages/arg-completer/public/img/cow.jpeg new file mode 100644 index 0000000..65795fb Binary files /dev/null and b/packages/arg-completer/public/img/cow.jpeg differ diff --git a/packages/arg-completer/public/vendor/C64_Pro-STYLE.woff2 b/packages/arg-completer/public/vendor/C64_Pro-STYLE.woff2 new file mode 100644 index 0000000..1d0a8d6 Binary files /dev/null and b/packages/arg-completer/public/vendor/C64_Pro-STYLE.woff2 differ diff --git a/packages/arg-completer/public/vendor/C64_Pro_Mono-STYLE.woff2 b/packages/arg-completer/public/vendor/C64_Pro_Mono-STYLE.woff2 new file mode 100644 index 0000000..a1b72c5 Binary files /dev/null and b/packages/arg-completer/public/vendor/C64_Pro_Mono-STYLE.woff2 differ diff --git a/packages/arg-completer/public/vendor/pico.fuchsia.css b/packages/arg-completer/public/vendor/pico.fuchsia.css new file mode 100644 index 0000000..bc7ce4a --- /dev/null +++ b/packages/arg-completer/public/vendor/pico.fuchsia.css @@ -0,0 +1,4 @@ +@charset "UTF-8";/*! + * Pico CSS ✨ v2.1.1 (https://picocss.com) + * Copyright 2019-2025 - Licensed under MIT + */:host,:root{--pico-font-family-emoji:"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--pico-font-family-sans-serif:system-ui,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,Helvetica,Arial,"Helvetica Neue",sans-serif,var(--pico-font-family-emoji);--pico-font-family-monospace:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace,var(--pico-font-family-emoji);--pico-font-family:var(--pico-font-family-sans-serif);--pico-line-height:1.5;--pico-font-weight:400;--pico-font-size:100%;--pico-text-underline-offset:0.1rem;--pico-border-radius:0.25rem;--pico-border-width:0.0625rem;--pico-outline-width:0.125rem;--pico-transition:0.2s ease-in-out;--pico-spacing:1rem;--pico-typography-spacing-vertical:1rem;--pico-block-spacing-vertical:var(--pico-spacing);--pico-block-spacing-horizontal:var(--pico-spacing);--pico-form-element-spacing-vertical:0.75rem;--pico-form-element-spacing-horizontal:1rem;--pico-group-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-group-box-shadow-focus-with-button:0 0 0 var(--pico-outline-width) var(--pico-primary-focus);--pico-group-box-shadow-focus-with-input:0 0 0 0.0625rem var(--pico-form-element-border-color);--pico-modal-overlay-backdrop-filter:blur(0.375rem);--pico-nav-element-spacing-vertical:1rem;--pico-nav-element-spacing-horizontal:0.5rem;--pico-nav-link-spacing-vertical:0.5rem;--pico-nav-link-spacing-horizontal:0.5rem;--pico-nav-breadcrumb-divider:">";--pico-icon-checkbox:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-minus:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--pico-icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-date:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--pico-icon-time:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-search:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--pico-icon-close:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(136, 145, 164)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--pico-icon-loading:url("data:image/svg+xml,%3Csvg fill='none' height='24' width='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' %3E%3Cstyle%3E g %7B animation: rotate 2s linear infinite; transform-origin: center center; %7D circle %7B stroke-dasharray: 75,100; stroke-dashoffset: -5; animation: dash 1.5s ease-in-out infinite; stroke-linecap: round; %7D @keyframes rotate %7B 0%25 %7B transform: rotate(0deg); %7D 100%25 %7B transform: rotate(360deg); %7D %7D @keyframes dash %7B 0%25 %7B stroke-dasharray: 1,100; stroke-dashoffset: 0; %7D 50%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -17.5; %7D 100%25 %7B stroke-dasharray: 44.5,100; stroke-dashoffset: -62; %7D %7D %3C/style%3E%3Cg%3E%3Ccircle cx='12' cy='12' r='10' fill='none' stroke='rgb(136, 145, 164)' stroke-width='4' /%3E%3C/g%3E%3C/svg%3E")}@media (min-width:576px){:host,:root{--pico-font-size:106.25%}}@media (min-width:768px){:host,:root{--pico-font-size:112.5%}}@media (min-width:1024px){:host,:root{--pico-font-size:118.75%}}@media (min-width:1280px){:host,:root{--pico-font-size:125%}}@media (min-width:1536px){:host,:root{--pico-font-size:131.25%}}a{--pico-text-decoration:underline}small{--pico-font-size:0.875em}h1,h2,h3,h4,h5,h6{--pico-font-weight:700}h1{--pico-font-size:2rem;--pico-line-height:1.125;--pico-typography-spacing-top:3rem}h2{--pico-font-size:1.75rem;--pico-line-height:1.15;--pico-typography-spacing-top:2.625rem}h3{--pico-font-size:1.5rem;--pico-line-height:1.175;--pico-typography-spacing-top:2.25rem}h4{--pico-font-size:1.25rem;--pico-line-height:1.2;--pico-typography-spacing-top:1.874rem}h5{--pico-font-size:1.125rem;--pico-line-height:1.225;--pico-typography-spacing-top:1.6875rem}h6{--pico-font-size:1rem;--pico-line-height:1.25;--pico-typography-spacing-top:1.5rem}tfoot td,tfoot th,thead td,thead th{--pico-font-weight:600;--pico-border-width:0.1875rem}code,kbd,pre,samp{--pico-font-family:var(--pico-font-family-monospace)}kbd{--pico-font-weight:bolder}:where(select,textarea),input:not([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-outline-width:0.0625rem}[type=search]{--pico-border-radius:5rem}[type=checkbox],[type=radio]{--pico-border-width:0.125rem}[type=checkbox][role=switch]{--pico-border-width:0.1875rem}[role=search]{--pico-border-radius:5rem}[role=group] [role=button],[role=group] [type=button],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=submit],[role=search] button{--pico-form-element-spacing-horizontal:2rem}details summary[role=button]::after{filter:brightness(0) invert(1)}[aria-busy=true]:not(input,select,textarea):is(button,[type=submit],[type=button],[type=reset],[role=button])::before{filter:brightness(0) invert(1)}:host(:not([data-theme=dark])),:root:not([data-theme=dark]),[data-theme=light]{color-scheme:light;--pico-background-color:#fff;--pico-color:#373c44;--pico-text-selection-color:rgba(247, 72, 183, 0.25);--pico-muted-color:#646b79;--pico-muted-border-color:rgb(231, 234, 239.5);--pico-primary:#c1208b;--pico-primary-background:#c1208b;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(193, 32, 139, 0.5);--pico-primary-hover:#98176d;--pico-primary-hover-background:#ac1c7c;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(247, 72, 183, 0.5);--pico-primary-inverse:#fff;--pico-secondary:#5d6b89;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(93, 107, 137, 0.5);--pico-secondary-hover:#48536b;--pico-secondary-hover-background:#48536b;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(93, 107, 137, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#181c25;--pico-contrast-background:#181c25;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(24, 28, 37, 0.5);--pico-contrast-hover:#000;--pico-contrast-hover-background:#000;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-secondary-hover);--pico-contrast-focus:rgba(93, 107, 137, 0.25);--pico-contrast-inverse:#fff;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(129, 145, 181, 0.01698),0.0335rem 0.067rem 0.402rem rgba(129, 145, 181, 0.024),0.0625rem 0.125rem 0.75rem rgba(129, 145, 181, 0.03),0.1125rem 0.225rem 1.35rem rgba(129, 145, 181, 0.036),0.2085rem 0.417rem 2.502rem rgba(129, 145, 181, 0.04302),0.5rem 1rem 6rem rgba(129, 145, 181, 0.06),0 0 0 0.0625rem rgba(129, 145, 181, 0.015);--pico-h1-color:#2d3138;--pico-h2-color:#373c44;--pico-h3-color:#424751;--pico-h4-color:#4d535e;--pico-h5-color:#5c6370;--pico-h6-color:#646b79;--pico-mark-background-color:rgb(252.5, 230.5, 191.5);--pico-mark-color:#0f1114;--pico-ins-color:rgb(28.5, 105.5, 84);--pico-del-color:rgb(136, 56.5, 53);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(243, 244.5, 246.75);--pico-code-color:#646b79;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(251, 251.5, 252.25);--pico-form-element-selected-background-color:#dfe3eb;--pico-form-element-border-color:#cfd5e2;--pico-form-element-color:#23262c;--pico-form-element-placeholder-color:var(--pico-muted-color);--pico-form-element-active-background-color:#fff;--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(183.5, 105.5, 106.5);--pico-form-element-invalid-active-border-color:rgb(200.25, 79.25, 72.25);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:rgb(76, 154.5, 137.5);--pico-form-element-valid-active-border-color:rgb(39, 152.75, 118.75);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#bfc7d9;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#dfe3eb;--pico-range-active-border-color:#bfc7d9;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:var(--pico-background-color);--pico-card-border-color:var(--pico-muted-border-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(251, 251.5, 252.25);--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(232, 234, 237, 0.75);--pico-progress-background-color:#dfe3eb;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(76, 154.5, 137.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(200.25, 79.25, 72.25)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}:host(:not([data-theme=dark])) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),:root:not([data-theme=dark]) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),[data-theme=light] input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}@media only screen and (prefers-color-scheme:dark){:host(:not([data-theme])),:root:not([data-theme]){color-scheme:dark;--pico-background-color:rgb(19, 22.5, 30.5);--pico-color:#c2c7d0;--pico-text-selection-color:rgba(248, 105, 191, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#f869bf;--pico-primary-background:#c1208b;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(248, 105, 191, 0.5);--pico-primary-hover:#fa9acf;--pico-primary-hover-background:#d9269d;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(248, 105, 191, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(7, 8.5, 12, 0.01698),0.0335rem 0.067rem 0.402rem rgba(7, 8.5, 12, 0.024),0.0625rem 0.125rem 0.75rem rgba(7, 8.5, 12, 0.03),0.1125rem 0.225rem 1.35rem rgba(7, 8.5, 12, 0.036),0.2085rem 0.417rem 2.502rem rgba(7, 8.5, 12, 0.04302),0.5rem 1rem 6rem rgba(7, 8.5, 12, 0.06),0 0 0 0.0625rem rgba(7, 8.5, 12, 0.015);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:rgb(205.5, 126, 123);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(26, 30.5, 40.25);--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(28, 33, 43.5);--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:rgb(26, 30.5, 40.25);--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(149.5, 74, 80);--pico-form-element-invalid-active-border-color:rgb(183.25, 63.5, 59);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:rgb(22, 137, 105.5);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(26, 30.5, 40.25);--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(7.5, 8.5, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}:host(:not([data-theme])) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]),:root:not([data-theme]) input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}}[data-theme=dark]{color-scheme:dark;--pico-background-color:rgb(19, 22.5, 30.5);--pico-color:#c2c7d0;--pico-text-selection-color:rgba(248, 105, 191, 0.1875);--pico-muted-color:#7b8495;--pico-muted-border-color:#202632;--pico-primary:#f869bf;--pico-primary-background:#c1208b;--pico-primary-border:var(--pico-primary-background);--pico-primary-underline:rgba(248, 105, 191, 0.5);--pico-primary-hover:#fa9acf;--pico-primary-hover-background:#d9269d;--pico-primary-hover-border:var(--pico-primary-hover-background);--pico-primary-hover-underline:var(--pico-primary-hover);--pico-primary-focus:rgba(248, 105, 191, 0.375);--pico-primary-inverse:#fff;--pico-secondary:#969eaf;--pico-secondary-background:#525f7a;--pico-secondary-border:var(--pico-secondary-background);--pico-secondary-underline:rgba(150, 158, 175, 0.5);--pico-secondary-hover:#b3b9c5;--pico-secondary-hover-background:#5d6b89;--pico-secondary-hover-border:var(--pico-secondary-hover-background);--pico-secondary-hover-underline:var(--pico-secondary-hover);--pico-secondary-focus:rgba(144, 158, 190, 0.25);--pico-secondary-inverse:#fff;--pico-contrast:#dfe3eb;--pico-contrast-background:#eff1f4;--pico-contrast-border:var(--pico-contrast-background);--pico-contrast-underline:rgba(223, 227, 235, 0.5);--pico-contrast-hover:#fff;--pico-contrast-hover-background:#fff;--pico-contrast-hover-border:var(--pico-contrast-hover-background);--pico-contrast-hover-underline:var(--pico-contrast-hover);--pico-contrast-focus:rgba(207, 213, 226, 0.25);--pico-contrast-inverse:#000;--pico-box-shadow:0.0145rem 0.029rem 0.174rem rgba(7, 8.5, 12, 0.01698),0.0335rem 0.067rem 0.402rem rgba(7, 8.5, 12, 0.024),0.0625rem 0.125rem 0.75rem rgba(7, 8.5, 12, 0.03),0.1125rem 0.225rem 1.35rem rgba(7, 8.5, 12, 0.036),0.2085rem 0.417rem 2.502rem rgba(7, 8.5, 12, 0.04302),0.5rem 1rem 6rem rgba(7, 8.5, 12, 0.06),0 0 0 0.0625rem rgba(7, 8.5, 12, 0.015);--pico-h1-color:#f0f1f3;--pico-h2-color:#e0e3e7;--pico-h3-color:#c2c7d0;--pico-h4-color:#b3b9c5;--pico-h5-color:#a4acba;--pico-h6-color:#8891a4;--pico-mark-background-color:#014063;--pico-mark-color:#fff;--pico-ins-color:#62af9a;--pico-del-color:rgb(205.5, 126, 123);--pico-blockquote-border-color:var(--pico-muted-border-color);--pico-blockquote-footer-color:var(--pico-muted-color);--pico-button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-table-border-color:var(--pico-muted-border-color);--pico-table-row-stripped-background-color:rgba(111, 120, 135, 0.0375);--pico-code-background-color:rgb(26, 30.5, 40.25);--pico-code-color:#8891a4;--pico-code-kbd-background-color:var(--pico-color);--pico-code-kbd-color:var(--pico-background-color);--pico-form-element-background-color:rgb(28, 33, 43.5);--pico-form-element-selected-background-color:#2a3140;--pico-form-element-border-color:#2a3140;--pico-form-element-color:#e0e3e7;--pico-form-element-placeholder-color:#8891a4;--pico-form-element-active-background-color:rgb(26, 30.5, 40.25);--pico-form-element-active-border-color:var(--pico-primary-border);--pico-form-element-focus-color:var(--pico-primary-border);--pico-form-element-disabled-opacity:0.5;--pico-form-element-invalid-border-color:rgb(149.5, 74, 80);--pico-form-element-invalid-active-border-color:rgb(183.25, 63.5, 59);--pico-form-element-invalid-focus-color:var(--pico-form-element-invalid-active-border-color);--pico-form-element-valid-border-color:#2a7b6f;--pico-form-element-valid-active-border-color:rgb(22, 137, 105.5);--pico-form-element-valid-focus-color:var(--pico-form-element-valid-active-border-color);--pico-switch-background-color:#333c4e;--pico-switch-checked-background-color:var(--pico-primary-background);--pico-switch-color:#fff;--pico-switch-thumb-box-shadow:0 0 0 rgba(0, 0, 0, 0);--pico-range-border-color:#202632;--pico-range-active-border-color:#2a3140;--pico-range-thumb-border-color:var(--pico-background-color);--pico-range-thumb-color:var(--pico-secondary-background);--pico-range-thumb-active-color:var(--pico-primary-background);--pico-accordion-border-color:var(--pico-muted-border-color);--pico-accordion-active-summary-color:var(--pico-primary-hover);--pico-accordion-close-summary-color:var(--pico-color);--pico-accordion-open-summary-color:var(--pico-muted-color);--pico-card-background-color:#181c25;--pico-card-border-color:var(--pico-card-background-color);--pico-card-box-shadow:var(--pico-box-shadow);--pico-card-sectioning-background-color:rgb(26, 30.5, 40.25);--pico-loading-spinner-opacity:0.5;--pico-modal-overlay-background-color:rgba(7.5, 8.5, 10, 0.75);--pico-progress-background-color:#202632;--pico-progress-color:var(--pico-primary-background);--pico-tooltip-background-color:var(--pico-contrast-background);--pico-tooltip-color:var(--pico-contrast-inverse);--pico-icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(42, 123, 111)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--pico-icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(149.5, 74, 80)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E")}[data-theme=dark] input:is([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[type=file]){--pico-form-element-focus-color:var(--pico-primary-focus)}[type=checkbox],[type=radio],[type=range],progress{accent-color:var(--pico-primary)}*,::after,::before{box-sizing:border-box;background-repeat:no-repeat}::after,::before{text-decoration:inherit;vertical-align:inherit}:where(:host),:where(:root){-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family);text-underline-offset:var(--pico-text-underline-offset);text-rendering:optimizeLegibility;overflow-wrap:break-word;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{width:100%;margin:0}main{display:block}body>footer,body>header,body>main{width:100%;margin-right:auto;margin-left:auto;padding:var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal)}@media (min-width:576px){body>footer,body>header,body>main{max-width:510px;padding-right:0;padding-left:0}}@media (min-width:768px){body>footer,body>header,body>main{max-width:700px}}@media (min-width:1024px){body>footer,body>header,body>main{max-width:950px}}@media (min-width:1280px){body>footer,body>header,body>main{max-width:1200px}}@media (min-width:1536px){body>footer,body>header,body>main{max-width:1450px}}section{margin-bottom:var(--pico-block-spacing-vertical)}b,strong{font-weight:bolder}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}address,blockquote,dl,ol,p,pre,table,ul{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-style:normal;font-weight:var(--pico-font-weight)}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:var(--pico-typography-spacing-vertical);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:var(--pico-font-size);line-height:var(--pico-line-height);font-family:var(--pico-font-family)}h1{--pico-color:var(--pico-h1-color)}h2{--pico-color:var(--pico-h2-color)}h3{--pico-color:var(--pico-h3-color)}h4{--pico-color:var(--pico-h4-color)}h5{--pico-color:var(--pico-h5-color)}h6{--pico-color:var(--pico-h6-color)}:where(article,address,blockquote,dl,figure,form,ol,p,pre,table,ul)~:is(h1,h2,h3,h4,h5,h6){margin-top:var(--pico-typography-spacing-top)}p{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup{margin-bottom:var(--pico-typography-spacing-vertical)}hgroup>*{margin-top:0;margin-bottom:0}hgroup>:not(:first-child):last-child{--pico-color:var(--pico-muted-color);--pico-font-weight:unset;font-size:1rem}:where(ol,ul) li{margin-bottom:calc(var(--pico-typography-spacing-vertical) * .25)}:where(dl,ol,ul) :where(dl,ol,ul){margin:0;margin-top:calc(var(--pico-typography-spacing-vertical) * .25)}ul li{list-style:square}mark{padding:.125rem .25rem;background-color:var(--pico-mark-background-color);color:var(--pico-mark-color);vertical-align:baseline}blockquote{display:block;margin:var(--pico-typography-spacing-vertical) 0;padding:var(--pico-spacing);border-right:none;border-left:.25rem solid var(--pico-blockquote-border-color);border-inline-start:0.25rem solid var(--pico-blockquote-border-color);border-inline-end:none}blockquote footer{margin-top:calc(var(--pico-typography-spacing-vertical) * .5);color:var(--pico-blockquote-footer-color)}abbr[title]{border-bottom:1px dotted;text-decoration:none;cursor:help}ins{color:var(--pico-ins-color);text-decoration:none}del{color:var(--pico-del-color)}::-moz-selection{background-color:var(--pico-text-selection-color)}::selection{background-color:var(--pico-text-selection-color)}:where(a:not([role=button])),[role=link]{--pico-color:var(--pico-primary);--pico-background-color:transparent;--pico-underline:var(--pico-primary-underline);outline:0;background-color:var(--pico-background-color);color:var(--pico-color);-webkit-text-decoration:var(--pico-text-decoration);text-decoration:var(--pico-text-decoration);text-decoration-color:var(--pico-underline);text-underline-offset:0.125em;transition:background-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),color var(--pico-transition),text-decoration var(--pico-transition),box-shadow var(--pico-transition),-webkit-text-decoration var(--pico-transition)}:where(a:not([role=button])):is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[role=link]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-color:var(--pico-primary-hover);--pico-underline:var(--pico-primary-hover-underline);--pico-text-decoration:underline}:where(a:not([role=button])):focus-visible,[role=link]:focus-visible{box-shadow:0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}a[role=button]{display:inline-block}button{margin:0;overflow:visible;font-family:inherit;text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[role=button],[type=button],[type=file]::file-selector-button,[type=reset],[type=submit],button{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);--pico-color:var(--pico-primary-inverse);--pico-box-shadow:var(--pico-button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);font-size:1rem;line-height:var(--pico-line-height);text-align:center;text-decoration:none;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}[role=button]:is(:hover,:active,:focus),[role=button]:is([aria-current]:not([aria-current=false])),[type=button]:is(:hover,:active,:focus),[type=button]:is([aria-current]:not([aria-current=false])),[type=file]::file-selector-button:is(:hover,:active,:focus),[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])),[type=reset]:is(:hover,:active,:focus),[type=reset]:is([aria-current]:not([aria-current=false])),[type=submit]:is(:hover,:active,:focus),[type=submit]:is([aria-current]:not([aria-current=false])),button:is(:hover,:active,:focus),button:is([aria-current]:not([aria-current=false])){--pico-background-color:var(--pico-primary-hover-background);--pico-border-color:var(--pico-primary-hover-border);--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));--pico-color:var(--pico-primary-inverse)}[role=button]:focus,[role=button]:is([aria-current]:not([aria-current=false])):focus,[type=button]:focus,[type=button]:is([aria-current]:not([aria-current=false])):focus,[type=file]::file-selector-button:focus,[type=file]::file-selector-button:is([aria-current]:not([aria-current=false])):focus,[type=reset]:focus,[type=reset]:is([aria-current]:not([aria-current=false])):focus,[type=submit]:focus,[type=submit]:is([aria-current]:not([aria-current=false])):focus,button:focus,button:is([aria-current]:not([aria-current=false])):focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-primary-focus)}[type=button],[type=reset],[type=submit]{margin-bottom:var(--pico-spacing)}[type=file]::file-selector-button,[type=reset]{--pico-background-color:var(--pico-secondary-background);--pico-border-color:var(--pico-secondary-border);--pico-color:var(--pico-secondary-inverse);cursor:pointer}[type=file]::file-selector-button:is([aria-current]:not([aria-current=false]),:hover,:active,:focus),[type=reset]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border);--pico-color:var(--pico-secondary-inverse)}[type=file]::file-selector-button:focus,[type=reset]:focus{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}:where(button,[type=submit],[type=reset],[type=button],[role=button])[disabled],:where(fieldset[disabled]) :is(button,[type=submit],[type=button],[type=reset],[role=button]){opacity:.5;pointer-events:none}:where(table){width:100%;border-collapse:collapse;border-spacing:0;text-indent:0}td,th{padding:calc(var(--pico-spacing)/ 2) var(--pico-spacing);border-bottom:var(--pico-border-width) solid var(--pico-table-border-color);background-color:var(--pico-background-color);color:var(--pico-color);font-weight:var(--pico-font-weight);text-align:left;text-align:start}tfoot td,tfoot th{border-top:var(--pico-border-width) solid var(--pico-table-border-color);border-bottom:0}table.striped tbody tr:nth-child(odd) td,table.striped tbody tr:nth-child(odd) th{background-color:var(--pico-table-row-stripped-background-color)}:where(audio,canvas,iframe,img,svg,video){vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}:where(iframe){border-style:none}img{max-width:100%;height:auto;border-style:none}:where(svg:not([fill])){fill:currentColor}svg:not(:host),svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-size:.875em;font-family:var(--pico-font-family)}pre code,pre samp{font-size:inherit;font-family:inherit}pre{-ms-overflow-style:scrollbar;overflow:auto}code,kbd,pre,samp{border-radius:var(--pico-border-radius);background:var(--pico-code-background-color);color:var(--pico-code-color);font-weight:var(--pico-font-weight);line-height:initial}code,kbd,samp{display:inline-block;padding:.375rem}pre{display:block;margin-bottom:var(--pico-spacing);overflow-x:auto}pre>code,pre>samp{display:block;padding:var(--pico-spacing);background:0 0;line-height:var(--pico-line-height)}kbd{background-color:var(--pico-code-kbd-background-color);color:var(--pico-code-kbd-color);vertical-align:baseline}figure{display:block;margin:0;padding:0}figure figcaption{padding:calc(var(--pico-spacing) * .5) 0;color:var(--pico-muted-color)}hr{height:0;margin:var(--pico-typography-spacing-vertical) 0;border:0;border-top:1px solid var(--pico-muted-border-color);color:inherit}[hidden],template{display:none!important}canvas{display:inline-block}input,optgroup,select,textarea{margin:0;font-size:1rem;line-height:var(--pico-line-height);font-family:inherit;letter-spacing:inherit}input{overflow:visible}select{text-transform:none}legend{max-width:100%;padding:0;color:inherit;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{padding:0;border-style:none}:-moz-focusring{outline:0}:-moz-ui-invalid{box-shadow:none}::-ms-expand{display:none}[type=file],[type=range]{padding:0;border-width:0}input:not([type=checkbox],[type=radio],[type=range]){height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2)}fieldset{width:100%;margin:0;margin-bottom:var(--pico-spacing);padding:0;border:0}fieldset legend,label{display:block;margin-bottom:calc(var(--pico-spacing) * .375);color:var(--pico-color);font-weight:var(--pico-form-label-font-weight,var(--pico-font-weight))}fieldset legend{margin-bottom:calc(var(--pico-spacing) * .5)}button[type=submit],input:not([type=checkbox],[type=radio]),select,textarea{width:100%}input:not([type=checkbox],[type=radio],[type=range],[type=file]),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal)}input,select,textarea{--pico-background-color:var(--pico-form-element-background-color);--pico-border-color:var(--pico-form-element-border-color);--pico-color:var(--pico-form-element-color);--pico-box-shadow:none;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:var(--pico-border-radius);outline:0;background-color:var(--pico-background-color);box-shadow:var(--pico-box-shadow);color:var(--pico-color);font-weight:var(--pico-font-weight);transition:background-color var(--pico-transition),border-color var(--pico-transition),color var(--pico-transition),box-shadow var(--pico-transition)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[readonly]):is(:active,:focus){--pico-background-color:var(--pico-form-element-active-background-color)}:where(select,textarea):not([readonly]):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[role=switch],[readonly]):is(:active,:focus){--pico-border-color:var(--pico-form-element-active-border-color)}:where(select,textarea):not([readonly]):focus,input:not([type=submit],[type=button],[type=reset],[type=range],[type=file],[readonly]):focus{--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-focus-color)}:where(fieldset[disabled]) :is(input:not([type=submit],[type=button],[type=reset]),select,textarea),input:not([type=submit],[type=button],[type=reset])[disabled],label[aria-disabled=true],select[disabled],textarea[disabled]{opacity:var(--pico-form-element-disabled-opacity);pointer-events:none}label[aria-disabled=true] input[disabled]{opacity:1}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid]{padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal)!important;padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem)!important;background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid=false]:not(select){background-image:var(--pico-icon-valid)}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week],[type=range])[aria-invalid=true]:not(select){background-image:var(--pico-icon-invalid)}:where(input,select,textarea)[aria-invalid=false]{--pico-border-color:var(--pico-form-element-valid-border-color)}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus){--pico-border-color:var(--pico-form-element-valid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus):not([type=checkbox],[type=radio]){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-valid-focus-color)!important}:where(input,select,textarea)[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus){--pico-border-color:var(--pico-form-element-invalid-active-border-color)!important}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus):not([type=checkbox],[type=radio]){--pico-box-shadow:0 0 0 var(--pico-outline-width) var(--pico-form-element-invalid-focus-color)!important}[dir=rtl] :where(input,select,textarea):not([type=checkbox],[type=radio]):is([aria-invalid],[aria-invalid=true],[aria-invalid=false]){background-position:center left .75rem}input::-webkit-input-placeholder,input::placeholder,select:invalid,textarea::-webkit-input-placeholder,textarea::placeholder{color:var(--pico-form-element-placeholder-color);opacity:1}input:not([type=checkbox],[type=radio]),select,textarea{margin-bottom:var(--pico-spacing)}select::-ms-expand{border:0;background-color:transparent}select:not([multiple],[size]){padding-right:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);padding-left:var(--pico-form-element-spacing-horizontal);padding-inline-start:var(--pico-form-element-spacing-horizontal);padding-inline-end:calc(var(--pico-form-element-spacing-horizontal) + 1.5rem);background-image:var(--pico-icon-chevron);background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}select[multiple] option:checked{background:var(--pico-form-element-selected-background-color);color:var(--pico-form-element-color)}[dir=rtl] select:not([multiple],[size]){background-position:center left .75rem}textarea{display:block;resize:vertical}textarea[aria-invalid]{--pico-icon-height:calc(1rem * var(--pico-line-height) + var(--pico-form-element-spacing-vertical) * 2 + var(--pico-border-width) * 2);background-position:top right .75rem!important;background-size:1rem var(--pico-icon-height)!important}:where(input,select,textarea,fieldset)+small{display:block;width:100%;margin-top:calc(var(--pico-spacing) * -.75);margin-bottom:var(--pico-spacing);color:var(--pico-muted-color)}:where(input,select,textarea,fieldset)[aria-invalid=false]+small{color:var(--pico-ins-color)}:where(input,select,textarea,fieldset)[aria-invalid=true]+small{color:var(--pico-del-color)}label>:where(input,select,textarea){margin-top:calc(var(--pico-spacing) * .25)}label:has([type=checkbox],[type=radio]){width:-moz-fit-content;width:fit-content;cursor:pointer}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:1.25em;height:1.25em;margin-top:-.125em;margin-inline-end:.5em;border-width:var(--pico-border-width);vertical-align:middle;cursor:pointer}[type=checkbox]::-ms-check,[type=radio]::-ms-check{display:none}[type=checkbox]:checked,[type=checkbox]:checked:active,[type=checkbox]:checked:focus,[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-checkbox);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=checkbox]~label,[type=radio]~label{display:inline-block;margin-bottom:0;cursor:pointer}[type=checkbox]~label:not(:last-of-type),[type=radio]~label:not(:last-of-type){margin-inline-end:1em}[type=checkbox]:indeterminate{--pico-background-color:var(--pico-primary-background);--pico-border-color:var(--pico-primary-border);background-image:var(--pico-icon-minus);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=radio]{border-radius:50%}[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--pico-background-color:var(--pico-primary-inverse);border-width:.35em;background-image:none}[type=checkbox][role=switch]{--pico-background-color:var(--pico-switch-background-color);--pico-color:var(--pico-switch-color);width:2.25em;height:1.25em;border:var(--pico-border-width) solid var(--pico-border-color);border-radius:1.25em;background-color:var(--pico-background-color);line-height:1.25em}[type=checkbox][role=switch]:not([aria-invalid]){--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:before{display:block;aspect-ratio:1;height:100%;border-radius:50%;background-color:var(--pico-color);box-shadow:var(--pico-switch-thumb-box-shadow);content:"";transition:margin .1s ease-in-out}[type=checkbox][role=switch]:focus{--pico-background-color:var(--pico-switch-background-color);--pico-border-color:var(--pico-switch-background-color)}[type=checkbox][role=switch]:checked{--pico-background-color:var(--pico-switch-checked-background-color);--pico-border-color:var(--pico-switch-checked-background-color);background-image:none}[type=checkbox][role=switch]:checked::before{margin-inline-start:calc(2.25em - 1.25em)}[type=checkbox][role=switch][disabled]{--pico-background-color:var(--pico-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus{--pico-background-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true]{--pico-background-color:var(--pico-form-element-invalid-border-color)}[type=checkbox][aria-invalid=false]:checked,[type=checkbox][aria-invalid=false]:checked:active,[type=checkbox][aria-invalid=false]:checked:focus,[type=checkbox][role=switch][aria-invalid=false]:checked,[type=checkbox][role=switch][aria-invalid=false]:checked:active,[type=checkbox][role=switch][aria-invalid=false]:checked:focus,[type=radio][aria-invalid=false]:checked,[type=radio][aria-invalid=false]:checked:active,[type=radio][aria-invalid=false]:checked:focus{--pico-border-color:var(--pico-form-element-valid-border-color)}[type=checkbox]:checked:active[aria-invalid=true],[type=checkbox]:checked:focus[aria-invalid=true],[type=checkbox]:checked[aria-invalid=true],[type=checkbox][role=switch]:checked:active[aria-invalid=true],[type=checkbox][role=switch]:checked:focus[aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true],[type=radio]:checked:active[aria-invalid=true],[type=radio]:checked:focus[aria-invalid=true],[type=radio]:checked[aria-invalid=true]{--pico-border-color:var(--pico-form-element-invalid-border-color)}[type=color]::-webkit-color-swatch-wrapper{padding:0}[type=color]::-moz-focus-inner{padding:0}[type=color]::-webkit-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}[type=color]::-moz-color-swatch{border:0;border-radius:calc(var(--pico-border-radius) * .5)}input:not([type=checkbox],[type=radio],[type=range],[type=file]):is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){--pico-icon-position:0.75rem;--pico-icon-width:1rem;padding-right:calc(var(--pico-icon-width) + var(--pico-icon-position));background-image:var(--pico-icon-date);background-position:center right var(--pico-icon-position);background-size:var(--pico-icon-width) auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=time]{background-image:var(--pico-icon-time)}[type=date]::-webkit-calendar-picker-indicator,[type=datetime-local]::-webkit-calendar-picker-indicator,[type=month]::-webkit-calendar-picker-indicator,[type=time]::-webkit-calendar-picker-indicator,[type=week]::-webkit-calendar-picker-indicator{width:var(--pico-icon-width);margin-right:calc(var(--pico-icon-width) * -1);margin-left:var(--pico-icon-position);opacity:0}@-moz-document url-prefix(){[type=date],[type=datetime-local],[type=month],[type=time],[type=week]{padding-right:var(--pico-form-element-spacing-horizontal)!important;background-image:none!important}}[dir=rtl] :is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){text-align:right}[type=file]{--pico-color:var(--pico-muted-color);margin-left:calc(var(--pico-outline-width) * -1);padding:calc(var(--pico-form-element-spacing-vertical) * .5) 0;padding-left:var(--pico-outline-width);border:0;border-radius:0;background:0 0}[type=file]::file-selector-button{margin-right:calc(var(--pico-spacing)/ 2);padding:calc(var(--pico-form-element-spacing-vertical) * .5) var(--pico-form-element-spacing-horizontal)}[type=file]:is(:hover,:active,:focus)::file-selector-button{--pico-background-color:var(--pico-secondary-hover-background);--pico-border-color:var(--pico-secondary-hover-border)}[type=file]:focus::file-selector-button{--pico-box-shadow:var(--pico-button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--pico-outline-width) var(--pico-secondary-focus)}[type=range]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:1.25rem;background:0 0}[type=range]::-webkit-slider-runnable-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-webkit-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-moz-range-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-moz-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-ms-track{width:100%;height:.375rem;border-radius:var(--pico-border-radius);background-color:var(--pico-range-border-color);-ms-transition:background-color var(--pico-transition),box-shadow var(--pico-transition);transition:background-color var(--pico-transition),box-shadow var(--pico-transition)}[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-webkit-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-moz-range-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-moz-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]::-ms-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.4375rem;border:2px solid var(--pico-range-thumb-border-color);border-radius:50%;background-color:var(--pico-range-thumb-color);cursor:pointer;-ms-transition:background-color var(--pico-transition),transform var(--pico-transition);transition:background-color var(--pico-transition),transform var(--pico-transition)}[type=range]:active,[type=range]:focus-within{--pico-range-border-color:var(--pico-range-active-border-color);--pico-range-thumb-color:var(--pico-range-thumb-active-color)}[type=range]:active::-webkit-slider-thumb{transform:scale(1.25)}[type=range]:active::-moz-range-thumb{transform:scale(1.25)}[type=range]:active::-ms-thumb{transform:scale(1.25)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem);background-image:var(--pico-icon-search);background-position:center left calc(var(--pico-form-element-spacing-horizontal) + .125rem);background-size:1rem auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{padding-inline-start:calc(var(--pico-form-element-spacing-horizontal) + 1.75rem)!important;background-position:center left 1.125rem,center right .75rem}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=false]{background-image:var(--pico-icon-search),var(--pico-icon-valid)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=true]{background-image:var(--pico-icon-search),var(--pico-icon-invalid)}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{background-position:center right 1.125rem}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{background-position:center right 1.125rem,center left .75rem}details{display:block;margin-bottom:var(--pico-spacing)}details summary{line-height:1rem;list-style-type:none;cursor:pointer;transition:color var(--pico-transition)}details summary:not([role]){color:var(--pico-accordion-close-summary-color)}details summary::-webkit-details-marker{display:none}details summary::marker{display:none}details summary::-moz-list-bullet{list-style-type:none}details summary::after{display:block;width:1rem;height:1rem;margin-inline-start:calc(var(--pico-spacing,1rem) * .5);float:right;transform:rotate(-90deg);background-image:var(--pico-icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:"";transition:transform var(--pico-transition)}details summary:focus{outline:0}details summary:focus:not([role]){color:var(--pico-accordion-active-summary-color)}details summary:focus-visible:not([role]){outline:var(--pico-outline-width) solid var(--pico-primary-focus);outline-offset:calc(var(--pico-spacing,1rem) * 0.5);color:var(--pico-primary)}details summary[role=button]{width:100%;text-align:left}details summary[role=button]::after{height:calc(1rem * var(--pico-line-height,1.5))}details[open]>summary{margin-bottom:var(--pico-spacing)}details[open]>summary:not([role]):not(:focus){color:var(--pico-accordion-open-summary-color)}details[open]>summary::after{transform:rotate(0)}[dir=rtl] details summary{text-align:right}[dir=rtl] details summary::after{float:left;background-position:left center}article{margin-bottom:var(--pico-block-spacing-vertical);padding:var(--pico-block-spacing-vertical) var(--pico-block-spacing-horizontal);border-radius:var(--pico-border-radius);background:var(--pico-card-background-color);box-shadow:var(--pico-card-box-shadow)}article>footer,article>header{margin-right:calc(var(--pico-block-spacing-horizontal) * -1);margin-left:calc(var(--pico-block-spacing-horizontal) * -1);padding:calc(var(--pico-block-spacing-vertical) * .66) var(--pico-block-spacing-horizontal);background-color:var(--pico-card-sectioning-background-color)}article>header{margin-top:calc(var(--pico-block-spacing-vertical) * -1);margin-bottom:var(--pico-block-spacing-vertical);border-bottom:var(--pico-border-width) solid var(--pico-card-border-color);border-top-right-radius:var(--pico-border-radius);border-top-left-radius:var(--pico-border-radius)}article>footer{margin-top:var(--pico-block-spacing-vertical);margin-bottom:calc(var(--pico-block-spacing-vertical) * -1);border-top:var(--pico-border-width) solid var(--pico-card-border-color);border-bottom-right-radius:var(--pico-border-radius);border-bottom-left-radius:var(--pico-border-radius)}[role=group],[role=search]{display:inline-flex;position:relative;width:100%;margin-bottom:var(--pico-spacing);border-radius:var(--pico-border-radius);box-shadow:var(--pico-group-box-shadow,0 0 0 transparent);vertical-align:middle;transition:box-shadow var(--pico-transition)}[role=group] input:not([type=checkbox],[type=radio]),[role=group] select,[role=group]>*,[role=search] input:not([type=checkbox],[type=radio]),[role=search] select,[role=search]>*{position:relative;flex:1 1 auto;margin-bottom:0}[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=group]>:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child),[role=search]>:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}[role=group] input:not([type=checkbox],[type=radio]):not(:last-child),[role=group] select:not(:last-child),[role=group]>:not(:last-child),[role=search] input:not([type=checkbox],[type=radio]):not(:last-child),[role=search] select:not(:last-child),[role=search]>:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}[role=group] input:not([type=checkbox],[type=radio]):focus,[role=group] select:focus,[role=group]>:focus,[role=search] input:not([type=checkbox],[type=radio]):focus,[role=search] select:focus,[role=search]>:focus{z-index:2}[role=group] [role=button]:not(:first-child),[role=group] [type=button]:not(:first-child),[role=group] [type=reset]:not(:first-child),[role=group] [type=submit]:not(:first-child),[role=group] button:not(:first-child),[role=group] input:not([type=checkbox],[type=radio]):not(:first-child),[role=group] select:not(:first-child),[role=search] [role=button]:not(:first-child),[role=search] [type=button]:not(:first-child),[role=search] [type=reset]:not(:first-child),[role=search] [type=submit]:not(:first-child),[role=search] button:not(:first-child),[role=search] input:not([type=checkbox],[type=radio]):not(:first-child),[role=search] select:not(:first-child){margin-left:calc(var(--pico-border-width) * -1)}[role=group] [role=button],[role=group] [type=button],[role=group] [type=reset],[role=group] [type=submit],[role=group] button,[role=search] [role=button],[role=search] [type=button],[role=search] [type=reset],[role=search] [type=submit],[role=search] button{width:auto}@supports selector(:has(*)){[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus),[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-button)}[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) input:not([type=checkbox],[type=radio]),[role=group]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) select,[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) input:not([type=checkbox],[type=radio]),[role=search]:has(button:focus,[type=submit]:focus,[type=button]:focus,[role=button]:focus) select{border-color:transparent}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus),[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus){--pico-group-box-shadow:var(--pico-group-box-shadow-focus-with-input)}[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=group]:has(input:not([type=submit],[type=button]):focus,select:focus) button,[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [role=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=button],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) [type=submit],[role=search]:has(input:not([type=submit],[type=button]):focus,select:focus) button{--pico-button-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-border);--pico-button-hover-box-shadow:0 0 0 var(--pico-border-width) var(--pico-primary-hover-border)}[role=group] [role=button]:focus,[role=group] [type=button]:focus,[role=group] [type=reset]:focus,[role=group] [type=submit]:focus,[role=group] button:focus,[role=search] [role=button]:focus,[role=search] [type=button]:focus,[role=search] [type=reset]:focus,[role=search] [type=submit]:focus,[role=search] button:focus{box-shadow:none}}[role=search]>:first-child{border-top-left-radius:5rem;border-bottom-left-radius:5rem}[role=search]>:last-child{border-top-right-radius:5rem;border-bottom-right-radius:5rem}[aria-busy=true]:not(input,select,textarea,html,form){white-space:nowrap}[aria-busy=true]:not(input,select,textarea,html,form)::before{display:inline-block;width:1em;height:1em;background-image:var(--pico-icon-loading);background-size:1em auto;background-repeat:no-repeat;content:"";vertical-align:-.125em}[aria-busy=true]:not(input,select,textarea,html,form):not(:empty)::before{margin-inline-end:calc(var(--pico-spacing) * .5)}[aria-busy=true]:not(input,select,textarea,html,form):empty{text-align:center}[role=button][aria-busy=true],[type=button][aria-busy=true],[type=reset][aria-busy=true],[type=submit][aria-busy=true],a[aria-busy=true],button[aria-busy=true]{pointer-events:none}:host,:root{--pico-scrollbar-width:0px}dialog{display:flex;z-index:999;position:fixed;top:0;right:0;bottom:0;left:0;align-items:center;justify-content:center;width:inherit;min-width:100%;height:inherit;min-height:100%;padding:0;border:0;-webkit-backdrop-filter:var(--pico-modal-overlay-backdrop-filter);backdrop-filter:var(--pico-modal-overlay-backdrop-filter);background-color:var(--pico-modal-overlay-background-color);color:var(--pico-color)}dialog>article{width:100%;max-height:calc(100vh - var(--pico-spacing) * 2);margin:var(--pico-spacing);overflow:auto}@media (min-width:576px){dialog>article{max-width:510px}}@media (min-width:768px){dialog>article{max-width:700px}}dialog>article>header>*{margin-bottom:0}dialog>article>header :is(a,button)[rel=prev]{margin:0;margin-left:var(--pico-spacing);padding:0;float:right}dialog>article>footer{text-align:right}dialog>article>footer [role=button],dialog>article>footer button{margin-bottom:0}dialog>article>footer [role=button]:not(:first-of-type),dialog>article>footer button:not(:first-of-type){margin-left:calc(var(--pico-spacing) * .5)}dialog>article :is(a,button)[rel=prev]{display:block;width:1rem;height:1rem;margin-top:calc(var(--pico-spacing) * -1);margin-bottom:var(--pico-spacing);margin-left:auto;border:none;background-image:var(--pico-icon-close);background-position:center;background-size:auto 1rem;background-repeat:no-repeat;background-color:transparent;opacity:.5;transition:opacity var(--pico-transition)}dialog>article :is(a,button)[rel=prev]:is([aria-current]:not([aria-current=false]),:hover,:active,:focus){opacity:1}dialog:not([open]),dialog[open=false]{display:none}:where(nav li)::before{float:left;content:"​"}nav,nav ul{display:flex}nav{justify-content:space-between;overflow:visible}nav ol,nav ul{align-items:center;margin-bottom:0;padding:0;list-style:none}nav ol:first-of-type,nav ul:first-of-type{margin-left:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav ol:last-of-type,nav ul:last-of-type{margin-right:calc(var(--pico-nav-element-spacing-horizontal) * -1)}nav li{display:inline-block;margin:0;padding:var(--pico-nav-element-spacing-vertical) var(--pico-nav-element-spacing-horizontal)}nav li :where(a,[role=link]){display:inline-block;margin:calc(var(--pico-nav-link-spacing-vertical) * -1) calc(var(--pico-nav-link-spacing-horizontal) * -1);padding:var(--pico-nav-link-spacing-vertical) var(--pico-nav-link-spacing-horizontal);border-radius:var(--pico-border-radius)}nav li :where(a,[role=link]):not(:hover){text-decoration:none}nav li [role=button],nav li [type=button],nav li button,nav li input:not([type=checkbox],[type=radio],[type=range],[type=file]),nav li select{height:auto;margin-right:inherit;margin-bottom:0;margin-left:inherit;padding:calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb]{align-items:center;justify-content:start}nav[aria-label=breadcrumb] ul li:not(:first-child){margin-inline-start:var(--pico-nav-link-spacing-horizontal)}nav[aria-label=breadcrumb] ul li a{margin:calc(var(--pico-nav-link-spacing-vertical) * -1) 0;margin-inline-start:calc(var(--pico-nav-link-spacing-horizontal) * -1)}nav[aria-label=breadcrumb] ul li:not(:last-child)::after{display:inline-block;position:absolute;width:calc(var(--pico-nav-link-spacing-horizontal) * 4);margin:0 calc(var(--pico-nav-link-spacing-horizontal) * -1);content:var(--pico-nav-breadcrumb-divider);color:var(--pico-muted-color);text-align:center;text-decoration:none;white-space:nowrap}nav[aria-label=breadcrumb] a[aria-current]:not([aria-current=false]){background-color:transparent;color:inherit;text-decoration:none;pointer-events:none}aside li,aside nav,aside ol,aside ul{display:block}aside li{padding:calc(var(--pico-nav-element-spacing-vertical) * .5) var(--pico-nav-element-spacing-horizontal)}aside li a{display:block}aside li [role=button]{margin:inherit}[dir=rtl] nav[aria-label=breadcrumb] ul li:not(:last-child) ::after{content:"\\"}progress{display:inline-block;vertical-align:baseline}progress{-webkit-appearance:none;-moz-appearance:none;display:inline-block;appearance:none;width:100%;height:.5rem;margin-bottom:calc(var(--pico-spacing) * .5);overflow:hidden;border:0;border-radius:var(--pico-border-radius);background-color:var(--pico-progress-background-color);color:var(--pico-progress-color)}progress::-webkit-progress-bar{border-radius:var(--pico-border-radius);background:0 0}progress[value]::-webkit-progress-value{background-color:var(--pico-progress-color);-webkit-transition:inline-size var(--pico-transition);transition:inline-size var(--pico-transition)}progress::-moz-progress-bar{background-color:var(--pico-progress-color)}@media (prefers-reduced-motion:no-preference){progress:indeterminate{background:var(--pico-progress-background-color) linear-gradient(to right,var(--pico-progress-color) 30%,var(--pico-progress-background-color) 30%) top left/150% 150% no-repeat;animation:progress-indeterminate 1s linear infinite}progress:indeterminate[value]::-webkit-progress-value{background-color:transparent}progress:indeterminate::-moz-progress-bar{background-color:transparent}}@media (prefers-reduced-motion:no-preference){[dir=rtl] progress:indeterminate{animation-direction:reverse}}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}[data-tooltip]{position:relative}[data-tooltip]:not(a,button,input,[role=button]){border-bottom:1px dotted;text-decoration:none;cursor:help}[data-tooltip]::after,[data-tooltip]::before,[data-tooltip][data-placement=top]::after,[data-tooltip][data-placement=top]::before{display:block;z-index:99;position:absolute;bottom:100%;left:50%;padding:.25rem .5rem;overflow:hidden;transform:translate(-50%,-.25rem);border-radius:var(--pico-border-radius);background:var(--pico-tooltip-background-color);content:attr(data-tooltip);color:var(--pico-tooltip-color);font-style:normal;font-weight:var(--pico-font-weight);font-size:.875rem;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none}[data-tooltip]::after,[data-tooltip][data-placement=top]::after{padding:0;transform:translate(-50%,0);border-top:.3rem solid;border-right:.3rem solid transparent;border-left:.3rem solid transparent;border-radius:0;background-color:transparent;content:"";color:var(--pico-tooltip-background-color)}[data-tooltip][data-placement=bottom]::after,[data-tooltip][data-placement=bottom]::before{top:100%;bottom:auto;transform:translate(-50%,.25rem)}[data-tooltip][data-placement=bottom]:after{transform:translate(-50%,-.3rem);border:.3rem solid transparent;border-bottom:.3rem solid}[data-tooltip][data-placement=left]::after,[data-tooltip][data-placement=left]::before{top:50%;right:100%;bottom:auto;left:auto;transform:translate(-.25rem,-50%)}[data-tooltip][data-placement=left]:after{transform:translate(.3rem,-50%);border:.3rem solid transparent;border-left:.3rem solid}[data-tooltip][data-placement=right]::after,[data-tooltip][data-placement=right]::before{top:50%;right:auto;bottom:auto;left:100%;transform:translate(.25rem,-50%)}[data-tooltip][data-placement=right]:after{transform:translate(-.3rem,-50%);border:.3rem solid transparent;border-right:.3rem solid}[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{opacity:1}@media (hover:hover) and (pointer:fine){[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{--pico-tooltip-slide-to:translate(-50%, -0.25rem);transform:translate(-50%,.75rem);animation-duration:.2s;animation-fill-mode:forwards;animation-name:tooltip-slide;opacity:0}[data-tooltip]:focus::after,[data-tooltip]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, 0rem);transform:translate(-50%,-.25rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:focus::before,[data-tooltip][data-placement=bottom]:hover::after,[data-tooltip][data-placement=bottom]:hover::before{--pico-tooltip-slide-to:translate(-50%, 0.25rem);transform:translate(-50%,-.75rem);animation-name:tooltip-slide}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:hover::after{--pico-tooltip-caret-slide-to:translate(-50%, -0.3rem);transform:translate(-50%,-.5rem);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:focus::before,[data-tooltip][data-placement=left]:hover::after,[data-tooltip][data-placement=left]:hover::before{--pico-tooltip-slide-to:translate(-0.25rem, -50%);transform:translate(.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:hover::after{--pico-tooltip-caret-slide-to:translate(0.3rem, -50%);transform:translate(.05rem,-50%);animation-name:tooltip-caret-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:focus::before,[data-tooltip][data-placement=right]:hover::after,[data-tooltip][data-placement=right]:hover::before{--pico-tooltip-slide-to:translate(0.25rem, -50%);transform:translate(-.75rem,-50%);animation-name:tooltip-slide}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:hover::after{--pico-tooltip-caret-slide-to:translate(-0.3rem, -50%);transform:translate(-.05rem,-50%);animation-name:tooltip-caret-slide}}@keyframes tooltip-slide{to{transform:var(--pico-tooltip-slide-to);opacity:1}}@keyframes tooltip-caret-slide{50%{opacity:0}to{transform:var(--pico-tooltip-caret-slide-to);opacity:1}}[aria-controls]{cursor:pointer}[aria-disabled=true],[disabled]{cursor:not-allowed}[aria-hidden=false][hidden]{display:initial}[aria-hidden=false][hidden]:not(:focus){clip:rect(0,0,0,0);position:absolute}[tabindex],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation}[dir=rtl]{direction:rtl}@media (prefers-reduced-motion:reduce){:not([aria-busy=true]),:not([aria-busy=true])::after,:not([aria-busy=true])::before{background-attachment:initial!important;animation-duration:1ms!important;animation-delay:-1ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-delay:0s!important;transition-duration:0s!important}} \ No newline at end of file diff --git a/packages/arg-completer/src/components/prompt.tsx b/packages/arg-completer/src/components/prompt.tsx new file mode 100644 index 0000000..1fd060d --- /dev/null +++ b/packages/arg-completer/src/components/prompt.tsx @@ -0,0 +1,30 @@ +import type { FC } from "hono/jsx" + +export const Prompt: FC = async () => { + return ( + <> +

Completion demo!

+
+ +
{/* tail follow=fsd */}
+ +
+
+ + ) +} diff --git a/packages/arg-completer/src/css/index.css b/packages/arg-completer/src/css/index.css new file mode 100644 index 0000000..cb6bce5 --- /dev/null +++ b/packages/arg-completer/src/css/index.css @@ -0,0 +1,166 @@ +@font-face { + font-family: 'C64ProMono'; + src: url('/vendor/C64_Pro_Mono-STYLE.woff2') format('woff2'); + font-weight: normal; + font-style: normal; +} + +:root { + --font-family: 'C64ProMono', monospace; + --black: #000000; + --white: #E0E0E0; + --grey: #aaa; + --dark-grey: #666; + --cyan: #00A8C8; + --red: #C62828; + --green: green; + --yellow: #C4A000; + --purple: #7C3AED; + --blue: #1565C0; + --magenta: #ff66cc; +} + +.black { + color: var(--black); +} + +.white { + color: var(--white); +} + +.cyan { + color: var(--cyan); +} + +.red { + color: var(--red); +} + +.green { + color: var(--green); +} + +.yellow { + color: var(--yellow); +} + +.purple { + color: var(--purple); +} + +.blue { + color: var(--blue); +} + +.magenta { + color: var(--magenta); +} + +body, +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-family); + color: var(--pico-h3-color); +} + +body { + margin: 10px; +} + + +#command-line { + display: flex; + flex-direction: column; + position: relative; + width: 95%; +} + +#command-line textarea { + width: 100%; + min-height: 2em; + resize: none; + background: transparent; + overflow: hidden; + padding: var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal); + box-sizing: border-box; + display: block; +} + +#command-prompt { + position: relative; + z-index: 2; +} + +#command-error { + position: absolute; + top: 1px; + left: 1px; + right: 0; + z-index: 0; + color: white; + padding: var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal); +} + +#command-error .error { + border-bottom: 3px groove red; +} + +#command-hint { + position: absolute; + top: 1px; + left: 1px; + right: 0; + bottom: 0; + z-index: 1; + color: #666; + border: none; +} + +.command-suggestion { + display: flex; + padding: 0.25em 0.5em; + justify-content: space-between; +} + +.command-suggestion:nth-child(odd) { + background-color: var(--white); +} + +/* #command-promp { + position: relative; + z-index: 2; + background: transparent; + padding: var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal); + font: inherit; + letter-spacing: inherit; + line-height: inherit; + height: auto; + min-height: 1.2em; + resize: none; + overflow: hidden; +} + +#command-hin { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1; + border: var(--pico-border-width) solid transparent; + pointer-events: none; + padding: var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal); + font: inherit; + letter-spacing: inherit; + line-height: inherit; + color: #666; + background: transparent; + resize: none; + overflow: hidden; + min-height: 1.2em; + height: auto; +} */ \ No newline at end of file diff --git a/packages/arg-completer/src/js/command.test.ts b/packages/arg-completer/src/js/command.test.ts new file mode 100644 index 0000000..4f24600 --- /dev/null +++ b/packages/arg-completer/src/js/command.test.ts @@ -0,0 +1,116 @@ +import { type CommandShape, Command } from "./command" +import { ParseError } from "./errors" +import { expect, test } from "bun:test" + +const fetchShape: CommandShape = { + command: "fetch", + description: "Fetch a URL and display the response", + args: [ + { name: "url", type: "string" }, + { name: "raw", type: "boolean", named: true, default: false }, + { name: "timeout", type: "number", named: true, default: 30 }, + ], +} + +test("parseArgs with valid args", () => { + const cmd = new Command(fetchShape) + const { args, error } = cmd.parse("fetch https://example.com raw=true timeout=10") + expect(error).toBeUndefined() + expect(args).toMatchObject([ + { name: "url", value: "https://example.com" }, + { name: "raw", value: true }, + { name: "timeout", value: 10 }, + ]) +}) + +test("parseArgs without some named args", () => { + const cmd = new Command(fetchShape) + const { args, error } = cmd.parse("fetch https://example.com raw=true") + expect(error).toBeUndefined() + expect(args).toEqual([ + { name: "url", value: "https://example.com" }, + { name: "raw", value: true }, + ]) +}) + +test("parseArgs with too many named args", () => { + const cmd = new Command(fetchShape) + const { error } = cmd.parse("fetch https://example.com meow") + expect(error).toBeDefined() + expect(error?.message).toEqual("Expected 1 positional argument(s) but got 2.") + expect(error?.start).toEqual(30) + expect(error?.end).toEqual(30) +}) + +test("parseArgs with not enough positional args", () => { + const cmd = new Command(fetchShape) + const { error } = cmd.parse("fetch") + expect(error).toBeDefined() + expect(error?.message).toEqual("Expected 1 positional argument(s) but got 0.") + expect(error?.start).toEqual(5) + expect(error?.end).toEqual(5) +}) + +const typeShape: CommandShape = { + command: "type", + description: "Fetch a URL and display the response", + args: [ + { name: "theBoolean", type: "boolean", named: true, default: false }, + { name: "theNumber", type: "number", named: true, default: 30 }, + ], +} + +test("parseArgs incorrect number type", () => { + const cmd = new Command(typeShape) + const { error } = cmd.parse("type theNumber=notanumber") + expect(error).toBeDefined() + + expect(error?.message).toEqual("Expected a number but got 'notanumber'.") + expect(error?.start).toEqual(5) + expect(error?.end).toEqual(25) +}) + +test("parseArgs incorrect boolean type", () => { + const cmd = new Command(typeShape) + const { error } = cmd.parse("type theBoolean=notaboolean") + expect(error).toBeDefined() + + expect(error?.message).toEqual("Expected a boolean but got 'notaboolean'.") + expect(error?.start).toEqual(5) + expect(error?.end).toEqual(27) +}) + +test("argSuggestions for positional and named args", () => { + const cmd = new Command(fetchShape) + + const expectNames = (input: string) => { + const names = cmd.getSuggestions(input).suggestions.map((a) => a.name + (a.named ? "=" : "")) + return expect(names, `from input "${input}"`) + } + + expectNames("fetch ").toEqual(["url"]) + expectNames("fetch path r").toEqual(["raw="]) + expectNames("fetch path raw").toEqual(["raw="]) + expectNames("fetch path raw=true ").toEqual(["timeout="]) + expectNames("fetch path raw=true t").toEqual(["timeout="]) +}) + +test("argSuggestions throw errors for unmatched patterns", () => { + const cmd = new Command(fetchShape) + + const expectError = (input: string) => { + const error = cmd.getSuggestions(input).error + return expect(error, `from input "${input}"`) + } + + expectError("fetch path f").toBeInstanceOf(ParseError) + expectError("fetch path raw=asdf ").toBeInstanceOf(ParseError) + expectError("fetch path raw=true").toBeUndefined() +}) + +/* +/* +# Ignored problems + +what if a positional arg looks like a named arg? For example echo verbose=32. If we want to echo verbose=32 it is ambiguous. +*/ diff --git a/packages/arg-completer/src/js/command.ts b/packages/arg-completer/src/js/command.ts new file mode 100644 index 0000000..fad7da9 --- /dev/null +++ b/packages/arg-completer/src/js/command.ts @@ -0,0 +1,171 @@ +import { Tokenizer, type Token } from "./tokenizer" +import { ParseError } from "./errors" + +export class Command { + shape: CommandShape + + constructor(shape: CommandShape) { + this.shape = shape + } + + getSuggestions(input: string): { suggestions: ArgShape[]; token?: Token } { + const tokens = Tokenizer.tokens(input) + if (tokens[0]?.value !== this.shape.command) { + return { suggestions: [] } + } + + const argTokens = tokens.slice(1) + const hasTrailingSpace = input.endsWith(" ") + const lastToken = argTokens.at(-1) + const isCompletingPartialArg = !hasTrailingSpace && lastToken + + const completedTokens = isCompletingPartialArg ? argTokens.slice(0, -1) : argTokens + + const usedArgs = new Set() + completedTokens.forEach((token, index) => { + const parsedArg = this.#convertTokenToArg(token, index, completedTokens) + if (usedArgs.has(parsedArg.name)) { + const message = `Argument '${parsedArg.name}' was provided more than once.` + throw new ParseError(message, token.start, token.end) + } + usedArgs.add(parsedArg.name) + }) + + const availableArgs = this.shape.args.filter((arg) => !usedArgs.has(arg.name)) + + if (isCompletingPartialArg) { + const positionalShapes = this.shape.args.filter((a) => !a.named) + const parsingPositionalArgs = completedTokens.length < positionalShapes.length + if (parsingPositionalArgs) + return { suggestions: [positionalShapes[completedTokens.length]!], token: lastToken } + + const prefix = lastToken.type === "named" ? lastToken.name : lastToken.value + const suggestions = availableArgs.filter((arg) => arg.named && arg.name.startsWith(prefix)) + if (suggestions.length > 0) { + return { suggestions, token: lastToken } + } else { + throw new ParseError(`No matches for '${prefix}'`, lastToken.start, lastToken.end) + } + } + + // If we still need positional args, only suggest the next positional arg + const positionalShapes = this.shape.args.filter((a) => !a.named) + const positionalCount = completedTokens.filter((t) => t.type === "positional").length + if (positionalCount < positionalShapes.length) { + const nextPositionalArg = positionalShapes[positionalCount]! + return { suggestions: [nextPositionalArg], token: lastToken } + } + + // Otherwise suggest available named args + return { suggestions: availableArgs.filter((arg) => arg.named), token: lastToken } + } + + parse(input: string): ParsedArg[] { + const parsedArgs: ParsedArg[] = [] + const tokens = Tokenizer.tokens(input) + const [command, ...argTokens] = tokens + + if (!command) { + throw new ParseError(`Expected command "${this.shape.command}" but got nothing.`, 0, 0) + } else if (command.value !== this.shape.command) { + const message = `Expected command "${this.shape.command}" but got "${command.value}."` + throw new ParseError(message, command.start, command.end) + } + + // Convert each token to a parsed arg + const usedArgs = new Set() + argTokens.forEach((token, index) => { + const parsedArg = this.#convertTokenToArg(token, index, argTokens) + if (usedArgs.has(parsedArg.name)) { + const message = `Argument '${parsedArg.name}' was provided more than once.` + throw new ParseError(message, token.start, token.end) + } + usedArgs.add(parsedArg.name) + parsedArgs.push(parsedArg) + }) + + // Check that we have the right number of positional args + const positionalShapes = this.shape.args.filter((a) => !a.named) + const positionalCount = argTokens.filter((t) => t.type === "positional").length + if (positionalCount !== positionalShapes.length) { + const message = `Expected ${positionalShapes.length} positional argument(s) but got ${positionalCount}.` + const lastToken = argTokens.at(-1) + const end = lastToken ? lastToken.end : command.end + throw new ParseError(message, end, end) + } + + return parsedArgs + } + + #convertTokenToArg(token: Token, tokenIndex: number, allTokens: Token[]): ParsedArg { + if (token.type === "positional") { + const positionalShapes = this.shape.args.filter((a) => !a.named) + + // If we're past the positional args, this is out of order + const shape = positionalShapes[tokenIndex] + if (!shape) { + const message = `Positional arguments must come before named arguments` + throw new ParseError(message, token.start, token.end) + } + + return { name: shape.name, value: this.#castArgValue(token, shape.type), token } + } else if (token.type === "named") { + const namedShapes = this.shape.args.filter((a) => a.named) + const shape = namedShapes.find((a) => a.name === token.name) + if (!shape) { + throw new ParseError(`Unknown named argument '${token.name}'`, token.start, token.end) + } + + return { name: token.name, value: this.#castArgValue(token, shape.type), token } + } + + throw new ParseError(`Unknown token type`, token.start, token.end) + } + + #castArgValue(token: Token, type: T): ArgTypeMap[T] { + if (type === "string") { + return token.value as ArgTypeMap[T] + } else if (type === "number") { + const number = Number(token.value) + if (isNaN(number)) + throw new ParseError(`Expected a number but got '${token.value}'.`, token.start, token.end) + + return number as ArgTypeMap[T] + } else if (type === "boolean") { + if (token.value.match(/^(true|1|t)$/i)) return true as ArgTypeMap[T] + if (token.value.match(/^(false|0|f)$/i)) return false as ArgTypeMap[T] + throw new ParseError(`Expected a boolean but got '${token.value}'.`, token.start, token.end) + } + + throw new ParseError(`Unknown arg type: ${type}.`, token.start, token.end) + } +} + +type ArgTypeMap = { + string: string + number: number + boolean: boolean +} + +type ArgShape = + | { + name: string + type: T + description?: string + named?: false + } + | { + name: string + type: T + description?: string + named: true + default: ArgTypeMap[T] + } + +export type CommandShape = { + command: string + description: string + args: ArgShape[] +} + +type ParsedArg = { name: string; value: ArgTypeMap[keyof ArgTypeMap]; token: Token } diff --git a/packages/arg-completer/src/js/commands.ts b/packages/arg-completer/src/js/commands.ts new file mode 100644 index 0000000..4bf649a --- /dev/null +++ b/packages/arg-completer/src/js/commands.ts @@ -0,0 +1,449 @@ +import { Command, type CommandShape } from "./command" + +export const commandsShapes = { + ls: { + command: "ls", + description: "List the contents of a directory", + args: [ + { name: "path", type: "string", description: "The path to list", named: false }, + { + name: "all", + type: "boolean", + description: "Show hidden files", + named: true, + default: false, + }, + { + name: "long", + type: "boolean", + description: "List in long format", + named: true, + default: false, + }, + { + name: "short-names", + type: "boolean", + description: "Only print file names", + named: true, + default: false, + }, + { + name: "full-paths", + type: "boolean", + description: "Display full paths", + named: true, + default: false, + }, + ], + }, + + cd: { + command: "cd", + description: "Change the current working directory", + args: [{ name: "path", type: "string", description: "The path to change to", named: false }], + }, + + cp: { + command: "cp", + description: "Copy files or directories", + args: [ + { name: "source", type: "string", description: "Source file or directory" }, + { name: "destination", type: "string", description: "Destination path" }, + { + name: "recursive", + type: "boolean", + description: "Copy recursively", + named: true, + default: false, + }, + { + name: "verbose", + type: "boolean", + description: "Verbose output", + named: true, + default: false, + }, + ], + }, + + mv: { + command: "mv", + description: "Move files or directories", + args: [ + { name: "source", type: "string", description: "Source file or directory" }, + { name: "destination", type: "string", description: "Destination path" }, + { + name: "verbose", + type: "boolean", + description: "Verbose output", + named: true, + default: false, + }, + ], + }, + + rm: { + command: "rm", + description: "Remove files or directories", + args: [ + { name: "path", type: "string", description: "Path to remove" }, + { + name: "recursive", + type: "boolean", + description: "Remove recursively", + named: true, + default: false, + }, + { name: "force", type: "boolean", description: "Force removal", named: true, default: false }, + { + name: "verbose", + type: "boolean", + description: "Verbose output", + named: true, + default: false, + }, + ], + }, + + mkdir: { + command: "mkdir", + description: "Create directories", + args: [ + { name: "path", type: "string", description: "Directory path to create" }, + { + name: "verbose", + type: "boolean", + description: "Verbose output", + named: true, + default: false, + }, + ], + }, + + touch: { + command: "touch", + description: "Create empty files or update timestamps", + args: [ + { name: "path", type: "string", description: "File path to touch" }, + { + name: "access", + type: "boolean", + description: "Update access time only", + named: true, + default: false, + }, + { + name: "modified", + type: "boolean", + description: "Update modified time only", + named: true, + default: false, + }, + ], + }, + + // Text operations + echo: { + command: "echo", + description: "Display a string", + args: [ + { name: "text", type: "string", description: "Text to display" }, + { + name: "no-newline", + type: "boolean", + description: "Don't append newline", + named: true, + default: false, + }, + ], + }, + + cat: { + command: "cat", + description: "Display file contents", + args: [ + { name: "path", type: "string", description: "File to display" }, + { + name: "numbered", + type: "boolean", + description: "Show line numbers", + named: true, + default: false, + }, + ], + }, + + head: { + command: "head", + description: "Show first lines of input", + args: [ + { name: "path", type: "string", description: "File to read from", named: false }, + { name: "lines", type: "number", description: "Number of lines", named: true, default: 10 }, + ], + }, + + tail: { + command: "tail", + description: "Show last lines of input", + args: [ + { name: "path", type: "string", description: "File to read from", named: false }, + { name: "lines", type: "number", description: "Number of lines", named: true, default: 10 }, + { + name: "follow", + type: "boolean", + description: "Follow file changes", + named: true, + default: false, + }, + ], + }, + + grep: { + command: "grep", + description: "Search for patterns in text", + args: [ + { name: "pattern", type: "string", description: "Pattern to search for" }, + { + name: "ignore-case", + type: "boolean", + description: "Case insensitive search", + named: true, + default: false, + }, + { + name: "invert-match", + type: "boolean", + description: "Invert match", + named: true, + default: false, + }, + { + name: "line-number", + type: "boolean", + description: "Show line numbers", + named: true, + default: false, + }, + ], + }, + + sort: { + command: "sort", + description: "Sort input", + args: [ + { + name: "reverse", + type: "boolean", + description: "Sort in reverse order", + named: true, + default: false, + }, + { + name: "ignore-case", + type: "boolean", + description: "Case insensitive sort", + named: true, + default: false, + }, + { + name: "numeric", + type: "boolean", + description: "Numeric sort", + named: true, + default: false, + }, + ], + }, + + uniq: { + command: "uniq", + description: "Filter out repeated lines", + args: [ + { + name: "count", + type: "boolean", + description: "Show count of occurrences", + named: true, + default: false, + }, + { + name: "repeated", + type: "boolean", + description: "Show only repeated lines", + named: true, + default: false, + }, + { + name: "unique", + type: "boolean", + description: "Show only unique lines", + named: true, + default: false, + }, + ], + }, + + // Data manipulation + select: { + command: "select", + description: "Select specific columns from data", + args: [{ name: "columns", type: "string", description: "Columns to select" }], + }, + + where: { + command: "where", + description: "Filter data based on conditions", + args: [{ name: "condition", type: "string", description: "Filter condition" }], + }, + + group_by: { + command: "group-by", + description: "Group data by column values", + args: [{ name: "column", type: "string", description: "Column to group by" }], + }, + + // // Network operations + // http_get: { + // command: "http get", + // description: "Fetch data from a URL via GET request", + // args: [ + // { name: "url", type: "string", description: "URL to fetch" }, + // { name: "headers", type: "string", description: "HTTP headers", named: true, default: "" }, + // { + // name: "raw", + // type: "boolean", + // description: "Return raw response", + // named: true, + // default: false, + // }, + // { + // name: "insecure", + // type: "boolean", + // description: "Allow insecure connections", + // named: true, + // default: false, + // }, + // ], + // }, + + // http_post: { + // command: "http post", + // description: "Send data via POST request", + // args: [ + // { name: "url", type: "string", description: "URL to post to" }, + // { name: "data", type: "string", description: "Data to send", named: false }, + // { + // name: "content-type", + // type: "string", + // description: "Content type", + // named: true, + // default: "application/json", + // }, + // { name: "headers", type: "string", description: "HTTP headers", named: true, default: "" }, + // ], + // }, + + // System operations + ps: { + command: "ps", + description: "List running processes", + args: [ + { + name: "long", + type: "boolean", + description: "Show detailed information", + named: true, + default: false, + }, + ], + }, + + sys: { + command: "sys", + description: "Show system information", + args: [], + }, + + which: { + command: "which", + description: "Find the location of a command", + args: [ + { name: "command", type: "string", description: "Command to locate" }, + { + name: "all", + type: "boolean", + description: "Show all matches", + named: true, + default: false, + }, + ], + }, + + // // Conversion operations + // to_json: { + // command: "to json", + // description: "Convert data to JSON", + // args: [ + // { name: "indent", type: "number", description: "JSON indentation", named: true, default: 2 }, + // { name: "raw", type: "boolean", description: "Output raw JSON", named: true, default: false }, + // ], + // }, + + // from_json: { + // command: "from json", + // description: "Parse JSON data", + // args: [ + // { + // name: "objects", + // type: "boolean", + // description: "Parse multiple objects", + // named: true, + // default: false, + // }, + // ], + // }, + + // to_csv: { + // command: "to csv", + // description: "Convert data to CSV", + // args: [ + // { + // name: "separator", + // type: "string", + // description: "Field separator", + // named: true, + // default: ",", + // }, + // { + // name: "no-headers", + // type: "boolean", + // description: "Don't include headers", + // named: true, + // default: false, + // }, + // ], + // }, + + // from_csv: { + // command: "from csv", + // description: "Parse CSV data", + // args: [ + // { + // name: "separator", + // type: "string", + // description: "Field separator", + // named: true, + // default: ",", + // }, + // { + // name: "no-headers", + // type: "boolean", + // description: "No header row", + // named: true, + // default: false, + // }, + // ], + // }, +} as const satisfies Record + +export const commands: Command[] = Object.values(commandsShapes).map((shape) => new Command(shape)) diff --git a/packages/arg-completer/src/js/dom.ts b/packages/arg-completer/src/js/dom.ts new file mode 100644 index 0000000..f869022 --- /dev/null +++ b/packages/arg-completer/src/js/dom.ts @@ -0,0 +1,12 @@ +export const $ = (id: string): HTMLElement | null => document.getElementById(id) + +export const $$ = (tag: string, html = ""): HTMLElement => { + const el = document.createElement(tag) + el.innerHTML = html + return el +} + +export const cmdPrompt = $("command-prompt") as HTMLTextAreaElement +export const cmdHint = $("command-hint") as HTMLTextAreaElement +export const cmdSuggestionList = $("command-suggestion-list") as HTMLUListElement +export const cmdError = $("command-error") as HTMLDivElement diff --git a/packages/arg-completer/src/js/errors.ts b/packages/arg-completer/src/js/errors.ts new file mode 100644 index 0000000..3d05447 --- /dev/null +++ b/packages/arg-completer/src/js/errors.ts @@ -0,0 +1,5 @@ +export class ParseError extends Error { + constructor(message: string, public start: number, public end: number) { + super(message) + } +} diff --git a/packages/arg-completer/src/js/index.tsx b/packages/arg-completer/src/js/index.tsx new file mode 100644 index 0000000..af9bed6 --- /dev/null +++ b/packages/arg-completer/src/js/index.tsx @@ -0,0 +1,133 @@ +import { commands } from "./commands" +import { cmdPrompt, cmdHint, cmdSuggestionList, cmdError } from "./dom" +import { ParseError } from "./errors" + +let tabCompletion = "" + +cmdPrompt.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + console.log("ENTER") + e.preventDefault + } else if (e.key === "Tab") { + e.preventDefault() + cmdPrompt.value = tabCompletion + updateSuggestionList(cmdPrompt.value) + } +}) + +cmdPrompt.addEventListener("input", (e) => { + updateSuggestionList(cmdPrompt.value) +}) + +// Corey hates this +const updateSuggestionList = (input: string) => { + if (input.trim() == "") return + + cmdSuggestionList.innerHTML = "" + cmdError.innerHTML = "" + tabCompletion = "" + + const { commandSuggestions, argSuggestions, error } = getSuggestions(input) + + if (error) { + const beforeError = input.slice(0, error.start) + const errorText = input.slice(error.start, error.end) + const afterError = input.slice(error.end) + cmdError.innerHTML = `${beforeError}${errorText}${afterError}` + } + + // Show command suggestions + commandSuggestions.forEach((cmdShape) => { + const div = createSuggestionElement(cmdShape.command, cmdShape.description) + cmdSuggestionList.appendChild(div) + }) + + // Show arg suggestions + argSuggestions?.forEach((arg) => { + let name = arg.name + if (arg.named) { + name = `${name}=${arg.default}` + } + const description = `<${arg.type}>` + (arg.description ? ` - ${arg.description}` : "") + const div = createSuggestionElement(name, description) + cmdSuggestionList.appendChild(div) + }) + + cmdHint.value = "" + tabCompletion = "" + + // Tab completion + if (commandSuggestions.length > 0) { + cmdHint.value = commandSuggestions[0]!.command + tabCompletion = cmdHint.value + " " + } else if (argSuggestions?.length > 0) { + const suggestion = argSuggestions[0]! + + const lastArg = input.trimStart().split(" ").pop() ?? "" + if (suggestion?.named) { + if (lastArg.match(/^[\w-]+=/)) { + const afterEquals = lastArg.split("=")[1] ?? "" + if (!afterEquals) { + const suggestionText = `<${suggestion.type}>` + tabCompletion = input + suggestionText + cmdHint.value = tabCompletion + } + } else { + const suggestionText = suggestion.name.slice(lastArg.length) + "=" + tabCompletion = input + suggestionText + cmdHint.value = tabCompletion + } + } else if (!lastArg) { + tabCompletion = "" + cmdHint.value = input + suggestion?.name + " " + } + } +} + +const getSuggestions = (input: string) => { + try { + const commandInput = input.trimStart().split(" ")[0] ?? "" + const matchingCommands = commands.filter((cmd) => cmd.shape.command.startsWith(commandInput)) + const hasCommand = input.trimStart().match(/\w+\s+/) + + if (!hasCommand) { + return { + commandSuggestions: matchingCommands.map((cmd) => cmd.shape), + argSuggestions: [], + command: null, + } + } else if (matchingCommands.length === 1) { + const command = matchingCommands[0]! + const { suggestions } = command.getSuggestions(input) + return { + commandSuggestions: [], + argSuggestions: suggestions, + } + } else { + return { + commandSuggestions: [], + argSuggestions: [], + error: new ParseError(`Unknown command: "${commandInput}."`, 0, commandInput.length), + } + } + } catch (error) { + if (error instanceof ParseError) { + return { commandSuggestions: [], argSuggestions: [], error } + } + throw error + } +} + +const createSuggestionElement = (title: string, description: string) => { + const div = document.createElement("div") + div.className = "command-suggestion" + const commandDiv = document.createElement("div") + commandDiv.textContent = title + commandDiv.className = "command-name" + const descriptionDiv = document.createElement("div") + descriptionDiv.textContent = description + descriptionDiv.className = "command-description" + div.appendChild(commandDiv) + div.appendChild(descriptionDiv) + return div +} diff --git a/packages/arg-completer/src/js/tokenizer.test.ts b/packages/arg-completer/src/js/tokenizer.test.ts new file mode 100644 index 0000000..fb6c480 --- /dev/null +++ b/packages/arg-completer/src/js/tokenizer.test.ts @@ -0,0 +1,105 @@ +import { Tokenizer } from "./tokenizer.js" +import { expect, test } from "bun:test" + +test("parse 'fetch url' into command and positional tokens", () => { + const { tokens, error } = Tokenizer.tokens("fetch https://example.com/path true") + expect(tokens).toEqual([ + { type: "command", value: "fetch", start: 0, end: 5 }, + { type: "positional", value: "https://example.com/path", start: 6, end: 30 }, + { type: "positional", value: "true", start: 31, end: 35 }, + ]) +}) + +test("parse 'fetch \"quoted url\"' and strip quotes from value", () => { + const { tokens, error } = Tokenizer.tokens('fetch "https://example.com/path"') + expect(tokens).toEqual([ + { type: "command", value: "fetch", start: 0, end: 5 }, + { type: "positional", value: "https://example.com/path", start: 6, end: 32 }, + ]) +}) + +test("parse 'fetch url timeout=30' with named argument", () => { + const { tokens, error } = Tokenizer.tokens("fetch https://example.com/path timeout=30") + expect(tokens).toEqual([ + { type: "command", value: "fetch", start: 0, end: 5 }, + { type: "positional", value: "https://example.com/path", start: 6, end: 30 }, + { type: "named", name: "timeout", value: "30", start: 31, end: 41 }, + ]) +}) + +test("parse mixed args 'fetch url timeout=30 raw=true'", () => { + const { tokens, error } = Tokenizer.tokens("fetch https://example.com/path timeout=30 raw=true") + expect(tokens).toEqual([ + { type: "command", value: "fetch", start: 0, end: 5 }, + { type: "positional", value: "https://example.com/path", start: 6, end: 30 }, + { type: "named", name: "timeout", value: "30", start: 31, end: 41 }, + { type: "named", name: "raw", value: "true", start: 42, end: 50 }, + ]) +}) + +test("return error for unclosed quote in 'fetch \"unterminated'", () => { + const { tokens, error } = Tokenizer.tokens('fetch "https://example.com/path') + expect(tokens).toEqual([]) + expect(error).toBeDefined() + expect(error?.message).toBe("Unclosed quote") + expect(error?.start).toBe(6) + expect(error?.end).toBe(31) +}) + +test("return error for escape char at end", () => { + const { tokens, error } = Tokenizer.tokens("fetch https://example.com/path\\") + expect(tokens).toEqual([]) + expect(error).toBeDefined() + expect(error?.message).toBe("Trailing backslash") + expect(error?.start).toBe(30) + expect(error?.end).toBe(31) +}) + +test('handle escaped quotes in \'fetch "url with \\"quotes\\""\'', () => { + const { tokens, error } = Tokenizer.tokens('fetch "url with \\"quotes\\""') + expect(tokens).toEqual([ + { type: "command", value: "fetch", start: 0, end: 5 }, + { type: "positional", value: 'url with "quotes"', start: 6, end: 27 }, + ]) + expect(error).toBeUndefined() +}) + +test("handle escaped quotes in in named arg", () => { + const { tokens, error } = Tokenizer.tokens('fetch url timeout="30 seconds" raw=true') + expect(tokens).toEqual([ + { type: "command", value: "fetch", start: 0, end: 5 }, + { type: "positional", value: "url", start: 6, end: 9 }, + { type: "named", name: "timeout", value: "30 seconds", start: 10, end: 30 }, + { type: "named", name: "raw", value: "true", start: 31, end: 39 }, + ]) + expect(error).toBeUndefined() +}) + +test("ignore multiple spaces between arguments", () => { + const { tokens, error } = Tokenizer.tokens( + "fetch https://example.com/path timeout=30 raw=true" + ) + expect(tokens).toEqual([ + { type: "command", value: "fetch", start: 0, end: 5 }, + { type: "positional", value: "https://example.com/path", start: 9, end: 33 }, + { type: "named", name: "timeout", value: "30", start: 36, end: 46 }, + { type: "named", name: "raw", value: "true", start: 49, end: 57 }, + ]) + expect(error).toBeUndefined() +}) + +test("return empty array for empty string input", () => { + const { tokens, error } = Tokenizer.tokens("") + expect(tokens).toEqual([]) + expect(error).toBeUndefined() +}) + +test("handle quote immediately followed by text 'fetch \"url\"more'", () => { + const { tokens, error } = Tokenizer.tokens('fetch "url"more timeout=30') + expect(tokens).toEqual([ + { type: "command", value: "fetch", start: 0, end: 5 }, + { type: "positional", value: "urlmore", start: 6, end: 15 }, + { type: "named", name: "timeout", value: "30", start: 16, end: 26 }, + ]) + expect(error).toBeUndefined() +}) diff --git a/packages/arg-completer/src/js/tokenizer.ts b/packages/arg-completer/src/js/tokenizer.ts new file mode 100644 index 0000000..128d24f --- /dev/null +++ b/packages/arg-completer/src/js/tokenizer.ts @@ -0,0 +1,122 @@ +import { ParseError } from "./errors.js" + +export class Tokenizer { + pos: number + tokens: Token[] + input: string + + constructor(input: string) { + this.pos = 0 + this.tokens = [] + this.input = input + } + + static tokens(input: string): Token[] { + const tokenizer = new Tokenizer(input) + return tokenizer.tokenize() + } + + tokenize(): Token[] { + while (this.pos < this.input.length) { + if (this.#skipWhitespace(this.input)) continue + + const rawTokenStart = this.pos + const rawToken = this.#readRawToken() + const token = this.#analyzeToken(rawToken, rawTokenStart, this.pos) + this.tokens.push(token) + } + + return this.tokens + } + + #analyzeToken(value: string, start: number, end: number): Token { + if (!value) throw new ParseError("Unexpected empty argument", start, end) + + if (this.tokens.length === 0) { + return { type: "command", value, start, end } + } + + const namedMatch = value.match(/^([\w-]+)=(.*)$/) + if (namedMatch) { + const [, name, argValue] = namedMatch + if (!name) { + throw new ParseError("Named argument missing name", start, end) + } + + return { type: "named", name, value: argValue, start, end } + } else { + return { type: "positional", value, start, end } + } + } + + #readRawToken(): string { + let token = "" + let inQuotes = false + let escapeNext = false + + while (!this.#eol()) { + const char = this.input[this.pos] + + if (escapeNext) { + if (char === '"') { + token += char // unescape the quote + } else { + token += "\\" + char // keep the backslash for non-quotes + } + escapeNext = false + } else if (char === "\\") { + escapeNext = true + } else if (char === '"') { + inQuotes = !inQuotes + } else if (char === " " && !inQuotes) { + break + } else { + token += char + } + + this.pos++ + } + + if (inQuotes) { + throw new ParseError("Unclosed quote", this.pos - token.length - 1, this.pos) + } else if (escapeNext) { + throw new ParseError("Trailing backslash", this.pos - 1, this.pos) + } + + return token + } + + #skipWhitespace(input: string): boolean { + const match = input.slice(this.pos).match(/^\s+/) + if (!match) return false + + const offset = match[0].length + this.pos += offset + return true + } + + #eol(): boolean { + return this.pos >= this.input.length + } +} + +export type Token = + | { + type: "command" + value: string + start: number + end: number + } + | { + type: "positional" + value: string + start: number + end: number + } + | { + type: "named" + name: string + value: string + start: number + end: number + } diff --git a/packages/arg-completer/src/layout.tsx b/packages/arg-completer/src/layout.tsx new file mode 100644 index 0000000..e46bb8e --- /dev/null +++ b/packages/arg-completer/src/layout.tsx @@ -0,0 +1,17 @@ +import { html } from "hono/html" + +export const Layout = (children: any) => html` + + + Completion test + + + + + + + + + ${children} + + ` diff --git a/packages/arg-completer/src/server.tsx b/packages/arg-completer/src/server.tsx new file mode 100644 index 0000000..0b80524 --- /dev/null +++ b/packages/arg-completer/src/server.tsx @@ -0,0 +1,32 @@ +import { Hono } from "hono" +import { Prompt } from "./components/prompt" +import { serveStatic } from "hono/bun" +import { Layout } from "./layout" +import { isFile, transpile } from "./utils" + +const app = new Hono() + +app.get("/", (c) => c.html(Layout())) +app.use("/vendor/*", serveStatic({ root: "./public" })) +app.use("/css/*", serveStatic({ root: "./src" })) + +app.get("/js/:path{.+}", async (c) => { + const path = "./src/js/" + c.req.param("path") + const ts = path.endsWith(".js") ? path.replace(".js", ".ts") : path + ".ts" + const tsx = path.endsWith(".js") ? path.replace(".js", ".tsx") : path + ".tsx" + let javascript = "" + + if (isFile(ts)) { + javascript = await transpile(ts) + } else if (isFile(tsx)) { + javascript = await transpile(tsx) + } else if (isFile(path)) { + javascript = await Bun.file(path).text() + } else { + return c.text("File not found", 404) + } + + return new Response(javascript, { headers: { "Content-Type": "text/javascript" } }) +}) + +export default app diff --git a/packages/arg-completer/src/utils.tsx b/packages/arg-completer/src/utils.tsx new file mode 100644 index 0000000..6f21833 --- /dev/null +++ b/packages/arg-completer/src/utils.tsx @@ -0,0 +1,58 @@ +import { statSync } from "node:fs" +import { stat } from "node:fs/promises" + +const transpiler = new Bun.Transpiler({ loader: 'tsx' }) + +export function css(strings: TemplateStringsArray, ...values: any[]) { + return