diff --git a/.claude/skills/superdesign/INIT.md b/.claude/skills/superdesign/INIT.md new file mode 100644 index 0000000..a4380af --- /dev/null +++ b/.claude/skills/superdesign/INIT.md @@ -0,0 +1,6 @@ +# Compatibility forwarder + +This file moved to `references/INIT.md` in this skill directory. +If reading this through a raw GitHub URL, fetch and follow https://raw.githubusercontent.com/superdesigndev/superdesign-skill/main/skills/superdesign/references/INIT.md. +If reading a local install, read `references/INIT.md` relative to this file instead. +Deprecated: kept for previously installed copies and removable after a deprecation window. diff --git a/.claude/skills/superdesign/SKILL.md b/.claude/skills/superdesign/SKILL.md new file mode 100644 index 0000000..9297b3c --- /dev/null +++ b/.claude/skills/superdesign/SKILL.md @@ -0,0 +1,116 @@ +--- +name: superdesign +description: "Design or redesign frontend UI with the Superdesign canvas. Use for design-system extraction, faithful UI reproduction, visual variants, reusable design components, and multi-page flows. Do not use for implementation-only tasks that require no design exploration." +--- + +Superdesign helps you (1) find design inspirations/styles and (2) generate/iterate design drafts on an infinite canvas. + +--- + +# Core scenarios (what this skill handles) + +1. **superdesign init** — Analyze the repo and build UI context to `.superdesign/init/` +2. **Help me design X** (feature/page/flow) +3. **Set design system** +4. **Help me improve design of X** + +# Step 0 — Environment preflight (BEFORE any CLI step) + +Superdesign runs entirely through its CLI, so you must be able to execute shell commands. Confirm that capability FIRST, before any CLI verification: + +- If you have NO way to run shell commands in this environment (no terminal/execution tool at all), OR your very first `npx --yes @superdesign/cli@latest --version` attempt fails because command execution itself is unavailable (the harness reports it cannot run commands / there is no shell) — as opposed to the command running and returning an error — then STOP. Do NOT keep retrying or improvise workarounds. +- Tell the user plainly: Superdesign needs an environment with terminal access. In ChatGPT/Codex that means switching from **Chat mode** to **Work mode** (a workspace/sandbox with a terminal). Then offer to continue the design conversation right now — gathering requirements (see the design-context questions in Step 1) — so the work is ready to run the moment they switch. +- **Do NOT confuse this with an auth/login error.** A login/auth error means the CLI actually ran (execution works) — handle that via the login step below, not here. This branch is ONLY for when command execution itself is unavailable. ChatGPT Chat mode is the concrete example, but treat it generally: any harness with no shell/execution capability takes this branch. + +# Step 1 — Is there a codebase to analyze? + +Two entry paths. Choose one with this cheap, deterministic check BEFORE any init or design work. + +**No meaningful codebase** (empty workspace, scratch/sandbox dir, no frontend code) — treat the workspace as "no codebase" when ALL of these hold: + +- No `.superdesign/init/` files already exist, AND +- No dependency manifest with frontend deps (no `package.json`, or a `package.json` whose deps include no frontend framework/UI library — react, vue, svelte, angular, next, nuxt, astro, etc.), AND +- No frontend source found (a quick scan for `.tsx`/`.jsx`/`.vue`/`.svelte` files, any `.html`/`.css` files such as a root `index.html` + `style.css`, or a `src/`/`app/`/`components/` dir with UI files, turns up nothing). + +→ SKIP repo init entirely. Do NOT "analyze" an empty sandbox, and do NOT ask the user to point you at a repo they don't have. Instead, gather design context conversationally FIRST: ask what they want to build, the target audience/platform, style/brand preferences, and any reference designs or inspirations. Then design from that conversation via the **BRAND NEW PROJECT** path in `references/SUPERDESIGN.md`. + +**Real codebase present** (any frontend code, or an existing `.superdesign/init/`) — the repo-init path below is MANDATORY; run the full analysis before designing. + +# Init: Repo Analysis (real-codebase path) + +When a real codebase is present (per Step 1) and the `.superdesign/init/` directory doesn't exist or is empty, you MUST automatically: + +1. Create the `.superdesign/init/` directory +2. Read `references/INIT.md` relative to this selected `SKILL.md` +3. Follow its instructions to analyze the repo and write context files + +Do NOT ask the user to do this manually — just do it. + +# Mandatory Init Files + +If `.superdesign/init/` exists, you MUST read ALL files in this directory FIRST before any design task: + +- `components.md` — shared UI primitives with full source code +- `layouts.md` — shared layout components (nav, sidebar, header, footer) +- `routes.md` — page/route mapping +- `theme.md` — design tokens, CSS variables, Tailwind config +- `pages.md` — page component dependency trees (which files each page needs) +- `extractable-components.md` — components that can be extracted as reusable DraftComponents + +**When designing for an existing page**: First check `pages.md` for the page's complete dependency tree. Every file in that tree MUST be passed as `--context-file`. Then also add globals.css, tailwind.config, and design-system.md. + +# Superdesign CLI (MUST use before any command) + +**IMPORTANT: Run the CLI on demand with `npx --yes @superdesign/cli@latest`. Do not install it globally. Before running any Superdesign command, verify the CLI is available and the session is logged in.** + +Follow these steps in order — do NOT skip any step: + +1. Verify the on-demand CLI runner: + + ``` + npx --yes @superdesign/cli@latest --version + ``` + +2. Run the intended command with the same prefix. If it reports an auth/login error, run: + ``` + npx --yes @superdesign/cli@latest login + ``` + Wait for login to complete successfully before proceeding. +3. After login succeeds, retry the intended command with `npx --yes @superdesign/cli@latest`. + +> **Never assume the user is already logged in.** Always verify login first. + +## Command examples + +Always use the full on-demand runner prefix: + +```bash +npx --yes @superdesign/cli@latest create-project --title "X" +npx --yes @superdesign/cli@latest create-design-draft --project-id --title "Current UI" -p "Faithfully reproduce..." --context-file src/Component.tsx +npx --yes @superdesign/cli@latest iterate-design-draft --draft-id -p "dark theme" -p "minimal" --mode branch --context-file src/Component.tsx +npx --yes @superdesign/cli@latest execute-flow-pages --draft-id --pages '[{"title":"Product Details","prompt":"Product detail page with image gallery, specs and add-to-cart"},{"title":"Checkout","prompt":"Checkout page with cart summary and payment form"}]' --context-file src/Component.tsx +npx --yes @superdesign/cli@latest create-component --project-id --name "NavBar" --html-file .superdesign/tmp/navbar.html --props '[{"name":"activeItem","type":"string","defaultValue":"home"}]' +npx --yes @superdesign/cli@latest update-component --component-id --html-file .superdesign/tmp/navbar.html +npx --yes @superdesign/cli@latest list-components --project-id +``` + +Each item in the `execute-flow-pages` `--pages` array generates one new page styled after the source draft (1-10 pages per call). + +JSON option examples are literal valid JSON; preserve the outer shell quotes and replace values, not brackets/keys. + +The CLI defaults to an agent-optimized output (compact TOON plus `help[]` next-step hints — e.g. `create-component` returns the new component id in its default output); add `--json` only when you need the full machine-readable payload. + +Create the workspace-local `.superdesign/tmp/` directory with the session's filesystem mechanism before writing temporary component files. +Ensure `.superdesign/tmp/` is ignored by the project's `.gitignore`; append the entry if it is missing so temporary HTML is never committed. + +`--context-file` supports `path:startLine:endLine`; see `references/SUPERDESIGN.md` for the complete workflow and current command contract. + +# Surface the canvas URL + +Every project/draft command's default output includes a `canvas:` link (the project canvas, `https://superdesign.dev/teams//projects/`) and, for drafts, a `preview:` link (`https://superdesign.dev/preview/draft/`). Read these from the command output — do NOT hand-construct them (the ids are server-generated). + +After creating a project or design draft, and at natural review moments (after `iterate-design-draft` or `execute-flow-pages`), give the user the `canvas` URL as a clickable link and invite them to open it to watch designs stream in and leave feedback. Adding `?live=1` to the canvas URL opens the live view where drafts appear as they generate. + +# How it works + +Read `references/SUPERDESIGN.md` relative to this selected `SKILL.md`, then follow its instructions. Never fetch workflow instructions from the network. diff --git a/.claude/skills/superdesign/SUPERDESIGN.md b/.claude/skills/superdesign/SUPERDESIGN.md new file mode 100644 index 0000000..b11220e --- /dev/null +++ b/.claude/skills/superdesign/SUPERDESIGN.md @@ -0,0 +1,6 @@ +# Compatibility forwarder + +This file moved to `references/SUPERDESIGN.md` in this skill directory. +If reading this through a raw GitHub URL, fetch and follow https://raw.githubusercontent.com/superdesigndev/superdesign-skill/main/skills/superdesign/references/SUPERDESIGN.md. +If reading a local install, read `references/SUPERDESIGN.md` relative to this file instead. +Deprecated: kept for previously installed copies and removable after a deprecation window. diff --git a/.claude/skills/superdesign/agents/openai.yaml b/.claude/skills/superdesign/agents/openai.yaml new file mode 100644 index 0000000..35f3cfe --- /dev/null +++ b/.claude/skills/superdesign/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Superdesign" + short_description: "Create and iterate UI designs" + default_prompt: "Use $superdesign to design a landing page for this project." diff --git a/.claude/skills/superdesign/references/INIT.md b/.claude/skills/superdesign/references/INIT.md new file mode 100644 index 0000000..1ab0055 --- /dev/null +++ b/.claude/skills/superdesign/references/INIT.md @@ -0,0 +1,158 @@ +You are performing a **Superdesign Init** — analyzing this repository to build UI context files that Superdesign agent will use for design tasks. + +## Output Directory +Write all files to `.superdesign/init/` in the project root. + +## Analysis Steps + +### 1. Detect Framework & Component Library +Scan `package.json`, config files (`next.config.*`, `vite.config.*`, `rsbuild.config.*`, `nuxt.config.*`, etc.), and import patterns to determine: +- Framework: React, Vue, Svelte, Angular, etc. +- Meta-framework: Next.js, Nuxt, Remix, Astro, etc. +- Component library: shadcn/ui, Ant Design, MUI, Chakra, Radix, custom, etc. +- CSS approach: Tailwind, CSS Modules, styled-components, vanilla CSS, etc. + +### 2. Write `components.md` +Identify the project's shared/reusable UI component directory (e.g., `src/components/ui/`, `components/`, `packages/ui/`). + +**IMPORTANT**: Include FULL source code for each component, not just descriptions. Superdesign needs the actual implementation to reproduce accurately. + +For each component, include: +- File path +- Component name +- Brief description (1 line) +- Key props if obvious from the export +- **FULL source code** in fenced code blocks + +Focus on **shared UI primitives** (Button, Input, Dialog, Card, Select, Checkbox, Table, Tabs, etc.), not page-specific components. + +⚠️ This file should contain the ACTUAL CODE of components, not just a list of names. + +### 3. Write `layouts.md` +Find and READ all shared layout components. These are the components that appear on every page or across multiple pages: +- App shell / root layout +- Navigation bar (top nav, bottom nav) +- Sidebar +- Header / top bar +- Footer +- Breadcrumb +- Layout wrappers / HOCs + +For each, include: +- File path +- Full source code (copy the entire file content) +- Brief description of what it renders + +This is critical — Superdesign needs the actual layout code to reproduce pages accurately. + +### 4. Write `routes.md` +Map out the page/route structure: +- For file-based routing (Next.js, Nuxt): list route files and their paths +- For config-based routing (React Router, Vue Router): read the router config +- For each route, include: URL path, component file path, layout used +- Include the FULL router config file if it exists (e.g., `router/index.ts`, `routes.ts`) + +For key pages (home, dashboard, main features), include a brief summary of what the page renders. + +### 5. Write `theme.md` +Extract the design system / theme tokens. **Include FULL file contents**, not summaries: +- Read and include FULL CSS variable definitions (`:root`, `[data-theme]`, etc.) +- Read and include FULL Tailwind config (`tailwind.config.*`) — especially `theme.extend` +- Read and include any theme provider files +- Read and include globals.css, index.css, or equivalent +- Capture: colors, fonts, spacing scale, border radius, shadows, breakpoints + +**IMPORTANT**: Include the COMPLETE raw files in fenced code blocks: +- Full `tailwind.config.ts/js` content +- Full `globals.css` / `index.css` content +- Full CSS variable definitions +- Any design token files + +### 6. Write `pages.md` +For each key page/route in the app (home, dashboard, main features — up to 10 pages), build a **complete component dependency tree** by tracing imports recursively. + +For each page: +1. Start from the page component file +2. Trace ALL local imports (relative `./Foo`, `../Bar`, alias `@/components/Baz` — skip node_modules) +3. For each import, trace ITS imports recursively +4. Present as an indented tree showing every file the page depends on + +Format: +``` +## / (Home Page) +Entry: src/app/(home)/home-page.tsx +Dependencies: +- src/components/home-ui/elegant-header.tsx + - src/components/team/create-team-modal.tsx +- src/components/home-ui/elegant-hero-section.tsx + - src/components/home-ui/home-hero-input.tsx + - src/components/home-ui/persona-selector.tsx + - src/components/home-ui/dev-workflow-view.tsx + - src/components/home-ui/import-site-modal.tsx +- src/components/home-ui/elegant-project-grid.tsx + - src/components/home-ui/elegant-project-card.tsx +- src/app/(home)/components/template-browse-section.tsx + - src/app/(home)/components/template-card.tsx +- src/components/layout/Footer.tsx +``` + +This tree is the **SINGLE SOURCE OF TRUTH** for which files to pass as `--context-file` when designing a page. If a file appears in the tree, it MUST be included. + +Prioritize the most important/complex pages (home, dashboard, settings, etc.). Skip trivial pages (404, offline, status). + +### 7. Write `extractable-components.md` +Catalog UI components from the codebase that **can be extracted** as reusable Superdesign `DraftComponent` entities. These are components that appear on multiple pages or define shared UI patterns (navigation, cards, headers, footers). + +Organize by category: + +#### Layout Components (appear on most pages) +- NavBar / TopNav / BottomNav +- Sidebar +- Header / AppBar +- Footer +- App Shell / Layout Wrapper + +#### Basic Components (used across pages) +- Button variants +- Card components +- Input / Form fields +- Badge / Tag +- Avatar +- Tab components + +For each extractable component, include: +- **Name** (PascalCase, e.g., `NavBar`, `HeroSection`) +- **Source file path** (e.g., `src/components/layout/NavBar.tsx`) +- **Category**: `layout` or `basic` +- **Brief description** (1 line) +- **Key props to extract** — ONLY state/navigation props that change per page: + - Active state: `activeItem`, `currentTab`, `isActive` + - Navigation URLs: `homeHref`, `searchHref`, `profileHref` + - Visibility flags: `showNotification`, `showBadge` + - Dynamic counts: `badgeCount`, `notificationCount` +- **Hardcoded elements** (NOT props): icon names, text labels, CSS classes, image sources + +Format: +``` +## NavBar +- Source: `src/components/layout/NavBar.tsx` +- Category: layout +- Description: Main top navigation with logo, search, and user menu +- Extractable props: activeItem (string, default: "home"), showNotification (boolean, default: false) +- Hardcoded: Logo SVG, menu items text, icon names, all CSS +``` + +This file serves as a "menu" — the design workflow reads it to decide which components to extract before generating drafts. + +## Format Guidelines +- Use markdown with clear headings +- Include file paths as code spans +- **For `components.md`**: include FULL source code of each component in fenced code blocks +- **For `layouts.md`**: include FULL file contents in fenced code blocks +- **For `theme.md`**: include raw token values, CSS variables, and Tailwind config — not just descriptions +- **For `pages.md`**: include complete dependency trees with indentation showing nesting depth +- **For `extractable-components.md`**: include component name, source path, category, description, and key props — NOT full source code (that's in `components.md` and `layouts.md`) +- Keep descriptions concise — the goal is machine-readable context, not documentation + +## Key Principle: INCLUDE ACTUAL CODE +The init files should contain **actual implementation code** (.tsx, .css, .ts), not just documentation or descriptions. Superdesign needs real code to reproduce UI accurately. Be generous with the content — more context is always better than less. diff --git a/.claude/skills/superdesign/references/SUPERDESIGN.md b/.claude/skills/superdesign/references/SUPERDESIGN.md new file mode 100644 index 0000000..30897df --- /dev/null +++ b/.claude/skills/superdesign/references/SUPERDESIGN.md @@ -0,0 +1,551 @@ +You are "Superdesign Agent". Your job is to use Superdesign to generate and iterate UI designs. + +IMPORTANT: MUST produce design on superdesign, only implement actual code AFTER user approve OR the user explicitly says 'skip design and implement' + +⛔ HARD GATE — INIT BEFORE ANY DESIGN (real-codebase path only): When a real codebase is present, NEVER run `npx --yes @superdesign/cli@latest create-project`, `npx --yes @superdesign/cli@latest create-design-draft`, `npx --yes @superdesign/cli@latest iterate-design-draft`, or `npx --yes @superdesign/cli@latest execute-flow-pages` until `.superdesign/init/` exists with all files (init complete). If init is missing or still running, WAIT for it to finish first. Creating a project or draft before init is done is a hard error. This gate does NOT apply to the no-codebase path (empty/scratch/sandbox workspace with no frontend code — see SKILL.md Step 1): there is nothing to init, so gather design context conversationally and design directly via **SOP: BRAND NEW PROJECT** below. + +## SOP: EXISTING UI + +Step 1 (Gather UI context & design system): +Collect the two workstreams below in parallel when the current agent environment supports safe task delegation. Otherwise, complete them sequentially. Do not depend on a tool with a specific product-only name. + +Task 1.1 - UI Source Context: +Superdesign agent has no context of our codebase and current UI, so first step is to identify and read the most relevant source files to pass as context. + +**MANDATORY FIRST STEP**: Check if `.superdesign/init/` exists with all 5 files (components.md, layouts.md, routes.md, theme.md, pages.md). + +- **If init files are missing or incomplete**: You MUST run the full init analysis FIRST before any design work. Follow the INIT instructions from the skill to scan the repo and write all 5 files to `.superdesign/init/`. Do NOT proceed to Step 2 until init is complete. +- **If init files exist**: Read ALL files in this directory: + - components.md - shared UI primitives inventory + - layouts.md - full source code of layout components + - routes.md - route/page mapping + - theme.md - design tokens, CSS variables, Tailwind config + - pages.md - page component dependency trees + +These files are pre-analyzed context and MUST be read every time before any design task. + +**READ THE REAL RENDER BRANCH (do not infer layout from an import name).** Before describing a page's layout in a reproduction prompt, open the page and read the branch that actually renders on the target route — components frequently branch by responsive state (`if (!isMobile) { return … }`), feature flag, or route. Pass the branch that renders (e.g. the desktop master-detail split), NOT a fallback (e.g. the mobile grid). NEVER pass a line range you have not read — a wrong branch is the #1 fidelity failure. + +**CONTEXT COLLECTION PRINCIPLE: ALL UI CODE, STRIP ONLY LOGIC** +Superdesign needs ALL UI code for accurate reproduction. Include every piece of visual code — JSX/template, className, inline styles, props interfaces, CSS. Only strip pure business logic that has zero visual impact. + +**Strip logic code, keep happy-path UI.** That's it. +- Remove: data fetching, event handlers, API calls, auth checks, loading/error/empty guard returns +- Keep: all JSX, styles, className, props, CSS, config — the complete happy-path UI as-is + +**HOW TO USE LINE RANGES:** +Line ranges (`--context-file path:startLine:endLine`) should ONLY be used to **skip large blocks of pure logic** (e.g., a 100-line data-fetching hook at the top of a file). Do NOT use line ranges to trim CSS, JSX, or any visual code. + +Example: A page component with 50 lines of hooks/fetching at top, then 80 lines of JSX: +→ Use `--context-file src/pages/Dashboard.tsx:50` to skip the logic, keep all JSX from line 50 onward. + +**RECURSIVE IMPORT TRACING (MANDATORY — DO NOT SKIP)** + +You MUST systematically trace imports starting from the target page: + +1. **Read** the target page/route component +2. **Extract** ALL local import paths (relative `./Foo`, `../Bar`, alias `@/components/Baz` — skip node_modules) +3. **For each imported file**: read it, check if it contains UI code +4. **Repeat** for nested imports until all UI-touching files are discovered +5. **Also add**: globals.css, tailwind.config, design-system.md + +If `.superdesign/init/pages.md` exists, use it as the starting point — it pre-computes dependency trees for key pages. Still open the target page and confirm the actual render branch before passing context (do not infer layout from import names). + +**What to collect:** + +1. **Target page/feature files**: page component + ALL sub-components +2. **Layout components**: nav, sidebar, header, footer — full render code +3. **Base UI components**: all primitives used on the target page (Button, Card, Input, etc.) +4. **Styling files**: globals.css, component CSS, CSS modules +5. **Config**: tailwind.config +6. **Utilities**: cn/classnames — pass full file +7. **Brand assets & icons** (see BRAND & ICON RULES below) + +**⚠️ 1000+ LINE FILE RULE (MANDATORY):** +Any file exceeding ~1000 lines MUST use line ranges — no exceptions. Extract only the sections relevant to the target page: +- **Large CSS files (1000+ lines)**: extract ONLY the selectors/variables actually used by the target page's components. Trace each className → find its CSS definition lines → include only those sections. +- **Large component files with many variants**: extract ONLY the variant/branch being used on the target page, skip unused variants. +- **Large config files**: extract only the relevant config sections. + +⚠️ **For normal-sized files (<1000 lines)**: pass full file by default. DO NOT trim small CSS, JSX, or config files. +⚠️ **DO NOT trim JSX/template code** in normal-sized files. Every element matters for pixel-perfect accuracy. +⚠️ **ONLY use line ranges to skip pure logic blocks** (data fetching, hooks, handlers) or to extract from 1000+ line files. + +**🚨 PAYLOAD BUDGET — BUDGET OR FAIL, NEVER THIN-RETRY (the #1 cause of garbage reproductions):** +The design API rejects oversized context with a **400**. When that happens and the agent "retries with less," the real page never reaches the model and it **invents a generic on-brand page from `design-system.md`** — total garbage. Prevent it: + +1. **Budget BEFORE the call.** Sum the lines of your `--context-file` set. A big page often pulls in a 1000+ line shared header + the 1000+ line page + a 900+ line `globals.css` — that combination WILL 400. Keep the set lean: + - **Shared shell/header/nav (1000+ lines): line-range to its render section only** (e.g. the `
` JSX `:452:1247`, not the 1249-line whole file). Skip the hooks/handlers/menus above the render. + - **The target page (1000+ lines): line-range to the render branch that actually renders** (e.g. the desktop `!isMobile` block `:697:935`), not the whole multi-branch file. + - **`globals.css` (900+ lines): do NOT pass it whole.** Prefer `.superdesign/init/theme.md` (the token summary) for the values; or line-range globals to its `:root`/`.dark` token block only. +2. **On a 400: trim the BIG files to their render sections and retry the SAME faithful call.** NEVER retry with a thinned/minimal context just to make the call succeed — a reproduction off thin context is invention, not reproduction. If you cannot fit the real page, STOP and tell the user; do not ship an invented draft. +3. **Prefer a self-contained page when the user is flexible.** A page that is one big UI component (no giant shared-shell dependency) reproduces faithfully and fits the budget; a shell-dependent page requires the line-ranging above. (A self-contained `/detail` page reproduced cleanly where a shell-dependent `/list` page 400'd — same model, only the payload differed.) + +**REPRODUCTION PROMPT = STRUCTURE, NOT AESTHETIC (Step 3a only):** describe the target page's ACTUAL layout and content from the branch you READ (e.g. "two-pane: left = search + All/Public/My Team tabs + a vertical list of slim prompt rows; right = preview panel with device frame + Use prompt"). Do NOT fill a reproduction prompt with design-system adjectives ("premium", "amber→orange gradient", "elegant layered shadows", "Playfair display") — with any context gap the model will render those adjectives as a generic marketing page instead of your real page. Aesthetic language belongs in Step 3b variations, not 3a. + +**BRAND & ICON RULES:** + +1. **Brand assets (logo, brand marks)**: Scan the project for brand assets (logo SVGs, brand images). Pass logo SVG files as `--context-file` so the design reproduces the actual brand identity. Designs MUST reuse the project's real logo/brand — never replace with generic placeholders. +2. **Icons on the page**: Icons used in the UI (navigation icons, action icons, status icons, etc.) MUST be reproduced 1:1. Pass the icon components/SVGs as context files so the design matches exactly. +3. **Decorative/content images (photos, illustrations, banners)**: Use a placeholder icon or generic image block instead. Do NOT pass large image files as context — these are not reproducible in design drafts anyway. + +Summary: **Logo = real, Icons = real, Photos/images = placeholder.** + +Task 1.2 - Design system: + +- Ensure .superdesign/design-system.md exists +- If missing: create it using 'Design System Setup' rule below +- The design-system.md should capture ALL design specifications: colors, fonts, spacing, components, patterns, layout conventions, etc. + +Step 2 - Requirements gathering: +Ask the user using the session's available user-input mechanism; if none is available, ask in chat. Ask only non-obvious, high-signal questions about constraints and tradeoffs. +Do multiple rounds if answers introduce new ambiguity. +For existing project, for visual approach only ask if they want to keep the same as now OR create new design style + +Step 2.5 — Component Extraction (BEFORE creating drafts): + +After requirements gathering, extract reusable components so they are available as `` tags in design drafts. This ensures UI consistency across all generated pages. + +1. **Read `extractable-components.md`** from `.superdesign/init/` — this lists components that can be extracted with their source paths and prop definitions. +2. **Create project first** (if not already created): `npx --yes @superdesign/cli@latest create-project --title ""` +3. **Check existing components**: `npx --yes @superdesign/cli@latest list-components --project-id ` +4. **For each needed component that doesn't exist yet**: + a. Read the React source code from the path listed in `extractable-components.md` + b. Convert to Petite-Vue HTML template following the **Petite-Vue Template Spec** below + c. Create `.superdesign/tmp/` if needed. Ensure `.superdesign/tmp/` is ignored by the project's `.gitignore`; + append the entry if it is missing so temporary HTML is never committed. Then write the HTML to a file there. + d. Create the component: + ``` + npx --yes @superdesign/cli@latest create-component --project-id \ + --name "NavBar" \ + --html-file .superdesign/tmp/navbar-component.html \ + --description "Main navigation bar" \ + --props '[{"name":"activeItem","type":"string","defaultValue":"home"}]' + ``` + The default output returns the new `componentId` plus `help[]` next-step hints — add `--json` only if you need the full machine payload. +5. **Focus on layout components first** (NavBar, Sidebar, Footer, Header) — these appear on every page and benefit most from extraction. +6. **Skip basic UI primitives** (Button, Input, Card) — these are too simple to warrant extraction and are better as inline HTML in drafts. + +After extraction, proceed to Step 3. The draft generation agent will automatically see these components via `buildComponentContext()` and use `` tags in the generated HTML. + +**When to skip Step 2.5:** +- Brand new projects with no existing UI components +- When the user explicitly says they don't want component extraction +- When `extractable-components.md` doesn't exist or lists no layout components + +Step 3 — Design in Superdesign + +- Create project (IMPORTANT - MUST create project first unless project id is given by user): `npx --yes @superdesign/cli@latest create-project --title ""` + +- **Step 3a — PIXEL-PERFECT reproduction (ground truth) — MANDATORY, DO NOT SKIP**: + Before ANY design changes, FIRST create a draft that is a **100% pixel-perfect reproduction** of the current UI. + + **GOAL: Pixel-to-pixel exact match.** Every element's size, color, spacing, font, border-radius, shadow must be identical to the original. + + **CONTEXT FILES: ALL UI CODE, STRIP ONLY LOGIC** + Pass all UI-related files with full visual code. Only use line ranges to skip large blocks of pure business logic. + + ``` + npx --yes @superdesign/cli@latest create-design-draft --project-id --title "Current " \ + -p "Create a PIXEL-PERFECT reproduction of the current page. Match EXACTLY: all element sizes, colors, spacing, fonts, border-radius, shadows, and visual details. The reproduction must be indistinguishable from the original. Use the provided source code as the single source of truth." \ + --context-file .superdesign/design-system.md \ + --context-file src/layouts/AppLayout.tsx \ + --context-file src/components/Nav.tsx \ + --context-file src/components/Sidebar.tsx \ + --context-file src/pages/Target.tsx:45 \ + --context-file src/components/Target/SubComponent1.tsx \ + --context-file src/components/Target/SubComponent2.tsx \ + --context-file src/components/ui/Button.tsx \ + --context-file src/components/ui/Card.tsx \ + --context-file src/components/ui/Input.tsx \ + --context-file src/styles/globals.css \ + --context-file tailwind.config.ts \ + --context-file src/lib/cn.ts + ``` + + **Line range usage:** + - Most files: pass **full file** (default — preserves all UI details) + - Large page components with heavy logic at top: skip the logic block — e.g. `Target.tsx:45` skips 44 lines of data fetching, keeps all JSX from line 45 + - **NEVER trim CSS, config, or pure UI component files** — always pass full + + ⚠️ This step produces ONE draft with ONE -p. The -p must ONLY ask for pixel-perfect reproduction, NO design changes. + +- **Step 3b — Iterate with design variations using BRANCH mode — SEPARATE STEP**: + AFTER Step 3a completes and you have a draft-id, use `iterate-design-draft` with `--mode branch` to create design variations. + Each -p is ONE distinct variation. Do NOT combine multiple variations into a single -p. + + **VARIANT COUNT RULE**: + - Default: generate exactly **2** variations (2 `-p` flags) unless the user specifies otherwise. + - If the user explicitly requests or describes only **1** variation, generate exactly **1** `-p`. Do NOT invent extra variations the user didn't ask for. + - Only generate 3+ variations if the user explicitly asks for more. + + ``` + npx --yes @superdesign/cli@latest iterate-design-draft --draft-id \ + -p "" \ + -p "" \ + --mode branch \ + --user-request "" \ + --context-file .superdesign/design-system.md \ + --context-file src/layouts/AppLayout.tsx \ + --context-file src/components/Nav.tsx \ + --context-file src/components/Sidebar.tsx \ + --context-file src/pages/Target.tsx:45 \ + --context-file src/components/ui/Button.tsx \ + --context-file src/components/ui/Card.tsx \ + --context-file src/styles/globals.css \ + --context-file tailwind.config.ts + ``` + + ⚠️ Pass the SAME context files as Step 3a to maintain consistency. + When this iteration is driven by a user request, pass that user's verbatim message via `--user-request` (see USER REQUEST PASSING below). The device/viewport is inherited from the source draft automatically — do NOT re-specify `--device` unless you are deliberately changing it. + +- Present the `canvas` URL (from the command output) as a clickable link and the title to the user; invite them to open the canvas to watch designs stream in and leave feedback, then ask for their feedback. (`?live=1` on the canvas URL opens the live streaming view.) +- Before further iteration, MUST read the design first: `npx --yes @superdesign/cli@latest get-design --draft-id --json` + +⛔ COMMON MISTAKES — DO NOT DO THESE: + +- ❌ Skipping Step 3a and jumping straight to design changes +- ❌ Putting multiple design variations into a single create-design-draft -p (create-design-draft only accepts ONE -p, and it should be reproduction only) +- ❌ Using create-design-draft for variations — use iterate-design-draft --mode branch instead +- ❌ Combining "reproduce current UI + try 4 new designs" in one step — these are ALWAYS two separate steps +- ❌ **Trimming CSS/JSX/config files with line ranges** — NEVER trim visual code. Only use line ranges to skip data-fetching blocks +- ❌ **Missing key files** — trace imports to find all UI-touching files. Missing a layout or CSS file = broken reproduction +- ❌ **Stripping conditional UI inside the main render** — `{x && }` and ternaries are visual details, NOT edge cases. Keep them all +- ❌ **Generating too many or too few variants** — default is 2 variants in branch mode; only 1 if the user describes a single direction; 3+ only if user explicitly asks + +Extension after approval: + +- If user wants to design more relevant pages or whole user journey based on a design, use execute-flow-pages: `npx --yes @superdesign/cli@latest execute-flow-pages --draft-id --pages '[{"title":"Product Details","prompt":"Product detail page with image gallery, specs and add-to-cart"},{"title":"Checkout","prompt":"Checkout page with cart summary and payment form"}]' --context-file src/components/Foo.tsx` +- IMPORTANT: Use execute-flow-pages instead of create-design-draft for extend more pages based on existing design, create-design-draft is ONLY used for creating brand new design + +## SOP: BRAND NEW PROJECT + +Step 1 — Requirements gathering: ask the user using the session's available user-input mechanism; if none is available, ask in chat + +Step 2 — Design system setup (MUST follow Section B): + +- Run: `npx --yes @superdesign/cli@latest search-prompts --tags "style"` +- Pick the most suitable style prompt ONLY from returned results (do not do further search). +- Fetch prompt details in TWO steps (do NOT blanket-fetch full bodies): + 1. Index first with the default output to confirm the slug(s) and their size: `npx --yes @superdesign/cli@latest get-prompts --slugs ""` + 2. Then fetch the full body ONLY for the chosen slug(s) right before writing design-system.md: `npx --yes @superdesign/cli@latest get-prompts --slugs "" --full` +- Optional: `npx --yes @superdesign/cli@latest extract-brand-guide --url ""` +- Write .superdesign/design-system.md adapted to: + product context + UX flows + visual direction + +Step 3 — Design in Superdesign: + +- Create project: `npx --yes @superdesign/cli@latest create-project --title ""` +- Create initial draft (only for brand new, ⚠️ single -p only): `npx --yes @superdesign/cli@latest create-design-draft --project-id --title "" -p "" --user-request ""` +- Present the `canvas` URL(s) from the command output as clickable links; invite the user to open the canvas to watch designs stream in and leave feedback, then gather feedback and iterate. +- Iterate in BRANCH mode; + +--- + +## DESIGN SYSTEM SETUP + +Design system should provides full context across: +- Product context, key pages & architecture, key features, JTBD +- Branding & styling: color, font, spacing, shadow, layout structure, etc. +- motion/animation patterns +- Specific project requirements + +## PROMPT RULE + +⚠️ create-design-draft accepts ONLY ONE -p. For existing UI, this single -p must be a faithful reproduction prompt — NO design changes. +iterate-design-draft accepts MULTIPLE -p (each -p = one variation/branch). This is the ONLY way to create design variations. +Do NOT use multiple -p with create-design-draft — only the last -p will be kept, all others are silently lost. +Do NOT put multiple design variations into one -p string — each variation MUST be its own -p flag on iterate-design-draft. + +When using iterate-design-draft with multiple -p prompts: + +- Default to **2** `-p` prompts. If the user specifies only 1 direction, use exactly **1** `-p`. Only use 3+ if the user explicitly asks. +- Each -p must describe ONE distinct direction (e.g. "conversion-focused hero", "editorial storytelling", "dense power-user layout"). +- Do NOT invent new colors, fonts, or gradients outside the design system. The design system defines ALL allowed values. +- Every -p MUST end with a design system fidelity constraint: "Use ONLY the fonts, colors, spacing, and component styles defined in the design system. Do not introduce any fonts, colors, or visual styles not in the design system." +- Prompt should specify which to changes/explore, which parts to keep the same + +**DESIGN SYSTEM FIDELITY (CRITICAL — #1 cause of bad iterations)** + +Without explicit constraints, the Superdesign design agent will invent random fonts (serif, decorative), random colors (pink, neon, purple gradients), and random button styles. This happens because vague prompts like "bold design" or "modern feel" give the design agent creative freedom to deviate. + +To prevent this: + +1. **ALWAYS pass `--context-file .superdesign/design-system.md`** on EVERY iterate-design-draft and create-design-draft call +2. **ALWAYS pass `--context-file `** on EVERY call — this contains the actual CSS tokens +3. **ALWAYS append the fidelity constraint** to every -p prompt (see above) +4. **Be explicit about what MUST stay the same** — e.g. "keep Inter as the font family, use black/white primary palette, amber/orange brand gradients only" + +## EXECUTE FLOW RULE + +When using execute-flow-pages: + +- MUST ideate the details of each page, then confirm all pages and each prompt with the user using the session's available user-input mechanism; if none is available, ask in chat + +## TOOL USE RULE + +Default tool while iterating design of a specific page is iterate-design-draf +Default mode is branch +You may ONLY use replace if user request a tiny tweak, you can describe it in one sentence and user is okay overwriting the previous version. +Default tool while generating new pages based on an existing confirmed page is execute-flow-pages + + +... +User: I don't like the book demo banner's position, help me figure out a few other ways +Assistant: +- First, let me read the .superdesign/init/ files to understand the project structure... +- Let me read the design to understand how it looks, `npx --yes @superdesign/cli@latest get-design --draft-id --json`... +- Got it, can you clarify why you didn't like the current banner position? [propose a few potential options using the session's available user-input mechanism; if none is available, ask in chat] +User: [Give answer] +Assistant: +- Let me ideate a few other ways to position the banner based on this: +npx --yes @superdesign/cli@latest iterate-design-draft --draft-id +--prompt "Move the book demo banner sticky at the top, remain anything else the same" +--prompt "Remove banner for book demo, instead add a card near the template project cards for book demo, remain anything else the same" +--mode branch +--user-request "I don't like the book demo banner's position, help me figure out a few other ways" +--context-file .superdesign/design-system.md +--context-file src/components/Banner.tsx +--context-file src/pages/Home.tsx:40 +--context-file src/layouts/AppLayout.tsx +--context-file src/components/Nav.tsx +--context-file src/components/Sidebar.tsx +--context-file src/components/ui/Button.tsx +--context-file src/components/ui/Card.tsx +--context-file src/styles/globals.css +--context-file tailwind.config.ts +... +User: great I like the card version, help me design the full book demo flow +Assistant: +- Let me think through the core user journey and pages involved... confirm them using the session's available user-input mechanism; if none is available, ask in chat +- npx --yes @superdesign/cli@latest execute-flow-pages --draft-id --pages '[{"title":"Signup","prompt":"..."},{"title":"Payment","prompt":"..."}]' \ + --context-file .superdesign/design-system.md \ + --context-file src/components/Banner.tsx \ + --context-file src/layouts/AppLayout.tsx \ + --context-file src/components/ui/Button.tsx \ + --context-file src/components/ui/Input.tsx \ + --context-file src/components/ui/Card.tsx \ + --context-file src/styles/globals.css + + +## USER REQUEST PASSING + +When you run `create-design-draft` or `iterate-design-draft` on behalf of a user request, you SHOULD pass the user's verbatim message for that round via `--user-request ""`. + +- Pass the user's ACTUAL words for this round (not your paraphrase, not the design-system-fidelity boilerplate). This is the caller-side signal the design backend uses to improve generation quality. +- This is separate from `-p`/`--prompt`: `-p` is the directional design instruction(s) you author; `--user-request` is the raw human ask that motivated them. +- Transparency: the text is shared with SuperDesign and stored server-side to improve generation. Keep it to the round's request; the field is capped at 16KB (truncate if longer). +- It is optional. Omit it for agent-initiated steps that no user directly asked for (e.g. the Step 3a pixel-perfect reproduction). + +## VERSION HISTORY & REVERT + +Every draft keeps a version history. The CLI's default output already self-discloses version anchoring (`currentVersion`/`versions` plus `help[]` hints), so discover version numbers with `get-design` rather than tracking them by hand. + +- **Iterate from an earlier version**: `iterate-design-draft ... --from-version ` starts from a specific historical version instead of the current head. +- **Revert to an earlier version** (no generation): `npx --yes @superdesign/cli@latest revert-design-draft --draft-id --to-version ` restores a prior version as the current head. The revert is itself reversible — the current head is snapshotted into history first — so it is always safe to try. Use `get-design` to find the version number to restore. + +## ALWAYS-ON RULES + +- Design system file path is fixed: .superdesign/design-system.md +- design-system.md = ALL design specs +- **MANDATORY INIT (real-codebase path)**: When a real codebase is present, if `.superdesign/init/` is missing or incomplete you MUST run the full init analysis FIRST (follow the INIT instructions from the skill); if it exists, you MUST read ALL files (components.md, layouts.md, routes.md, theme.md, pages.md, extractable-components.md) at the START of every design task. This is NOT optional. On the no-codebase path (empty/scratch/sandbox workspace, no frontend code — see SKILL.md Step 1), there is nothing to init: skip it and gather design context conversationally instead. +- **MANDATORY CONTEXT FILES on EVERY design command** (create-design-draft, iterate-design-draft, execute-flow-pages): + - `--context-file .superdesign/design-system.md` — so the design agent knows the allowed fonts, colors, spacing + - `--context-file ` — so the design agent has the actual CSS tokens and variables + - These two files are NON-NEGOTIABLE. Never skip them, even if they were already set as project prompt. +- **DESIGN SYSTEM = HARD CONSTRAINT, NOT SUGGESTION**: Iteration prompts explore layout/structure/content direction, NOT visual style. The design system defines the visual style. Never let a -p prompt override the design system. +- **ALL UI CODE, STRIP ONLY DATA-FETCHING**: Pass all UI-related files with complete visual code. Use line ranges ONLY to skip data-fetching blocks or to extract from 1000+ line files. Keep ALL conditional rendering, state, props, and JSX. +- **1000+ LINE FILES MUST USE LINE RANGES.** Extract only the sections relevant to the target page. This applies to large CSS files, large component libraries, and large configs. +- **TRACE ALL UI FILES.** Use import tracing to find all files that touch UI. Include them with full UI code. For large mixed files (logic + UI), use line ranges to skip the logic portion only. +- **VARIANT COUNT**: Default to **2** variations in branch mode. If the user describes only **1** direction, generate exactly **1**. Only generate 3+ if the user explicitly requests more. Never invent extra variations. +- Prefer iterating existing design draft over creating new ones. +- When designing for existing UI, MUST pass relevant source files via --context-file to give Superdesign real codebase context +- **PIXEL-PERFECT GROUND TRUTH FIRST**: For existing UI, ALWAYS create a 100% pixel-perfect reproduction draft (Step 3a) before making design changes (Step 3b). The reproduction must match EXACTLY — sizes, colors, spacing, fonts, shadows, border-radius. Never skip straight to redesign. Never combine reproduction and design changes in one command. +- **TWO-STEP WORKFLOW**: Step 3a = `create-design-draft` with reproduction-only prompt → Step 3b = `iterate-design-draft --mode branch` with variation prompts. These are ALWAYS two separate commands. +- **COMPLETE CONTEXT**: Always include shared/global layout files (nav, sidebar, header, footer, layout wrapper) in --context-file, not just the target component. +- **INCLUDE IMPLEMENTATION FILES**: Context files should be actual implementation (.tsx, .css, .ts) — not just documentation (.md). The AI needs real code to reproduce accurately. +- **CLI VERSION**: Always invoke the current CLI with `npx --yes @superdesign/cli@latest`. If a flag is not recognized, inspect `npx --yes @superdesign/cli@latest --help` and follow the command contract below; never install or upgrade the CLI globally. +- **DEFAULT OUTPUT IS FOR AGENTS**: Every command's default output is already agent-optimized (compact TOON plus `help[]` next-step hints). Read it directly; add `--json` only when you genuinely need the full machine-readable payload, and `--full` only to expand truncated fields (e.g. a chosen prompt body). Do NOT reflexively append `--json`/`--full`. +- **USER REQUEST PASSING**: When a `create-design-draft`/`iterate-design-draft` call is driven by a user request, pass the user's verbatim round message via `--user-request ""` (see USER REQUEST PASSING above). +- **DEVICE/VIEWPORT INHERITANCE**: `iterate-design-draft` inherits the device/viewport from the source draft automatically. Do NOT re-specify `--device`/`--width`/`--height` on iterate chains unless you are deliberately changing the viewport. + +--- + +## CONTEXT FILE LINE RANGES + +`--context-file` supports an optional `:startLine:endLine` suffix to include only specific portions of a file: + +| Syntax | Meaning | +| ---------------------------------- | ------------------------------------- | +| `--context-file src/App.tsx` | Full file (default) | +| `--context-file src/App.tsx:10:50` | Lines 10-50 only (1-based, inclusive) | +| `--context-file src/App.tsx:10` | From line 10 to end of file | + +Multiple ranges from the same file are automatically merged into a single context entry with omission markers between non-contiguous ranges. + +**Default is FULL FILE** for normal-sized files. Use line ranges in two cases: skipping pure logic, or extracting from very large files. + +**When to use line ranges:** + +1. **Pure logic blocks** — page components with data-fetching/hooks at the top, skip the logic, keep all JSX + - e.g. `--context-file src/pages/Dashboard.tsx:60` — skips 59 lines of hooks/fetching, keeps JSX from line 60 +2. **1000+ line files (MANDATORY)** — always extract only the relevant sections: + - Large CSS files: extract only selectors used by target page — e.g. `--context-file src/styles/globals.css:1:120` for CSS variables + `--context-file src/styles/globals.css:800:900` for relevant component styles + - Large component libraries: extract only the variant/component actually used + - Large config files: extract relevant config block + +**When to use full files (DEFAULT):** + +- Normal-sized files (<1000 lines) — always full +- ALL UI components (Button, Card, Nav, Sidebar, etc.) — always full +- ALL layout files — always full +- Any file where UI and logic are interleaved (safer to include everything) + +--- + +## PETITE-VUE TEMPLATE SPEC (for component extraction) + +When converting React components to Petite-Vue HTML templates for `create-component`: + +### What to HARDCODE in the template (NOT props): +- Icon names and SVG markup +- Text labels, menu item names +- Image sources and alt text +- CSS classes and all styling +- Structural HTML and layout +- Color values, font sizes, spacing + +### What to EXTRACT as props (ONLY these categories): +- **Active state**: `activeItem`, `isActive`, `currentTab` — indicates which page/section is selected +- **Navigation URLs**: `homeHref`, `searchHref`, `profileHref` — link destinations +- **Conditional visibility**: `showNotification`, `showBadge`, `isExpanded` — toggle elements +- **Dynamic counts**: `badgeCount`, `notificationCount` — numeric values that change + +### Allowed Petite-Vue syntax: +- `{{ propName }}` — text interpolation +- `:href="propName"` — attribute binding +- `v-if="propName"` / `v-show="propName"` — conditional rendering +- `:class="{ 'active': activeItem === 'home' }"` — dynamic class binding +- `@click="$emit('name', payload)"` — event emission + +### NOT allowed: +- `v-for` for navigation items (hardcode each item instead) +- `v-model` (no two-way binding) +- `v-html` (no raw HTML injection) +- Complex JavaScript expressions in templates + +### Every prop MUST have a non-empty `defaultValue`. + +### Output requirements: +- Valid HTML with Tailwind CSS classes +- Replace all CSS modules / styled-components with Tailwind utilities or inline styles +- Use Lucide icon CDN or inline SVGs for icons +- Include reasonable `previewWidth` and `previewHeight` estimates in the component description + +### Example conversion: + +**React source:** +```tsx +function NavBar({ activeItem = 'home' }) { + return ( + + ); +} +``` + +**Petite-Vue template:** +```html + +``` + +**Props:** +```json +[ + {"name": "activeItem", "type": "string", "defaultValue": "home"}, + {"name": "homeHref", "type": "string", "defaultValue": "#"}, + {"name": "exploreHref", "type": "string", "defaultValue": "#"} +] +``` + +--- + + +| Category | Platform | Asset Type | Aspect Ratio | Recommended Size (px) | +| --------- | ---------------------- | --------------------- | ------------ | --------------------- | +| Feed | Instagram | Feed Post (Square) | 1:1 | 1080 × 1080 (default) | +| Feed | Instagram | Feed Post (Portrait) | 4:5 | 1080 × 1350 | +| Feed | Instagram | Feed Post (Landscape) | 1.91:1 | 1080 × 566 | +| Feed | Facebook | Feed Post | 1.91:1 | 1200 × 630 | +| Feed | LinkedIn | Feed Post | 1:1 | 1200 × 1200 (default) | +| Feed | LinkedIn | Feed Post (Landscape) | 1.91:1 | 1200 × 627 | +| Feed | X / Twitter | Post Image | 16:9 | 1200 × 675 | +| Feed | Threads | Post Image | 1:1 | 1080 × 1080 | +| Vertical | Instagram | Story | 9:16 | 1080 × 1920 | +| Vertical | Instagram | Reel Cover | 9:16 | 1080 × 1920 | +| Vertical | TikTok | Video / Cover | 9:16 | 1080 × 1920 | +| Vertical | YouTube | Shorts | 9:16 | 1080 × 1920 | +| Carousel | Instagram | Carousel Slide | 4:5 | 1080 × 1350 | +| Carousel | LinkedIn | Carousel (PDF slides) | 1:1 | 1080 × 1080 | +| Cover | LinkedIn | Profile Cover | 4:1 | 1584 × 396 | +| Cover | Facebook | Page Cover | ~1.9:1 | 1640 × 856 | +| Cover | X / Twitter | Header | 3:1 | 1500 × 500 | +| Cover | YouTube | Channel Art | 16:9 | 2560 × 1440 | +| Thumbnail | YouTube | Video Thumbnail | 16:9 | 1280 × 720 | +| Ads | Google Display Ads | Medium Rectangle | 4:3 | 300 × 250 | +| Ads | Google Display Ads | Large Rectangle | 336 × 280 | | +| Ads | Google Display Ads | Leaderboard | 728 × 90 | | +| Ads | Google Display Ads | Large Leaderboard | 970 × 90 | | +| Ads | Google Display Ads | Billboard | 970 × 250 | | +| Ads | Google Display Ads | Half Page | 300 × 600 | | +| Ads | Google Display Ads | Large Mobile Banner | 320 × 100 | | +| Ads | Google Display Ads | Mobile Banner | 320 × 50 | | +| Ads | Google Display Ads | Square | 250 × 250 | | +| Ads | Google Display Ads | Small Square | 200 × 200 | | +| Ads | Google Performance Max | Landscape Image | 1.91:1 | 1200 × 628 | +| Ads | Google Performance Max | Square Image | 1:1 | 1200 × 1200 | +| Ads | Google Performance Max | Portrait Image | 4:5 | 960 × 1200 | +| Ads | Google App Ads | App Landscape | 1.91:1 | 1200 × 628 | +| Ads | Google App Ads | App Square | 1:1 | 1200 × 1200 | + +For marketing assets, MUST confirm with the user the dimension before creating, do NOT assume the dimension + + +--- + +## COMMAND CONTRACT (DO NOT HALLUCINATE FLAGS) + +Every command supports `--json` for the full machine-readable payload; the default output is agent-optimized (TOON + `help[]`). Only the flags below `--json` (e.g. `--full`, `--user-request`) are per-command. + +- create-project: required `--title`; optional `--template `, `--device ` (default: desktop), `--extend-from `, `--json` +- iterate-design-draft: + - required `--draft-id`, `-p`/`--prompt`, and `--mode `; optional `--context-file` (one or more paths; supports `path:startLine:endLine`), `--model`, `--user-request `, `--json` + - branch: can include multiple `-p` prompts; optional `--count <1-4>` is valid only with a single prompt + - replace: use exactly one `-p`; do not use `--count` + - `--from-version `: iterate from a specific historical version instead of the current head (discover version numbers via `get-design`) + - `--device ` / `--width ` / `--height `: OVERRIDE the viewport. Defaults to the source draft's device — omit unless deliberately changing it. `--width`/`--height` require `--device custom`. +- create-design-draft: required `--project-id`, `--title`, and `-p` (SINGLE prompt only); optional `--device ` (default: desktop), `--width `, `--height `, `--context-file` (one or more paths; supports `path:startLine:endLine`), `--model`, `--user-request `, `--json` + - ⚠️ ONLY accepts ONE -p flag. Multiple -p flags will silently drop all but the last one. + - Combine all design directions into a single -p string. + - Only use this for creating purely new design from scratch. + - --device custom requires both --width and --height (min 20px each). Providing --width/--height auto-sets --device to custom. +- revert-design-draft: required `--draft-id`, `--to-version `; optional `--json`. Restores a prior version as the current head with NO generation; reversible (the current head is snapshotted into history first). Discover version numbers via `get-design`. +- execute-flow-pages: required `--draft-id`, `--pages`; optional `--context`, `--context-file` (one or more paths; supports `path:startLine:endLine`), `--model`, `--json` +- get-design: required `--draft-id`; optional `--json`, `--output ` +- create-component: required `--project-id`, `--name` (PascalCase), and exactly one of `--html` or `--html-file`; optional `--description`, `--props` (JSON array), `--slots` (JSON array), `--events` (JSON array), `--css-imports` (JSON array), `--json` +- update-component: required `--component-id`; optional `--name`, `--html` or `--html-file` (not both), `--description`, `--props` (JSON array), `--slots` (JSON array), `--events` (JSON array), `--css-imports` (JSON array), `--json` +- list-components: required `--project-id`; optional `--full`, `--json` +- list-design-systems: no required flags; optional `--full`, `--json`. Lists default design systems and projects available to `create-project --extend-from`. +- search-prompts: no required flags; optional `--query `, `--tags `, `--limit ` (default 20, max 100), `--offset `, `--full`, `--json` +- get-prompts: required `--slugs `; optional `--full` (print full prompt bodies instead of the compact index), `--json`. Index first with the default output, then re-run with `--full` for the chosen slug(s) only. +- extract-brand-guide: required `--url `; optional `--output-dir ` (default `.superdesign/brand_styles/`), `--json` + +**Supported --model values**: gemini-3-flash, gemini-3.1-pro, claude-haiku-4-5, claude-sonnet-5, claude-opus-4-8, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5-mini, grok-4.5, kimi-k2.6, deepseek-v4-pro, deepseek-v4-flash, glm-5.2 +If --model is omitted, the backend uses the default model. Only pass --model when the user explicitly requests a specific model. diff --git a/.superdesign/design-system.md b/.superdesign/design-system.md new file mode 100644 index 0000000..dfa2929 --- /dev/null +++ b/.superdesign/design-system.md @@ -0,0 +1,77 @@ +# Design System — OSM → NS-3 Mobility Trace Converter (Frontend) + +## Product Context +Web frontend for an **OSM → NS-3 Mobility Trace Conversion** service (FastAPI + SUMO pipeline). The backend converts OpenStreetMap files into NS-3 mobility traces (`ns_movements`, `mobility.tcl`) via a 5-stage pipeline, and exposes a CORS-enabled REST API. + +**Audience:** network-simulation researchers, VANET engineers, and developers. Technically literate; they think in terms of pipeline stages, parameters, logs, and traces. The UI must feel precise, structured, and trustworthy — like good engineering tooling. + +**Core job (JTBD):** Upload an OSM file → configure the pipeline → watch it run → download NS-3 traces. + +## Visual Direction: "Reality-First" (Poster-Modernist Technical) +Rejects SaaS fluff in favor of a structured, modernist, technical aesthetic. Strict grid, monochromatic base with a single vibrant accent, heavy typographic contrast, flat surfaces. Establishes authority and clarity. + +### Palette +| Token | Hex | Usage | +|-------|-----|-------| +| `--bg` | `#E3E2DE` | Primary background (warm cream) | +| `--surface` | `#EDECEA` | Elevated surface / card fill (slightly lighter than bg) | +| `--ink` | `#141414` | Primary text (jet black) | +| `--ink-2` | `#444343` | Secondary text (deep gray) | +| `--muted` | `#7A7A7A` | Labels, metadata, muted text | +| `--line` | `#C7C7C7` | Borders, dividers, grid lines (1px solid) | +| `--accent` | `#1351AA` | Primary accent (cobalt blue) — links, primary actions, active states | +| `--accent-ink` | `#E3E2DE` | Text on accent fills | + +**Functional status tokens (restrained, functional only):** +- `--status-pending`: `#7A7A7A` (muted gray) +- `--status-running`: `#1351AA` (cobalt — matches accent; pulse animation) +- `--status-done`: `#1E7A46` (muted green — success) +- `--status-failed`: `#B4231F` (muted red — failure) +- `--status-skipped`: `#9A9A9A` + +### Typography +- **Headings / Display:** `'Inter'` (sans-serif), weight 700–900, tight tracking (`letter-spacing: -0.03em`), line-height 0.9. May use uppercase for section headers. +- **Body:** `'Inter'`, weight 400–500, 14–16px, line-height 1.5, color `--ink-2`. +- **Technical / Metadata:** `'JetBrains Mono'` (monospace) — endpoints, job IDs, durations, stage names, file names, status tags. Uppercase, tracked out (`letter-spacing: 0.15em`), 11–13px, color `--muted`. +- For React+Tailwind: load `Inter` + `JetBrains Mono` via Google Fonts in `index.html`. + +### Structural Elements +- **Grid:** Strict 12-column layout. Reserve left 3 columns for section labels/metadata; main content in columns 4–12 (the "Label Sidebar" pattern). +- **Borders:** 1px solid `--line` to separate every section, card, row, and cell. This is the primary depth mechanism. +- **Corners:** **0px border-radius throughout** (sharp, technical, poster-modernist). No exceptions for cards/buttons/panels. (Inputs may use 0px as well — keep it consistent.) +- **Shadows:** **None.** Use flat color blocks and 1px borders for separation, never drop shadows. (The only allowed "depth" is a subtle background-tint hover.) +- **Motion:** Fast, linear transitions (0.2–0.3s) for color changes only. No bouncy/organic easing. Status dots may pulse. + +### Navigation / Shell +- Sticky top bar, ~64–80px, cream bg at 95% opacity with subtle backdrop-blur, 1px bottom border `--line`. +- 12-col grid: columns 1–3 = logo (uppercase, bold, tracking-tight, e.g. `OSM→NS3` with a small black square mark). Columns 4–9 = empty or a system-status indicator. Columns 10–12 = right-aligned nav links (text-sm, font-semibold, uppercase tracked). +- Pages: **Dashboard**, **New Job**, **Jobs/Detail**, **System**. + +### Key Components (Petite-Vue / HTML for drafts) +- **Poster Button:** rectangular, 0px radius, padding 14px 28px, 13px bold uppercase tracked label. Primary = `--accent` fill / `--accent-ink` text; hover → `--ink` fill. Secondary = `--ink` fill / cream text, or transparent with 1px `--line` border + `--ink` text, hover → `--surface` bg. Ghost = text-only underline on hover. +- **Mono Status Tag:** flex row, mono uppercase 10–11px tracked, with a 6px dot. Color = status token. e.g. `● RUNNING` (cobalt, pulsing). +- **Job Row / Table:** full-width rows separated by 1px top border. Columns: Job ID (mono, truncated), Status (tag), Created (mono), Stages mini-progress (5 ticks), Actions (View / Delete). Hover → `--surface` bg. Alternating subtle tone optional (white/#EDECEA vs cream). +- **Pipeline Stepper:** horizontal 5-step track — `netconvert → random_trips → duarouter → sumo → trace_export`. Each step shows index, mono stage name, and a status dot/bar. Current step = cobalt + pulse; done = green check; failed = red; pending = gray. Connect with 1px lines. +- **Config Section (guided):** grouped card per stage (`netconvert`, `random_trips`, `sumo`, `trace_exporter`). Each shows 3–5 key params as labeled inputs (top-aligned labels, mono field names, 1px border inputs, focus → border `--ink`). A collapsible "Advanced" region reveals remaining params. Prefill from `/defaults`. +- **Log Block:** monospace, 12–13px, 0px radius, 1px `--line` border, `--ink` on `--surface` or near-black ` #141414` block with `#EDECEA` text for stderr emphasis. Show `=== stdout ===` / `=== stderr ===` separators. +- **Health Tile:** grid of tool-availability tiles (`netconvert`, `duarouter`, `sumo`, `traceExporter.py`, `randomTrips.py`), each a status tag + availability. +- **File / Download Row:** filename (mono), size (mono, muted), download link/button. + +## UX Flows & Pages (design these 4) +1. **Dashboard** — Jobs overview: header with `New Job` CTA + system-status pill; a jobs table (most recent first) with status, created-at, stage mini-progress, actions; empty/loading/error states; a compact health strip (`/health`). +2. **New Job** — Upload card (drag/drop `.osm`/`.osm.pbf` + filename display), then a guided config editor grouped by the 4 stages with collapsible advanced sections prefilled from `/defaults`; submit → `POST /jobs` (multipart) → redirect to Job Detail. Show `GET /schema`/`/defaults` usage hint. +3. **Job Detail** — Job header (ID mono, status tag, created/completed). Live **pipeline stepper** (poll `GET /jobs/{id}`). Tabs/sections: **Progress** (stepper + current stage + warnings), **Outputs** (`GET /jobs/{id}/files` → download rows), **Logs** (`GET /jobs/{id}/logs` → per-stage stdout/stderr), **Config** (`GET /jobs/{id}/config` → JSON), **Delete** action (`DELETE /jobs/{id}`, disabled while running). +4. **System** — `GET /health` view: overall `status` (`ready`/`degraded`), `pipeline_ready` flag, and a grid of SUMO tool-availability tiles. Monospace, technical, status-colored. + +## API-Aware Content (use real names — do not invent) +- **Stages (order):** `netconvert`, `random_trips`, `duarouter`, `sumo`, `trace_export`. +- **Status values:** `pending` → `running` → `done` | `failed` (also `skipped`). +- **Config groups:** `netconvert`, `random_trips`, `sumo`, `trace_exporter`. Example key params: netconvert → `osm_highway_types`, `default_speed_limit`, `tl_type`, `proj_utm`; random_trips → `end`, `period`, `vehicle_class`, `seed`; sumo → `end`, `step_length`, `fcd_output_period`, `default_max_speed`; trace_exporter → `output_format` (ns_movements|tcl|both), `sampling_period`, `penetration_rate`. +- **Output files:** `ns_movements`, `mobility.tcl`, `tripinfo.xml`, `summary.xml`, `fcd-output.xml`. +- **Endpoints:** `POST /jobs`, `GET /jobs`, `GET /jobs/{id}`, `GET /jobs/{id}/files`, `GET /jobs/{id}/download/{filename}`, `GET /jobs/{id}/logs`, `GET /jobs/{id}/config`, `DELETE /jobs/{id}`, `GET /health`, `GET /schema`, `GET /defaults`. + +## Implementation Notes (React + Tailwind + Vite) +- Tailwind theme extend tokens: `bg`, `surface`, `ink`, `ink-2`, `muted`, `line`, `accent`, `accent-ink`, plus status colors. Set `borderRadius` default to 0. +- Fonts: `Inter` (sans) + `JetBrains Mono` (mono) via Google Fonts in `index.html`. +- Use 1px `--line` borders + background tints for all separation; **never** drop shadows. +- Mono utility class for technical text (`font-mono uppercase tracking-[0.15em] text-[11px] text-muted`). diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a1e6ec0 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +dist-ssr +*.local +.DS_Store +.vite diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..aa9ab7a --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,18 @@ + + + + + + OSM → NS-3 Mobility Trace Converter + + + + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..b99d84e --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2761 @@ +{ + "name": "osm-ns3-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "osm-ns3-frontend", + "version": "0.1.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.2" + }, + "devDependencies": { + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.13", + "typescript": "^5.5.4", + "vite": "^5.4.8" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.393", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz", + "integrity": "sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..29ce008 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,26 @@ +{ + "name": "osm-ns3-frontend", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.2" + }, + "devDependencies": { + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.13", + "typescript": "^5.5.4", + "vite": "^5.4.8" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..bc63569 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,22 @@ +import { Routes, Route } from 'react-router-dom'; +import { Nav } from './components/Nav'; +import Dashboard from './pages/Dashboard'; +import NewJob from './pages/NewJob'; +import JobDetail from './pages/JobDetail'; +import System from './pages/System'; + +export default function App() { + return ( +
+
+ ); +} diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts new file mode 100644 index 0000000..989f95f --- /dev/null +++ b/frontend/src/api/client.ts @@ -0,0 +1,70 @@ +import type { + ConversionRequest, + HealthResponse, + JobFilesResponse, + JobLogsResponse, + JobStatus, +} from './types'; + +const API_BASE: string = + (import.meta.env.VITE_API_BASE as string | undefined) || 'http://localhost:8000'; + +async function request(path: string, init?: RequestInit): Promise { + const res = await fetch(`${API_BASE}${path}`, init); + if (!res.ok) { + let detail = ''; + try { + const j = await res.json(); + detail = j.detail ?? JSON.stringify(j); + } catch { + detail = await res.text(); + } + throw new Error(detail || `Request failed: ${res.status}`); + } + if (res.status === 204) return undefined as T; + return (await res.json()) as T; +} + +export interface CreateJobResult { + job_id: string; + message: string; + config_snapshot: ConversionRequest; +} + +export const api = { + base: API_BASE, + + listJobs: () => request('/jobs'), + getJob: (id: string) => request(`/jobs/${id}`), + getJobFiles: (id: string) => + request(`/jobs/${id}/files`), + getJobLogs: (id: string) => request(`/jobs/${id}/logs`), + getJobConfig: (id: string) => + request(`/jobs/${id}/config`), + deleteJob: (id: string) => + request(`/jobs/${id}`, { method: 'DELETE' }), + getHealth: () => request('/health'), + getDefaults: () => request('/defaults'), + getSchema: () => request('/schema'), + + createJob: async ( + osmFile: File, + config: ConversionRequest, + ): Promise => { + const fd = new FormData(); + fd.append('osm_file', osmFile); + fd.append('config', JSON.stringify(config)); + const res = await fetch(`${API_BASE}/jobs`, { method: 'POST', body: fd }); + if (!res.ok) { + let detail = ''; + try { + const j = await res.json(); + detail = j.detail ?? JSON.stringify(j); + } catch { + detail = await res.text(); + } + throw new Error(detail || `Upload failed: ${res.status}`); + } + return (await res.json()) as CreateJobResult; + }, +}; diff --git a/frontend/src/api/configSchema.ts b/frontend/src/api/configSchema.ts new file mode 100644 index 0000000..d3c80c9 --- /dev/null +++ b/frontend/src/api/configSchema.ts @@ -0,0 +1,149 @@ +export type FieldType = + | 'number' + | 'text' + | 'boolean' + | 'select' + | 'multiselect'; + +export interface FieldDef { + key: string; + label: string; + type: FieldType; + options?: string[]; + min?: number; + max?: number; + step?: number; + /** When true, an empty "(none)" option is allowed (server default null). */ + nullable?: boolean; + group: 'key' | 'advanced'; + placeholder?: string; +} + +export interface SectionDef { + key: string; + title: string; + fields: FieldDef[]; +} + +const HIGHWAY = [ + 'motorway', 'motorway_link', 'trunk', 'trunk_link', 'primary', 'primary_link', + 'secondary', 'secondary_link', 'tertiary', 'tertiary_link', 'residential', + 'living_street', 'unclassified', 'service', 'road', 'track', 'footway', + 'cycleway', 'path', 'steps', 'pedestrian', 'bus_guideway', 'raceway', + 'construction', +]; +const VCLASS = [ + 'passenger', 'bus', 'truck', 'motorcycle', 'bicycle', 'pedestrian', + 'emergency', 'authority', 'army', 'vip', 'hov', 'custom1', 'custom2', +]; +const JTYPE = [ + 'priority', 'traffic_light', 'right_before_left', 'unregulated', + 'allway_stop', 'zipper', +]; +const TLTYPE = ['static', 'actuated', 'delay_based', 'sotl_phase', 'sotl_platoon']; +const EREM = ['all', 'noFringe']; +const OFMT = ['ns_movements', 'tcl', 'both']; +const SMODE = ['right_of_way', 'no_checks', 'all_checks']; +const LCMODE = ['default', 'no_lc', 'strategic']; +const COLL = ['warn', 'teleport', 'remove', 'none']; + +export const SECTIONS: SectionDef[] = [ + { + key: 'netconvert', + title: 'Netconvert', + fields: [ + { key: 'osm_highway_types', label: 'osm_highway_types', type: 'multiselect', options: HIGHWAY, group: 'key' }, + { key: 'default_speed_limit', label: 'default_speed_limit (m/s)', type: 'number', min: 1, max: 83.3, step: 0.1, group: 'key' }, + { key: 'default_junction_type', label: 'default_junction_type', type: 'select', options: JTYPE, group: 'key' }, + { key: 'tl_type', label: 'tl_type', type: 'select', options: TLTYPE, group: 'key' }, + { key: 'proj_utm', label: 'proj_utm', type: 'boolean', group: 'key' }, + { key: 'osm_remove_isolated_edges', label: 'osm_remove_isolated_edges', type: 'boolean', group: 'advanced' }, + { key: 'osm_no_large_roundabouts', label: 'osm_no_large_roundabouts', type: 'boolean', group: 'advanced' }, + { key: 'osm_oneway_spread', label: 'osm_oneway_spread', type: 'boolean', group: 'advanced' }, + { key: 'geometry_remove_isolated_nodes', label: 'geometry_remove_isolated_nodes', type: 'boolean', group: 'advanced' }, + { key: 'geometry_no_internal_links', label: 'geometry_no_internal_links', type: 'boolean', group: 'advanced' }, + { key: 'junctions_internal_link_detail', label: 'junctions_internal_link_detail', type: 'number', min: 1, max: 20, group: 'advanced' }, + { key: 'junctions_corner_detail', label: 'junctions_corner_detail', type: 'number', min: 1, max: 20, group: 'advanced' }, + { key: 'junctions_min_size', label: 'junctions_min_size (m)', type: 'number', min: 0, max: 20, step: 0.1, group: 'advanced' }, + { key: 'junctions_limit_turn_speed', label: 'junctions_limit_turn_speed (m/s)', type: 'number', min: 0, max: 30, step: 0.1, group: 'advanced' }, + { key: 'default_lane_width', label: 'default_lane_width (m)', type: 'number', min: 1, max: 10, step: 0.1, group: 'advanced' }, + { key: 'default_num_lanes', label: 'default_num_lanes', type: 'number', min: 1, max: 8, group: 'advanced' }, + { key: 'no_turnarounds', label: 'no_turnarounds', type: 'boolean', group: 'advanced' }, + { key: 'no_left_connections', label: 'no_left_connections', type: 'boolean', group: 'advanced' }, + { key: 'tl_guess', label: 'tl_guess', type: 'boolean', group: 'advanced' }, + { key: 'tl_join', label: 'tl_join', type: 'boolean', group: 'advanced' }, + { key: 'tl_min_dur', label: 'tl_min_dur (s)', type: 'number', min: 1, max: 120, group: 'advanced' }, + { key: 'tl_max_dur', label: 'tl_max_dur (s)', type: 'number', min: 5, max: 300, group: 'advanced' }, + { key: 'keep_edges_by_vclass', label: 'keep_edges_by_vclass', type: 'select', options: VCLASS, nullable: true, group: 'advanced' }, + { key: 'remove_edges_by_type', label: 'remove_edges_by_type', type: 'text', placeholder: 'footway,cycleway,path', group: 'advanced' }, + { key: 'keep_fringe', label: 'keep_fringe', type: 'select', options: EREM, group: 'advanced' }, + { key: 'proj_plain_geo', label: 'proj_plain_geo', type: 'boolean', group: 'advanced' }, + ], + }, + { + key: 'random_trips', + title: 'Random Trips', + fields: [ + { key: 'end', label: 'end (s)', type: 'number', min: 1, group: 'key' }, + { key: 'period', label: 'period (s)', type: 'number', min: 0.1, max: 3600, step: 0.1, group: 'key' }, + { key: 'vehicle_class', label: 'vehicle_class', type: 'select', options: VCLASS, group: 'key' }, + { key: 'seed', label: 'seed', type: 'number', min: 0, group: 'key' }, + { key: 'begin', label: 'begin (s)', type: 'number', min: 0, group: 'advanced' }, + { key: 'insertion_density', label: 'insertion_density (/h/km)', type: 'number', min: 0, group: 'advanced' }, + { key: 'min_distance', label: 'min_distance (m)', type: 'number', min: 0, group: 'advanced' }, + { key: 'max_distance', label: 'max_distance (m)', type: 'number', min: 0, group: 'advanced' }, + { key: 'fringe_factor', label: 'fringe_factor', type: 'number', min: 0, max: 100, step: 0.1, group: 'advanced' }, + { key: 'fringe_threshold', label: 'fringe_threshold', type: 'number', min: 0, max: 1, step: 0.01, group: 'advanced' }, + { key: 'validate_routes', label: 'validate_routes', type: 'boolean', group: 'advanced' }, + { key: 'allow_loops', label: 'allow_loops', type: 'boolean', group: 'advanced' }, + { key: 'random_depart_pos', label: 'random_depart_pos', type: 'boolean', group: 'advanced' }, + { key: 'random_arrival_pos', label: 'random_arrival_pos', type: 'boolean', group: 'advanced' }, + ], + }, + { + key: 'sumo', + title: 'SUMO Simulation', + fields: [ + { key: 'end', label: 'end (s)', type: 'number', min: 1, group: 'key' }, + { key: 'step_length', label: 'step_length (s)', type: 'number', min: 0.01, max: 10, step: 0.01, group: 'key' }, + { key: 'fcd_output_period', label: 'fcd_output_period (s)', type: 'number', min: 0.01, max: 60, step: 0.01, group: 'key' }, + { key: 'default_max_speed', label: 'default_max_speed (m/s)', type: 'number', min: 1, max: 200, step: 0.1, group: 'key' }, + { key: 'begin', label: 'begin (s)', type: 'number', min: 0, group: 'advanced' }, + { key: 'default_accel', label: 'default_accel (m/s²)', type: 'number', min: 0.1, max: 20, step: 0.1, group: 'advanced' }, + { key: 'default_decel', label: 'default_decel (m/s²)', type: 'number', min: 0.1, max: 20, step: 0.1, group: 'advanced' }, + { key: 'default_emergency_decel', label: 'default_emergency_decel (m/s²)', type: 'number', min: 0.1, max: 30, step: 0.1, group: 'advanced' }, + { key: 'default_sigma', label: 'default_sigma', type: 'number', min: 0, max: 1, step: 0.01, group: 'advanced' }, + { key: 'default_tau', label: 'default_tau (s)', type: 'number', min: 0.1, max: 10, step: 0.1, group: 'advanced' }, + { key: 'default_vehicle_length', label: 'default_vehicle_length (m)', type: 'number', min: 1, max: 30, step: 0.1, group: 'advanced' }, + { key: 'default_min_gap', label: 'default_min_gap (m)', type: 'number', min: 0, max: 20, step: 0.1, group: 'advanced' }, + { key: 'speed_mode', label: 'speed_mode', type: 'select', options: SMODE, group: 'advanced' }, + { key: 'lanechange_mode', label: 'lanechange_mode', type: 'select', options: LCMODE, group: 'advanced' }, + { key: 'no_internal_links', label: 'no_internal_links', type: 'boolean', group: 'advanced' }, + { key: 'ignore_route_errors', label: 'ignore_route_errors', type: 'boolean', group: 'advanced' }, + { key: 'collision_action', label: 'collision_action', type: 'select', options: COLL, group: 'advanced' }, + { key: 'lateral_resolution', label: 'lateral_resolution (m)', type: 'number', min: 0, max: 5, step: 0.1, group: 'advanced' }, + { key: 'fcd_output_geo', label: 'fcd_output_geo', type: 'boolean', group: 'advanced' }, + { key: 'fcd_filter_shapes', label: 'fcd_filter_shapes', type: 'boolean', group: 'advanced' }, + { key: 'tripinfo_output', label: 'tripinfo_output', type: 'boolean', group: 'advanced' }, + { key: 'summary_output', label: 'summary_output', type: 'boolean', group: 'advanced' }, + { key: 'seed', label: 'seed', type: 'number', min: 0, group: 'advanced' }, + ], + }, + { + key: 'trace_exporter', + title: 'Trace Exporter', + fields: [ + { key: 'output_format', label: 'output_format', type: 'select', options: OFMT, group: 'key' }, + { key: 'sampling_period', label: 'sampling_period (s)', type: 'number', min: 0.1, max: 60, step: 0.1, group: 'key' }, + { key: 'penetration_rate', label: 'penetration_rate', type: 'number', min: 0.01, max: 1, step: 0.01, group: 'key' }, + { key: 'begin', label: 'begin (s)', type: 'number', min: 0, group: 'advanced' }, + { key: 'end', label: 'end (s)', type: 'number', min: 0, group: 'advanced' }, + { key: 'x_offset', label: 'x_offset (m)', type: 'number', group: 'advanced' }, + { key: 'y_offset', label: 'y_offset (m)', type: 'number', group: 'advanced' }, + { key: 'boundary', label: 'boundary (xMin,yMin,xMax,yMax)', type: 'text', placeholder: '0,0,5000,5000', group: 'advanced' }, + { key: 'ns2_include_speed', label: 'ns2_include_speed', type: 'boolean', group: 'advanced' }, + { key: 'write_fcd_filtered', label: 'write_fcd_filtered', type: 'boolean', group: 'advanced' }, + { key: 'seed', label: 'seed', type: 'number', min: 0, group: 'advanced' }, + ], + }, +]; diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts new file mode 100644 index 0000000..5fe72cc --- /dev/null +++ b/frontend/src/api/types.ts @@ -0,0 +1,66 @@ +export type StageStatus = + | 'pending' + | 'running' + | 'done' + | 'failed' + | 'skipped'; + +export type StageKey = + | 'netconvert' + | 'random_trips' + | 'duarouter' + | 'sumo' + | 'trace_export'; + +/** Ordered pipeline stages (matches the API's stage order). */ +export const STAGES: StageKey[] = [ + 'netconvert', + 'random_trips', + 'duarouter', + 'sumo', + 'trace_export', +]; + +export interface JobStatus { + job_id: string; + status: StageStatus; + current_stage: StageKey | null; + stages: Partial>; + stage_logs: Record; + error: string | null; + outputs: string[]; + created_at: string; + completed_at: string | null; + warnings: string[]; +} + +export interface JobFile { + filename: string; + size_bytes: number; + download_url: string; +} + +export interface JobFilesResponse { + job_id: string; + files: JobFile[]; +} + +export interface JobLogsResponse { + job_id: string; + warnings: string[]; + stage_logs: Record; +} + +export interface HealthResponse { + status: 'ready' | 'degraded'; + tools: Record; + pipeline_ready: boolean; +} + +/** Nested pipeline configuration posted with the OSM file. */ +export interface ConversionRequest { + netconvert: Record; + random_trips: Record; + sumo: Record; + trace_exporter: Record; +} diff --git a/frontend/src/components/ConfigForm.tsx b/frontend/src/components/ConfigForm.tsx new file mode 100644 index 0000000..119d0e0 --- /dev/null +++ b/frontend/src/components/ConfigForm.tsx @@ -0,0 +1,73 @@ +import { useState } from 'react'; +import { SECTIONS } from '../api/configSchema'; +import type { ConversionRequest } from '../api/types'; +import { Field } from './ui/Field'; + +interface Props { + value: ConversionRequest; + onChange: (section: keyof ConversionRequest, key: string, val: unknown) => void; +} + +export function ConfigForm({ value, onChange }: Props) { + const [open, setOpen] = useState>({}); + + return ( +
+ {SECTIONS.map((section, idx) => { + const secVal = value[section.key as keyof ConversionRequest] ?? {}; + const keyFields = section.fields.filter((f) => f.group === 'key'); + const advFields = section.fields.filter((f) => f.group === 'advanced'); + const isOpen = open[section.key]; + + return ( +
+

+ {section.title} +

+
+ {keyFields.map((f) => ( + onChange(section.key as keyof ConversionRequest, f.key, v)} + /> + ))} +
+ + {advFields.length > 0 && ( +
+ + {isOpen && ( +
+ {advFields.map((f) => ( + + onChange(section.key as keyof ConversionRequest, f.key, v) + } + /> + ))} +
+ )} +
+ )} +
+ ); + })} +
+ ); +} diff --git a/frontend/src/components/FileRow.tsx b/frontend/src/components/FileRow.tsx new file mode 100644 index 0000000..656a629 --- /dev/null +++ b/frontend/src/components/FileRow.tsx @@ -0,0 +1,25 @@ +import type { JobFile } from '../api/types'; +import { api } from '../api/client'; +import { formatBytes } from '../lib/format'; + +export function FileRow({ file }: { file: JobFile }) { + return ( +
+
+ {file.filename} +
+
+ {formatBytes(file.size_bytes)} +
+ +
+ ); +} diff --git a/frontend/src/components/HealthStrip.tsx b/frontend/src/components/HealthStrip.tsx new file mode 100644 index 0000000..f8b5a29 --- /dev/null +++ b/frontend/src/components/HealthStrip.tsx @@ -0,0 +1,41 @@ +import type { HealthResponse } from '../api/types'; +import { StatusTag } from './ui/StatusTag'; + +const TOOL_ORDER = [ + 'netconvert', + 'duarouter', + 'sumo', + 'traceExporter.py', + 'randomTrips.py', +]; + +export function HealthStrip({ health }: { health: HealthResponse | null }) { + if (!health) return null; + return ( +
+
+ + PIPELINE HEALTH + + +
+
+ {TOOL_ORDER.map((tool) => ( +
+ {tool} + +
+ ))} +
+
+ ); +} diff --git a/frontend/src/components/JobTable.tsx b/frontend/src/components/JobTable.tsx new file mode 100644 index 0000000..7367dc5 --- /dev/null +++ b/frontend/src/components/JobTable.tsx @@ -0,0 +1,46 @@ +import { Link } from 'react-router-dom'; +import type { JobStatus } from '../api/types'; +import { StatusTag } from './ui/StatusTag'; +import { StageTicks } from './StageTicks'; +import { formatDate, truncateId } from '../lib/format'; + +export function JobTable({ jobs }: { jobs: JobStatus[] }) { + if (jobs.length === 0) { + return ( +
+ NO JOBS YET +
+ ); + } + return ( +
+ {jobs.map((job) => ( +
+
+ {truncateId(job.job_id, 12)} +
+
+ +
+
+ {formatDate(job.created_at)} +
+
+ +
+
+ + View + +
+
+ ))} +
+ ); +} diff --git a/frontend/src/components/LogBlock.tsx b/frontend/src/components/LogBlock.tsx new file mode 100644 index 0000000..bf1556a --- /dev/null +++ b/frontend/src/components/LogBlock.tsx @@ -0,0 +1,18 @@ +export function LogBlock({ + title, + content, +}: { + title: string; + content: string | null; +}) { + return ( +
+
+ {title} +
+
+        {content ?? '— no output —'}
+      
+
+ ); +} diff --git a/frontend/src/components/Nav.tsx b/frontend/src/components/Nav.tsx new file mode 100644 index 0000000..0037a8f --- /dev/null +++ b/frontend/src/components/Nav.tsx @@ -0,0 +1,37 @@ +import { NavLink } from 'react-router-dom'; + +const LINKS = [ + { to: '/', label: 'DASHBOARD', end: true }, + { to: '/new', label: 'NEW JOB', end: false }, + { to: '/system', label: 'SYSTEM', end: false }, +]; + +export function Nav() { + return ( +
+
+
+ + OSM→NS3 +
+
+ +
+
+ ); +} diff --git a/frontend/src/components/PipelineStepper.tsx b/frontend/src/components/PipelineStepper.tsx new file mode 100644 index 0000000..9f5323e --- /dev/null +++ b/frontend/src/components/PipelineStepper.tsx @@ -0,0 +1,58 @@ +import type { StageKey, StageStatus } from '../api/types'; +import { STAGES } from '../api/types'; + +function StageDot({ status }: { status: StageStatus }) { + const color = + status === 'done' + ? 'bg-status-done' + : status === 'running' + ? 'bg-status-running animate-pulse' + : status === 'failed' + ? 'bg-status-failed' + : status === 'skipped' + ? 'bg-status-skipped' + : 'bg-status-pending'; + return ; +} + +export function PipelineStepper({ + stages, + currentStage, +}: { + stages: Partial>; + currentStage?: StageKey | null; +}) { + return ( +
+ {STAGES.map((stage, i) => { + const st = stages[stage] ?? 'pending'; + const isCurrent = currentStage === stage; + return ( +
+
+
+ + {String(i + 1).padStart(2, '0')} + + +
+
+ {stage} +
+
+ {st} +
+
+ {i < STAGES.length - 1 && ( +
+ )} +
+ ); + })} +
+ ); +} diff --git a/frontend/src/components/StageTicks.tsx b/frontend/src/components/StageTicks.tsx new file mode 100644 index 0000000..ac884ac --- /dev/null +++ b/frontend/src/components/StageTicks.tsx @@ -0,0 +1,33 @@ +import type { StageKey, StageStatus } from '../api/types'; +import { STAGES } from '../api/types'; + +export function StageTicks({ + stages, +}: { + stages: Partial>; +}) { + return ( +
+ {STAGES.map((s) => { + const st = stages[s] ?? 'pending'; + const color = + st === 'done' + ? 'bg-status-done' + : st === 'running' + ? 'bg-status-running animate-pulse' + : st === 'failed' + ? 'bg-status-failed' + : st === 'skipped' + ? 'bg-status-skipped' + : 'bg-status-pending/40'; + return ( + + ); + })} +
+ ); +} diff --git a/frontend/src/components/ui/Button.tsx b/frontend/src/components/ui/Button.tsx new file mode 100644 index 0000000..3134824 --- /dev/null +++ b/frontend/src/components/ui/Button.tsx @@ -0,0 +1,26 @@ +import type { ButtonHTMLAttributes } from 'react'; + +type Variant = 'primary' | 'secondary' | 'ghost'; + +interface Props extends ButtonHTMLAttributes { + variant?: Variant; +} + +const VARIANTS: Record = { + primary: 'bg-accent text-accent-ink hover:bg-ink', + secondary: 'bg-ink text-bg hover:bg-accent', + ghost: 'bg-transparent text-ink border border-line hover:bg-surface', +}; + +export function Button({ + variant = 'primary', + className = '', + ...rest +}: Props) { + return ( + + ); + })} +
+ + ); + } + + if (field.type === 'number') { + return ( +
+ + + onChange(e.target.value === '' ? null : Number(e.target.value)) + } + className={inputCls} + /> +
+ ); + } + + // text + return ( +
+ + onChange(e.target.value === '' ? null : e.target.value)} + className={inputCls} + /> +
+ ); +} diff --git a/frontend/src/components/ui/StatusTag.tsx b/frontend/src/components/ui/StatusTag.tsx new file mode 100644 index 0000000..cf2b9ee --- /dev/null +++ b/frontend/src/components/ui/StatusTag.tsx @@ -0,0 +1,30 @@ +const STATUS_MAP: Record< + string, + { label: string; color: string; pulse?: boolean } +> = { + pending: { label: 'PENDING', color: 'text-status-pending' }, + running: { label: 'RUNNING', color: 'text-status-running', pulse: true }, + done: { label: 'DONE', color: 'text-status-done' }, + failed: { label: 'FAILED', color: 'text-status-failed' }, + skipped: { label: 'SKIPPED', color: 'text-status-skipped' }, +}; + +export function StatusTag({ + status, + label, +}: { + status: string; + label?: string; +}) { + const s = STATUS_MAP[status] ?? STATUS_MAP.pending; + return ( + + + {label ?? s.label} + + ); +} diff --git a/frontend/src/hooks/usePolling.ts b/frontend/src/hooks/usePolling.ts new file mode 100644 index 0000000..2297e32 --- /dev/null +++ b/frontend/src/hooks/usePolling.ts @@ -0,0 +1,54 @@ +import { useEffect, useRef, useState } from 'react'; + +interface PollingState { + data: T | null; + error: string | null; + loading: boolean; +} + +/** + * Fetches `fetcher` immediately and then every `intervalMs` while `enabled`. + * The fetcher is kept in a ref so the effect does not restart on every render. + */ +export function usePolling( + fetcher: () => Promise, + intervalMs: number, + enabled = true, +): PollingState { + const fetcherRef = useRef(fetcher); + fetcherRef.current = fetcher; + + const [state, setState] = useState>({ + data: null, + error: null, + loading: true, + }); + + useEffect(() => { + if (!enabled) return; + let active = true; + + const run = async () => { + try { + const d = await fetcherRef.current(); + if (active) setState({ data: d, error: null, loading: false }); + } catch (e) { + if (active) + setState((s) => ({ + ...s, + error: e instanceof Error ? e.message : 'Request failed', + loading: false, + })); + } + }; + + run(); + const id = setInterval(run, intervalMs); + return () => { + active = false; + clearInterval(id); + }; + }, [intervalMs, enabled]); + + return state; +} diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..7505d3b --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,20 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + html, + body, + #root { + height: 100%; + } + body { + @apply bg-bg text-ink font-sans antialiased; + } +} + +@layer utilities { + .label-mono { + @apply font-mono text-[11px] uppercase tracking-[0.2em] text-muted; + } +} diff --git a/frontend/src/lib/format.ts b/frontend/src/lib/format.ts new file mode 100644 index 0000000..3f4d98f --- /dev/null +++ b/frontend/src/lib/format.ts @@ -0,0 +1,22 @@ +export function truncateId(id: string, head = 8): string { + return id.length > head + 1 ? `${id.slice(0, head)}…` : id; +} + +export function formatBytes(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + const units = ['KB', 'MB', 'GB', 'TB']; + let v = bytes / 1024; + let i = 0; + while (v >= 1024 && i < units.length - 1) { + v /= 1024; + i++; + } + return `${v.toFixed(1)} ${units[i]}`; +} + +export function formatDate(iso: string | null): string { + if (!iso) return '—'; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return iso; + return d.toISOString().replace('T', ' ').slice(0, 16) + 'Z'; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..c68f490 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + , +); diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx new file mode 100644 index 0000000..c032af3 --- /dev/null +++ b/frontend/src/pages/Dashboard.tsx @@ -0,0 +1,54 @@ +import { useNavigate } from 'react-router-dom'; +import { api } from '../api/client'; +import { usePolling } from '../hooks/usePolling'; +import { JobTable } from '../components/JobTable'; +import { HealthStrip } from '../components/HealthStrip'; +import { Button } from '../components/ui/Button'; + +export default function Dashboard() { + const navigate = useNavigate(); + const jobs = usePolling(() => api.listJobs(), 4000); + const health = usePolling(() => api.getHealth(), 10000); + + return ( +
+
+
JOBS
+

+ Conversion Jobs +

+
+
+ POLLING /jobs + +
+ +
+ {jobs.loading && jobs.data === null ? ( +
+ LOADING /jobs… +
+ ) : jobs.error ? ( +
+ {jobs.error} +
+ ) : ( + <> +
+
Job ID
+
Status
+
Created
+
Stages
+
Actions
+
+ + + )} +
+ +
+ +
+
+ ); +} diff --git a/frontend/src/pages/JobDetail.tsx b/frontend/src/pages/JobDetail.tsx new file mode 100644 index 0000000..87302d5 --- /dev/null +++ b/frontend/src/pages/JobDetail.tsx @@ -0,0 +1,187 @@ +import { useState } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { api } from '../api/client'; +import { usePolling } from '../hooks/usePolling'; +import type { JobLogsResponse } from '../api/types'; +import { StatusTag } from '../components/ui/StatusTag'; +import { PipelineStepper } from '../components/PipelineStepper'; +import { FileRow } from '../components/FileRow'; +import { LogBlock } from '../components/LogBlock'; +import { formatDate, truncateId } from '../lib/format'; + +type Tab = 'progress' | 'outputs' | 'logs' | 'config'; + +const TABS: { key: Tab; label: string }[] = [ + { key: 'progress', label: 'Progress' }, + { key: 'outputs', label: 'Outputs' }, + { key: 'logs', label: 'Logs' }, + { key: 'config', label: 'Config' }, +]; + +export default function JobDetail() { + const { jobId = '' } = useParams(); + const navigate = useNavigate(); + const [tab, setTab] = useState('progress'); + const [deleting, setDeleting] = useState(false); + const [delError, setDelError] = useState(null); + + const jobQ = usePolling(() => api.getJob(jobId), 2000, !!jobId); + const filesQ = usePolling( + () => api.getJobFiles(jobId), + 5000, + !!jobId && tab === 'outputs', + ); + const logsQ = usePolling( + () => api.getJobLogs(jobId), + 5000, + !!jobId && tab === 'logs', + ); + const configQ = usePolling( + () => api.getJobConfig(jobId), + 60000, + !!jobId && tab === 'config', + ); + + const job = jobQ.data; + const running = job?.status === 'running'; + + const onDelete = async () => { + if (!window.confirm('Delete this job and all its files?')) return; + setDeleting(true); + setDelError(null); + try { + await api.deleteJob(jobId); + navigate('/'); + } catch (e) { + setDelError(e instanceof Error ? e.message : 'Delete failed'); + setDeleting(false); + } + }; + + if (jobQ.error && !job) { + return ( +
+ {jobQ.error} +
+ ); + } + if (!job) { + return ( +
+ LOADING /jobs/{truncateId(jobId, 8)}… +
+ ); + } + + return ( +
+
+
JOB
+
+ {job.job_id} +
+
+
+
+ + + {formatDate(job.created_at)} → {formatDate(job.completed_at)} + +
+ +
+ +
+ +
+ + {job.warnings?.length > 0 && ( +
+
WARNINGS
+ {job.warnings.map((w, i) => ( +
+ {w} +
+ ))} +
+ )} + {job.error && ( +
+ {job.error} +
+ )} + {delError && ( +
+ {delError} +
+ )} + +
+
+ {TABS.map((t) => ( + + ))} +
+ +
+ {tab === 'progress' && ( +
+ Current stage:{' '} + {job.current_stage ?? '—'} +
+ )} + + {tab === 'outputs' && ( +
+ {filesQ.loading && !filesQ.data ? ( +
+ LOADING… +
+ ) : (filesQ.data?.files.length ?? 0) === 0 ? ( +
+ NO OUTPUT FILES +
+ ) : ( + filesQ.data!.files.map((f) => ( + + )) + )} +
+ )} + + {tab === 'logs' && ( +
+ {Object.entries( + (logsQ.data as JobLogsResponse | null)?.stage_logs ?? {}, + ).map(([stage, content]) => ( + + ))} +
+ )} + + {tab === 'config' && ( +
+              {JSON.stringify(configQ.data, null, 2)}
+            
+ )} +
+
+
+ ); +} diff --git a/frontend/src/pages/NewJob.tsx b/frontend/src/pages/NewJob.tsx new file mode 100644 index 0000000..eee902b --- /dev/null +++ b/frontend/src/pages/NewJob.tsx @@ -0,0 +1,140 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { api } from '../api/client'; +import type { ConversionRequest } from '../api/types'; +import { ConfigForm } from '../components/ConfigForm'; +import { Button } from '../components/ui/Button'; + +const emptyConfig = (): ConversionRequest => ({ + netconvert: {}, + random_trips: {}, + sumo: {}, + trace_exporter: {}, +}); + +export default function NewJob() { + const navigate = useNavigate(); + const [file, setFile] = useState(null); + const [config, setConfig] = useState(emptyConfig()); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + api + .getDefaults() + .then(setConfig) + .catch(() => setConfig(emptyConfig())); + }, []); + + const onField = ( + section: keyof ConversionRequest, + key: string, + val: unknown, + ) => { + setConfig((c) => ({ + ...c, + [section]: { ...(c[section] as Record), [key]: val }, + })); + }; + + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!file) { + setError('Select an OSM file first.'); + return; + } + setSubmitting(true); + setError(null); + try { + const res = await api.createJob(file, config); + navigate(`/jobs/${res.job_id}`); + } catch (err) { + setError(err instanceof Error ? err.message : 'Upload failed'); + setSubmitting(false); + } + }; + + return ( +
+
+
NEW JOB
+

+ Start Conversion +

+

+ POST /jobs · multipart +

+
+ +
+ {/* Upload */} +
+

+ Upload OSM File +

+ + {file && ( +
+ {file.name} + +
+ )} +
+ + {/* Config */} +
+
+

+ Pipeline Configuration +

+ + PRE-FILLED FROM GET /defaults + +
+ +
+ + {error && ( +
+ {error} +
+ )} + +
+ + +
+
+
+ ); +} diff --git a/frontend/src/pages/System.tsx b/frontend/src/pages/System.tsx new file mode 100644 index 0000000..5f4bdb1 --- /dev/null +++ b/frontend/src/pages/System.tsx @@ -0,0 +1,113 @@ +import { api } from '../api/client'; +import { usePolling } from '../hooks/usePolling'; +import { StatusTag } from '../components/ui/StatusTag'; + +const TOOLS = [ + 'netconvert', + 'duarouter', + 'sumo', + 'traceExporter.py', + 'randomTrips.py', +]; + +const ENDPOINTS: { method: string; path: string }[] = [ + { method: 'GET', path: '/health' }, + { method: 'GET', path: '/schema' }, + { method: 'GET', path: '/defaults' }, + { method: 'POST', path: '/jobs' }, + { method: 'GET', path: '/jobs' }, + { method: 'GET', path: '/jobs/{id}' }, + { method: 'GET', path: '/jobs/{id}/files' }, + { method: 'GET', path: '/jobs/{id}/logs' }, + { method: 'GET', path: '/jobs/{id}/config' }, + { method: 'DELETE', path: '/jobs/{id}' }, +]; + +export default function System() { + const health = usePolling(() => api.getHealth(), 10000); + const h = health.data; + + return ( +
+
+
SYSTEM
+

+ Pipeline Health +

+

+ GET /health +

+
+ +
+ {!h ? ( +
+ LOADING /health… +
+ ) : ( +
+
+
OVERALL STATUS
+
+ + {h.status.toUpperCase()} + + +
+
+ pipeline_ready: {String(h.pipeline_ready)} +
+
+ +
+
+ SUMO TOOL AVAILABILITY +
+
+ {TOOLS.map((tool) => ( +
+ {tool} + +
+ ))} +
+
+ +
+
+ API ENDPOINTS +
+
+ {ENDPOINTS.map((e) => ( +
+ + {e.method} + + {e.path} +
+ ))} +
+
+
+ )} +
+
+ ); +} diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..4af48fb --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1,8 @@ +/// + +interface ImportMetaEnv { + readonly VITE_API_BASE?: string; +} +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..8dcd4be --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,39 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{ts,tsx}'], + theme: { + extend: { + colors: { + bg: '#E3E2DE', + surface: '#EDECEA', + ink: '#141414', + 'ink-2': '#444343', + muted: '#7A7A7A', + line: '#C7C7C7', + accent: '#1351AA', + 'accent-ink': '#E3E2DE', + 'status-pending': '#7A7A7A', + 'status-running': '#1351AA', + 'status-done': '#1E7A46', + 'status-failed': '#B4231F', + 'status-skipped': '#9A9A9A', + }, + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'], + mono: ['"JetBrains Mono"', 'ui-monospace', 'monospace'], + }, + borderRadius: { + none: '0px', + DEFAULT: '0px', + sm: '0px', + md: '0px', + lg: '0px', + xl: '0px', + '2xl': '0px', + '3xl': '0px', + full: '9999px', + }, + }, + }, + plugins: [], +}; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..17f43b1 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/tsconfig.node.tsbuildinfo b/frontend/tsconfig.node.tsbuildinfo new file mode 100644 index 0000000..c5249fc --- /dev/null +++ b/frontend/tsconfig.node.tsbuildinfo @@ -0,0 +1 @@ +{"fileNames":["./node_modules/typescript/lib/lib.d.ts","./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.webworker.importscripts.d.ts","./node_modules/typescript/lib/lib.scripthost.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/@types/estree/index.d.ts","./node_modules/rollup/dist/rollup.d.ts","./node_modules/rollup/dist/parseast.d.ts","./node_modules/vite/types/hmrpayload.d.ts","./node_modules/vite/types/customevent.d.ts","./node_modules/vite/types/hot.d.ts","./node_modules/vite/dist/node/types.d-agj9qkwt.d.ts","./node_modules/esbuild/lib/main.d.ts","./node_modules/source-map-js/source-map.d.ts","./node_modules/postcss/lib/previous-map.d.ts","./node_modules/postcss/lib/input.d.ts","./node_modules/postcss/lib/css-syntax-error.d.ts","./node_modules/postcss/lib/declaration.d.ts","./node_modules/postcss/lib/root.d.ts","./node_modules/postcss/lib/warning.d.ts","./node_modules/postcss/lib/lazy-result.d.ts","./node_modules/postcss/lib/no-work-result.d.ts","./node_modules/postcss/lib/processor.d.ts","./node_modules/postcss/lib/result.d.ts","./node_modules/postcss/lib/document.d.ts","./node_modules/postcss/lib/rule.d.ts","./node_modules/postcss/lib/node.d.ts","./node_modules/postcss/lib/comment.d.ts","./node_modules/postcss/lib/container.d.ts","./node_modules/postcss/lib/at-rule.d.ts","./node_modules/postcss/lib/list.d.ts","./node_modules/postcss/lib/postcss.d.ts","./node_modules/postcss/lib/postcss.d.mts","./node_modules/vite/dist/node/runtime.d.ts","./node_modules/vite/types/importglob.d.ts","./node_modules/vite/types/metadata.d.ts","./node_modules/vite/dist/node/index.d.ts","./node_modules/@babel/types/lib/index.d.ts","./node_modules/@types/babel__generator/index.d.ts","./node_modules/@babel/parser/typings/babel-parser.d.ts","./node_modules/@types/babel__template/index.d.ts","./node_modules/@types/babel__traverse/index.d.ts","./node_modules/@types/babel__core/index.d.ts","./node_modules/@vitejs/plugin-react/dist/index.d.ts","./vite.config.ts","./node_modules/@types/prop-types/index.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/@types/react-dom/index.d.ts"],"fileIdsList":[[40],[40,41,42,43,44],[40,42],[51],[48,49,50],[39,45],[31],[29,31],[20,28,29,30,32,34],[18],[21,26,31,34],[17,34],[21,22,25,26,27,34],[21,22,23,25,26,34],[18,19,20,21,22,26,27,28,30,31,32,34],[34],[16,18,19,20,21,22,23,25,26,27,28,29,30,31,32,33],[16,34],[21,23,24,26,27,34],[25,34],[26,27,31,34],[19,29],[9,38],[8,9],[9,10,11,12,13,14,15,35,36,37,38],[11,12,13,14],[11,12,13],[11],[12],[9],[39,46]],"fileInfos":[{"version":"a7297ff837fcdf174a9524925966429eb8e5feecc2cc55cc06574e6b092c1eaa","impliedFormat":1},{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"751764bb94219b4ce8f5475dc35d3de2e432fea01a0c9610cd7f69ad05e398c6","impliedFormat":1},{"version":"ee70b8037ecdf0de6c04f35277f253663a536d7e38f1539d270e4e916d225a3f","affectsGlobalScope":true,"impliedFormat":1},{"version":"a660aa95476042d3fdcc1343cf6bb8fdf24772d31712b1db321c5a4dcc325434","impliedFormat":1},{"version":"282f98006ed7fa9bb2cd9bdbe2524595cfc4bcd58a0bb3232e4519f2138df811","impliedFormat":1},{"version":"6222e987b58abfe92597e1273ad7233626285bc2d78409d4a7b113d81a83496b","impliedFormat":1},{"version":"cbe726263ae9a7bf32352380f7e8ab66ee25b3457137e316929269c19e18a2be","impliedFormat":1},{"version":"8b96046bf5fb0a815cba6b0880d9f97b7f3a93cf187e8dcfe8e2792e97f38f87","impliedFormat":99},{"version":"bacf2c84cf448b2cd02c717ad46c3d7fd530e0c91282888c923ad64810a4d511","affectsGlobalScope":true,"impliedFormat":1},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","impliedFormat":1},{"version":"52dcc257df5119fb66d864625112ce5033ac51a4c2afe376a0b299d2f7f76e4a","impliedFormat":1},{"version":"e5bab5f871ef708d52d47b3e5d0aa72a08ee7a152f33931d9a60809711a2a9a3","impliedFormat":1},{"version":"e16dc2a81595736024a206c7d5c8a39bfe2e6039208ef29981d0d95434ba8fcf","impliedFormat":1},{"version":"cc4a4903fb698ca1d961d4c10dce658aa3a479faf40509d526f122b044eaf6a4","impliedFormat":1},{"version":"19ee8416e6473ed6c7adb868fa796b5653cf0fa2a337658e677eaa0d134388c3","impliedFormat":1},{"version":"1328ab4e442614b28cdb3d4b414cf68325c0da0dca07287a338d0654b7a00261","impliedFormat":1},{"version":"a039dc21f045919f3cbee2ec13812cc6cc3eebc99dae4be00973230f468d19a6","impliedFormat":1},{"version":"3fbe57af01460e49dcd29df55d6931e1672bc6f1be0fb073d11410bc16f9037d","impliedFormat":1},{"version":"f760be449e8562ec5c09bb5187e8e1eabf3c113c0c58cddda53ef8c69f3e2131","impliedFormat":1},{"version":"44325ed13294fce6ab825b82947bbeed2611db7dad9d9135260192f375e5a189","impliedFormat":1},{"version":"e392e8fb5b514eafc585601c1d781485aa6dd6a320e75daf1064a4c6918a1b45","impliedFormat":1},{"version":"46e4a36e8ddbdfb4e7330e11c81c970dc8b218611df9183d39c41c5f8c653b55","impliedFormat":1},{"version":"370bde134aa8c2abc926d0e99d3a4d5d5dba65c6ee65459137e4f02670cbf841","impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","impliedFormat":1},{"version":"354a7f8e1287d9d6b7561bc97fdd8cbc2f7c1dd79e4cb37b942e8a5cfaff1085","impliedFormat":1},{"version":"8d369483f0c2b9ee388129cfdb6a43bc8112b377e86a41884bd06e19ce04f4c1","impliedFormat":99},{"version":"82e687ebd99518bc63ea04b0c3810fb6e50aa6942decd0ca6f7a56d9b9a212a6","impliedFormat":99},{"version":"7f698624bbbb060ece7c0e51b7236520ebada74b747d7523c7df376453ed6fea","impliedFormat":1},{"version":"8f07f2b6514744ac96e51d7cb8518c0f4de319471237ea10cf688b8d0e9d0225","impliedFormat":1},{"version":"257b83faa134d971c738a6b9e4c47e59bb7b23274719d92197580dd662bfafc3","impliedFormat":99},{"version":"556ccd493ec36c7d7cb130d51be66e147b91cc1415be383d71da0f1e49f742a9","impliedFormat":1},{"version":"b6d03c9cfe2cf0ba4c673c209fcd7c46c815b2619fd2aad59fc4229aaef2ed43","impliedFormat":1},{"version":"95aba78013d782537cc5e23868e736bec5d377b918990e28ed56110e3ae8b958","impliedFormat":1},{"version":"670a76db379b27c8ff42f1ba927828a22862e2ab0b0908e38b671f0e912cc5ed","impliedFormat":1},{"version":"13b77ab19ef7aadd86a1e54f2f08ea23a6d74e102909e3c00d31f231ed040f62","impliedFormat":1},{"version":"069bebfee29864e3955378107e243508b163e77ab10de6a5ee03ae06939f0bb9","impliedFormat":1},{"version":"26e0ffceb2198feb1ef460d5d14111c69ad07d44c5a67fd4bfeb74c969aa9afb","impliedFormat":99},{"version":"14501439875c563028a6d28102ab04a36105e6f013b0803a9b5f62690bf45098","signature":"4b96dd19fd2949d28ce80e913412b0026dc421e5bf6c31d87c7b5eb11b5753b4"},{"version":"87d9d29dbc745f182683f63187bf3d53fd8673e5fca38ad5eaab69798ed29fbc","impliedFormat":1},{"version":"eb5b19b86227ace1d29ea4cf81387279d04bb34051e944bc53df69f58914b788","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"09ddcfcfbe77a8232d155ca1030005106b1328f6210df43629d0be750da07c16","affectsGlobalScope":true,"impliedFormat":1},{"version":"17ed71200119e86ccef2d96b73b02ce8854b76ad6bd21b5021d4269bec527b5f","impliedFormat":1}],"root":[47],"options":{"allowSyntheticDefaultImports":true,"composite":true,"module":99,"skipLibCheck":true,"strict":true},"referencedMap":[[42,1],[45,2],[41,1],[43,3],[44,1],[52,4],[51,5],[46,6],[32,7],[30,8],[31,9],[19,10],[20,8],[27,11],[18,12],[23,13],[24,14],[29,15],[35,16],[34,17],[17,18],[25,19],[26,20],[21,21],[28,7],[22,22],[10,23],[9,24],[39,25],[36,26],[14,27],[12,28],[13,29],[38,30],[47,31]],"latestChangedDtsFile":"./vite.config.d.ts","version":"5.9.3"} \ No newline at end of file diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo new file mode 100644 index 0000000..4f59de7 --- /dev/null +++ b/frontend/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/api/configschema.ts","./src/api/types.ts","./src/components/configform.tsx","./src/components/filerow.tsx","./src/components/healthstrip.tsx","./src/components/jobtable.tsx","./src/components/logblock.tsx","./src/components/nav.tsx","./src/components/pipelinestepper.tsx","./src/components/stageticks.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/field.tsx","./src/components/ui/statustag.tsx","./src/hooks/usepolling.ts","./src/lib/format.ts","./src/pages/dashboard.tsx","./src/pages/jobdetail.tsx","./src/pages/newjob.tsx","./src/pages/system.tsx"],"version":"5.9.3"} \ No newline at end of file diff --git a/frontend/vite.config.d.ts b/frontend/vite.config.d.ts new file mode 100644 index 0000000..340562a --- /dev/null +++ b/frontend/vite.config.d.ts @@ -0,0 +1,2 @@ +declare const _default: import("vite").UserConfig; +export default _default; diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..08ceb93 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +export default defineConfig({ + plugins: [react()], + server: { port: 5173 }, +}); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..93ea31e --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { port: 5173 }, +});