From cd0815b4ce17f44201cb9332ceb275640326ab5f Mon Sep 17 00:00:00 2001 From: Annie Chen Date: Fri, 13 Mar 2026 14:15:53 -0400 Subject: [PATCH 01/25] refine ai summary llm prompt --- ai/data/dummy_data.json | 92 ++++++++++++++++++++++++ ai/prompts/clubSummary.ts | 26 +++++++ ai/prompts/weeklySummary.ts | 48 +++++++++++++ ai/summarize.ts | 14 ++++ ai/test.ts | 27 +++++++ bun.lock | 139 ++++++++++++++++++++++++++++++++++++ package.json | 1 + 7 files changed, 347 insertions(+) create mode 100644 ai/data/dummy_data.json create mode 100644 ai/prompts/clubSummary.ts create mode 100644 ai/prompts/weeklySummary.ts create mode 100644 ai/summarize.ts create mode 100644 ai/test.ts diff --git a/ai/data/dummy_data.json b/ai/data/dummy_data.json new file mode 100644 index 0000000..cf25c77 --- /dev/null +++ b/ai/data/dummy_data.json @@ -0,0 +1,92 @@ +[ + { + "listserv": "ACSU", + "host": "ACSU", + "club": "ACSU", + "title": "ACSU Student Faculty Luncheon", + "description": "Connect with CS professors and students over lunch with other CS students.", + "date": "2026-03-13", + "location": "CIS 250 (Young Conference Room)", + "tags": ["networking", "cs", "faculty"] + }, + { + "listserv": "ACSU", + "host": "ACSU", + "club": "ACSU", + "title": "1:1 Faculty Coffee Chat Week", + "description": "Sign up for informal 1:1 chats with professors about research opportunities, careers, and advice.", + "date": "2026-03-09", + "location": "Various campus locations", + "tags": ["research", "mentorship", "coffee"] + }, + { + "listserv": "ACSU", + "host": "Cornell AppDev & Ramp", + "club": "Cornell AppDev", + "title": "AppDev x Ramp - HACK→HIRED", + "description": "One-day AI-focused hackathon with workshops, prizes, and a chance to interview for a Fall SWE internship at Ramp.", + "date": "2026-03-14", + "location": "eHub", + "tags": ["hackathon", "career", "recruiting", "ai"] + }, + { + "listserv": "ACSU", + "host": "Citadel GQS", + "club": "", + "title": "Citadel Global Quantitative Strategies Tech Talk", + "description": "Tech talk on quantitative trading careers and how your research skills translate to GQS at Citadel.", + "date": "2026-03-17", + "location": "Upson 142", + "tags": ["finance", "tech-talk", "recruiting"] + }, + { + "listserv": "WICC", + "host": "WICC", + "club": "WICC", + "title": "Cracking Your Career (CYC)", + "description": "Four-part series on pathways in computing, interviewing, resumes, and standing out in tech roles.", + "date": "2026-03-03", + "location": "Gates 114", + "tags": ["career", "workshop"] + }, + { + "listserv": "WICC", + "host": "WICC & MathWorks", + "club": "WICC", + "title": "WICC x MathWorks MATLAB Challenge", + "description": "Hands-on MATLAB challenge using MATLAB Copilot with prizes, swag, and info on careers at MathWorks.", + "date": "2026-03-16", + "location": "Gates G01", + "tags": ["workshop", "mathworks", "ai", "swag"] + }, + { + "listserv": "WICC", + "host": "WICC & Jane Street", + "club": "WICC", + "title": "WICC x Jane Street Tech Talks", + "description": "Talk on predictable communication at scale and Aria, Jane Street's low-latency messaging system, with food and swag.", + "date": "2026-03-18", + "location": "Phillips 101", + "tags": ["talk", "jane-street", "systems"] + }, + { + "listserv": "WICC", + "host": "External", + "club": "", + "title": "HackDartmouth XI", + "description": "24-hour hackathon at Dartmouth with workshops, prizes, and a Lost In Space theme.", + "date": "2026-04-11", + "location": "Dartmouth", + "tags": ["hackathon", "travel"] + }, + { + "listserv": "WICC", + "host": "Jane Street", + "club": "", + "title": "Jane Street FOCUS Program", + "description": "Multi-day NYC program for first-year students to explore trading, software development, and strategy at Jane Street.", + "date": "2026-05-19", + "location": "New York City", + "tags": ["program", "first-year", "professional-track"] + } +] \ No newline at end of file diff --git a/ai/prompts/clubSummary.ts b/ai/prompts/clubSummary.ts new file mode 100644 index 0000000..81ce705 --- /dev/null +++ b/ai/prompts/clubSummary.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +export function clubSummaryPrompt(events: any[]) { + return ` +You are writing a very short description of what a student club does, based only on their recent events. + +You are given a JSON array called "events". All events belong to the **same club**, with a "club" field that contains its name. + +### Your task +- Write **2–3 concise sentences** (max **70 words total**) describing: + - What the club focuses on (e.g. hackathons, career prep, mentorship, theory, community building). + - The typical kinds of events they run (talks, workshops, study groups, socials, conferences, etc.). + - The community/audience they serve (e.g. CS undergrads, women in computing, international students, etc.). +- **Start the first sentence with the club name**, taken from the "club" field (e.g. "ACSU is...", "WICC is..."). +- Do **not** mention specific dates, rooms, or one‑off logistics; speak in general patterns. +- Keep the tone clear and informative; no emojis. + +### Style constraints +- Output **plain text only** (no markdown headings or bullets). +- Use 2–3 sentences in a single short paragraph. + +Here are the events for this club (JSON): +\`\`\`json +${JSON.stringify(events, null, 2)} +\`\`\` +` +} \ No newline at end of file diff --git a/ai/prompts/weeklySummary.ts b/ai/prompts/weeklySummary.ts new file mode 100644 index 0000000..f5cebe0 --- /dev/null +++ b/ai/prompts/weeklySummary.ts @@ -0,0 +1,48 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +export function weeklySummaryPrompt(events: any[]) { + return ` +You are generating a concise weekly opportunities digest for a single student. + +You are given a JSON array called "events". Each event may look like: +{ + "listserv": "ACSU", // listserv or source that advertised the event + "host": "Cornell AppDev", // primary hosting org or company (may differ from listserv) + "title": "AppDev x Ramp - HACK→HIRED", + "description": "One‑day hackathon with prizes and recruiting", + "date": "2026-03-14", + "location": "eHub", + "tags": ["hackathon", "career", "recruiting"] +} + +Important: +- \`listserv\` = who sent/advertised the opportunity (ACSU, WICC, etc.). +- \`host\` = who is actually running the event (AppDev, Ramp, a company, a conference, etc.). +Many events are cross‑posted; do not assume the listserv owns the event. + +### Your task +- Write a **compact markdown digest** for this week, **no intro or outro sentences**. +- **Group events by listserv** using markdown headings based on the \`listserv\` field: + - Use a level-3 heading for each listserv that appears, like: + - \`### ACSU\` + - \`### WICC\` + - \`### Cornell\` + - \`### Opportunities\` +- Under each heading, list at most **3 high‑value events** that were advertised on that listserv, chosen for impact (recruiting, hackathons, major talks, strong mentorship/community events). +- For each event, use **one clean bullet point** in this exact format: + - \`- **Title (Mar 14)** – 1‑sentence description that clearly names the **host** or event type, the key value prop, and location if available.\` + - Keep each description **under 25 words**. +- Prefer **hackathons, recruiting, mentorship, and flagship events** over minor or redundant ones. +- If a listserv has more than 3 events, summarize only the top 3 and ignore the rest. + +### Style constraints +- Output **markdown only** (no code fences). +- Use only \`###\` headings and \`- \` bullets, nothing else. +- **No emojis, no unsubscribe text, no calendar links, no greetings or sign‑offs.** +- **Total digest <= 220 words** across all sections. + +Now write the digest based on this JSON: +\`\`\`json +${JSON.stringify(events, null, 2)} +\`\`\` +` +} \ No newline at end of file diff --git a/ai/summarize.ts b/ai/summarize.ts new file mode 100644 index 0000000..e4fcc39 --- /dev/null +++ b/ai/summarize.ts @@ -0,0 +1,14 @@ +import { GoogleGenAI } from "@google/genai"; + +const ai = new GoogleGenAI({ + apiKey: process.env.GEMINI_API_KEY, +}) + +export async function generateSummary(prompt: string) { + const res = await ai.models.generateContent({ + model: "gemini-2.5-flash", + contents: prompt + }) + + return res.text +} \ No newline at end of file diff --git a/ai/test.ts b/ai/test.ts new file mode 100644 index 0000000..0058558 --- /dev/null +++ b/ai/test.ts @@ -0,0 +1,27 @@ +import data from "./data/dummy_data.json" +import { generateSummary } from "./summarize" +import { weeklySummaryPrompt } from "./prompts/weeklySummary" +import { clubSummaryPrompt } from "./prompts/clubSummary" + +async function run() { + // Example: a user subscribed to ACSU and WICC listservs + const subscribedListservs = ["ACSU", "WICC"] + const weeklyEvents = data.filter(e => subscribedListservs.includes(e.listserv)) + + const weeklyPrompt = weeklySummaryPrompt(weeklyEvents) + const weeklySummary = await generateSummary(weeklyPrompt) + + console.log("\nWEEKLY SUMMARY\n") + console.log(weeklySummary) + + // Example: summarize what ACSU does based on its hosted events + const acsuEvents = data.filter(e => e.club === "ACSU") + + const clubPrompt = clubSummaryPrompt(acsuEvents) + const clubSummary = await generateSummary(clubPrompt) + + console.log("\nACSU CLUB SUMMARY\n") + console.log(clubSummary) +} + +run() \ No newline at end of file diff --git a/bun.lock b/bun.lock index 8dba42b..5ad5520 100644 --- a/bun.lock +++ b/bun.lock @@ -7,6 +7,7 @@ "dependencies": { "@auth/core": "0.37.0", "@convex-dev/auth": "^0.0.91", + "@google/genai": "^1.44.0", "convex": "^1.32.0", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -140,6 +141,8 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@google/genai": ["@google/genai@1.44.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-kRt9ZtuXmz+tLlcNntN/VV4LRdpl6ZOu5B1KbfNgfR65db15O6sUQcwnwLka8sT/V6qysD93fWrgJHF2L7dA9A=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], @@ -148,6 +151,8 @@ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], @@ -168,6 +173,28 @@ "@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="], @@ -240,6 +267,8 @@ "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/type-utils": "8.56.1", "@typescript-eslint/utils": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg=="], @@ -266,20 +295,30 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="], + "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "caniuse-lite": ["caniuse-lite@1.0.30001774", "", {}, "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA=="], @@ -302,12 +341,20 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + "electron-to-chromium": ["electron-to-chromium@1.5.302", "", {}, "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg=="], + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -334,6 +381,8 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], @@ -342,6 +391,8 @@ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], @@ -350,20 +401,36 @@ "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], + + "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], + "google-auth-library": ["google-auth-library@10.6.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "7.1.3", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-5awwuLrzNol+pFDmKJd0dKtZ0fPLAtoA5p7YO4ODsDu6ONJUVqbYwvv8y2ZBO5MBNp9TJXigB19710kYpBPdtA=="], + + "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -372,12 +439,16 @@ "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-network-error": ["is-network-error@1.3.1", "", {}, "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + "jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], @@ -386,6 +457,8 @@ "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], @@ -394,6 +467,10 @@ "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + "jwt-decode": ["jwt-decode@4.0.0", "", {}, "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="], "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], @@ -404,18 +481,26 @@ "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "lucia": ["lucia@3.2.2", "", { "dependencies": { "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0" } }, "sha512-P1FlFBGCMPMXu+EGdVD9W4Mjm0DqsusmKgO7Xc33mI5X1bklmsQb0hfzPhXomQr9waWIBDsiOjvr1e6BTaUqpA=="], "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], "oauth4webapi": ["oauth4webapi@3.8.5", "", {}, "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg=="], @@ -426,12 +511,18 @@ "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -450,6 +541,8 @@ "pretty-format": ["pretty-format@3.8.0", "", {}, "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="], + "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], @@ -460,8 +553,14 @@ "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + + "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -472,8 +571,18 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -496,10 +605,16 @@ "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], @@ -526,6 +641,22 @@ "convex/esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="], + "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.3", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA=="], "convex/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="], @@ -580,6 +711,14 @@ "convex/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg=="], + "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], } } diff --git a/package.json b/package.json index 15a3d81..78c3cd0 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@auth/core": "0.37.0", "@convex-dev/auth": "^0.0.91", + "@google/genai": "^1.44.0", "convex": "^1.32.0", "react": "^19.2.0", "react-dom": "^19.2.0" From afcae29ab5664161781f4346d9205049bef53f13 Mon Sep 17 00:00:00 2001 From: Annie Chen Date: Fri, 20 Mar 2026 14:19:05 -0400 Subject: [PATCH 02/25] first draft schema --- convex/schema.ts | 116 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 6 deletions(-) diff --git a/convex/schema.ts b/convex/schema.ts index 96b6449..41abec4 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -1,9 +1,113 @@ -import { defineSchema } from "convex/server"; +// convex/schema.ts +import { defineSchema, defineTable } from "convex/server"; import { authTables } from "@convex-dev/auth/server"; - -const schema = defineSchema({ +import { v } from "convex/values"; + +export default defineSchema({ ...authTables, - // Your other tables... + + // raw email data + listservEmails: defineTable({ + listserv: v.string(), // listserv name + sentAt: v.number(), // timestamp + fromAddress: v.optional(v.string()), // listserv email address + rawHtml: v.optional(v.string()), + rawText: v.optional(v.string()), + section: v.optional(v.string()), // "On-Campus", "Off-Campus", "Opportunities" + }).index("by_listserv", ["listserv"]), + + // One row per listserv item + events: defineTable({ + listservEmailId: v.id("listservEmails"), // link to the listserv email data + listserv: v.string(), + listservSection: v.string(), + + title: v.string(), + description: v.string(), + eventType: v.union( + v.literal("event"), // one-time event + v.literal("opportunity"), // internship, job, program, application + v.literal("hackathon"), + v.literal("courses"), // workshop series / class series + v.literal("fundraiser"), + v.literal("info"), // announcements with no clear CTA, catch-all + ), + + // ex. WICC x Jane Street, AppDev x Ramp, etc. + hostClub: v.string(), // primary org (maps to a clubs table eventually) + coHosts: v.array(v.string()), // ["Ramp"], [] if none + + dates: v.array( + v.object({ + timestamp: v.number(), + type: v.union( + v.literal("start"), // start & end for recurring events + v.literal("end"), + v.literal("deadline"), + v.literal("single"), // one-off event with no end + ), + }), + ), + isRecurring: v.boolean(), + recurrenceNote: v.optional(v.string()), // "Every Wednesday 5-6:30pm" + + location: v.optional( + v.object({ + displayText: v.string(), + address: v.optional(v.string()), + isVirtual: v.boolean(), + buildingCode: v.optional(v.string()), // "Gates 114", "CIS 250" + }), + ), + + links: v.array( + v.object({ + url: v.string(), + type: v.union( + v.literal("registration"), // more for courses, hackathons, etc. + v.literal("application"), // internships, jobs, programs + v.literal("rsvp"), // events with physical link (decide if combines with registration) + v.literal("info"), // general info, websites, etc. + v.literal("social"), // instagram, etc. + ), + label: v.optional(v.string()), // "Apply here", "RSVP Link" + }), + ), + + contacts: v.array( + v.object({ + type: v.union( + v.literal("email"), + v.literal("instagram"), + v.literal("website"), + ), + value: v.string(), + }), + ), + + tags: v.array(v.string()), + targetAudience: v.optional( + v.union( // decide if we need all or define more + v.literal("all"), + v.literal("first_year"), + v.literal("women_nonbinary"), + v.literal("international"), + v.literal("graduate"), + ), + ), + perks: v.array( + v.union( + v.literal("food"), + v.literal("swag"), + v.literal("prizes"), + v.literal("travel_covered"), + v.literal("paid"), + ), + ), + + }) + .index("by_listserv", ["listserv"]) + .index("by_section", ["listservSection"]) + .index("by_event_type", ["eventType"]) + .index("by_host", ["hostClub"]), }); - -export default schema; \ No newline at end of file From bea066d3be2aed827924e20a73c2fd829af48c07 Mon Sep 17 00:00:00 2001 From: Annie Chen Date: Sun, 22 Mar 2026 16:13:11 -0400 Subject: [PATCH 03/25] add aidescription field --- convex/schema.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/convex/schema.ts b/convex/schema.ts index 41abec4..d1f1596 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -24,6 +24,7 @@ export default defineSchema({ title: v.string(), description: v.string(), + aiDescription: v.string(), // condensed 1 sentence ai-generated description eventType: v.union( v.literal("event"), // one-time event v.literal("opportunity"), // internship, job, program, application From 86b62036fe27f32975a0cbd167a4eef5fab704d5 Mon Sep 17 00:00:00 2001 From: Annie Chen Date: Fri, 10 Apr 2026 14:02:59 -0400 Subject: [PATCH 04/25] tweak host club portion --- convex/schema.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/convex/schema.ts b/convex/schema.ts index d1f1596..02fdc7a 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -34,9 +34,22 @@ export default defineSchema({ v.literal("info"), // announcements with no clear CTA, catch-all ), - // ex. WICC x Jane Street, AppDev x Ramp, etc. - hostClub: v.string(), // primary org (maps to a clubs table eventually) - coHosts: v.array(v.string()), // ["Ramp"], [] if none + // ex. WICC x Jane Street, AppDev x Ramp, WICC x AppDev + hosts: v.array( + v.object({ + name: v.string(), + kind: v.union( + v.literal("club"), + v.literal("company"), + v.literal("external_org"), + ), + role: v.union( + v.literal("primary"), + v.literal("cohost"), + v.literal("sponsor"), + ), + }), + ), dates: v.array( v.object({ @@ -109,6 +122,5 @@ export default defineSchema({ }) .index("by_listserv", ["listserv"]) .index("by_section", ["listservSection"]) - .index("by_event_type", ["eventType"]) - .index("by_host", ["hostClub"]), + .index("by_event_type", ["eventType"]), }); From 3a5563e5e1f2695f9e984a350d1ad43a8deb9580 Mon Sep 17 00:00:00 2001 From: Annie Chen Date: Mon, 13 Apr 2026 19:57:52 -0400 Subject: [PATCH 05/25] initial commit for design system --- .cursor/rules/frontend/figma.md | 25 + bun.lock | 143 ++++ index.html | 4 + package.json | 5 +- src/App.tsx | 12 +- src/assets/bookmark-filled.svg | 3 + src/assets/bookmark.svg | 3 + src/assets/calendar.svg | 6 + src/assets/close_search.svg | 3 + src/assets/close_tags.svg | 3 + src/assets/external-link.svg | 5 + src/assets/home-icon.svg | 4 + src/assets/location-pin.svg | 4 + src/assets/loop-logo-mixed.svg | 14 + src/assets/loop_logo.svg | 14 + src/assets/newspaper-outline.svg | 6 + src/assets/profile.svg | 5 + src/assets/search_icon.svg | 11 + src/assets/star.svg | 10 + src/assets/subscriptions-icon.svg | 5 + src/components/Avatar.tsx | 171 +++++ src/components/Button.tsx | 139 ++++ src/components/Cards/DashboardEventCard.tsx | 281 ++++++++ src/components/Cards/DashboardPost.tsx | 208 ++++++ src/components/Cards/ExtensionEventCard.tsx | 296 ++++++++ src/components/Cards/LoopSummary.tsx | 109 +++ src/components/Cards/index.ts | 14 + src/components/DateBadge.tsx | 124 ++++ src/components/Logo.tsx | 88 +++ src/components/SearchBar.tsx | 197 +++++ src/components/SearchPanel.tsx | 473 ++++++++++++ src/components/SideBar.tsx | 244 +++++++ src/components/Tags.tsx | 137 ++++ src/components/Toggle.tsx | 148 ++++ src/index.css | 75 +- src/pages/DesignSystem.tsx | 757 ++++++++++++++++++++ src/styles/tokens.css | 197 +++++ tsconfig.app.json | 2 +- vite.config.ts | 8 +- 39 files changed, 3884 insertions(+), 69 deletions(-) create mode 100644 .cursor/rules/frontend/figma.md create mode 100644 src/assets/bookmark-filled.svg create mode 100644 src/assets/bookmark.svg create mode 100644 src/assets/calendar.svg create mode 100644 src/assets/close_search.svg create mode 100644 src/assets/close_tags.svg create mode 100644 src/assets/external-link.svg create mode 100644 src/assets/home-icon.svg create mode 100644 src/assets/location-pin.svg create mode 100644 src/assets/loop-logo-mixed.svg create mode 100644 src/assets/loop_logo.svg create mode 100644 src/assets/newspaper-outline.svg create mode 100644 src/assets/profile.svg create mode 100644 src/assets/search_icon.svg create mode 100644 src/assets/star.svg create mode 100644 src/assets/subscriptions-icon.svg create mode 100644 src/components/Avatar.tsx create mode 100644 src/components/Button.tsx create mode 100644 src/components/Cards/DashboardEventCard.tsx create mode 100644 src/components/Cards/DashboardPost.tsx create mode 100644 src/components/Cards/ExtensionEventCard.tsx create mode 100644 src/components/Cards/LoopSummary.tsx create mode 100644 src/components/Cards/index.ts create mode 100644 src/components/DateBadge.tsx create mode 100644 src/components/Logo.tsx create mode 100644 src/components/SearchBar.tsx create mode 100644 src/components/SearchPanel.tsx create mode 100644 src/components/SideBar.tsx create mode 100644 src/components/Tags.tsx create mode 100644 src/components/Toggle.tsx create mode 100644 src/pages/DesignSystem.tsx create mode 100644 src/styles/tokens.css diff --git a/.cursor/rules/frontend/figma.md b/.cursor/rules/frontend/figma.md new file mode 100644 index 0000000..696ffe5 --- /dev/null +++ b/.cursor/rules/frontend/figma.md @@ -0,0 +1,25 @@ +## Figma MCP Integration Rules +These rules define how to translate Figma inputs into code for this project and must be followed for every Figma-driven change. + +### Required flow (do not skip) +1. Run get_design_context first to fetch the structured representation for the exact node(s). +2. If the response is too large or truncated, run get_metadata to get the high‑level node map and then re‑fetch only the required node(s) with get_design_context. +3. Run get_screenshot for a visual reference of the node variant being implemented. +4. Only after you have both get_design_context and get_screenshot, download any assets needed and start implementation. +5. Translate the output (usually React + Tailwind) into this project’s conventions, styles and framework.  Reuse the project’s color tokens, components, and typography wherever possible. +6. Validate against Figma for 1:1 look and behavior before marking complete. + +### Implementation rules +- Treat the Figma MCP output (React + Tailwind) as a representation of design and behavior, not as final code style. +- Replace Tailwind utility classes with the project’s preferred utilities/design‑system tokens when applicable. +- Reuse existing components (e.g., buttons, inputs, typography, icon wrappers) instead of duplicating functionality. +- Use the project’s color system, typography scale, and spacing tokens consistently. +- Respect existing routing, state management, and data‑fetch patterns already adopted in the repo. +- Strive for 1:1 visual parity with the Figma design. When conflicts arise, prefer design‑system tokens and adjust spacing or sizes minimally to match visuals. +- Validate the final UI against the Figma screenshot for both look and behavior. + +### Figma MCP server rules +  - The Figma MCP server provides an assets endpoint which can serve image and SVG assets +  - IMPORTANT: If the Figma MCP server returns a localhost source for an image or an SVG, use that image or SVG source directly +  - IMPORTANT: DO NOT import/add new icon packages, all the assets should be in the Figma payload +  - IMPORTANT: do NOT use or create placeholders if a localhost source is provided \ No newline at end of file diff --git a/bun.lock b/bun.lock index 8dba42b..7bc8bd9 100644 --- a/bun.lock +++ b/bun.lock @@ -10,9 +10,11 @@ "convex": "^1.32.0", "react": "^19.2.0", "react-dom": "^19.2.0", + "vite-plugin-svgr": "^5.2.0", }, "devDependencies": { "@eslint/js": "^9.39.1", + "@tailwindcss/vite": "^4.2.2", "@types/node": "^24.10.1", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", @@ -21,6 +23,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "tailwindcss": "^4.2.2", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", "vite": "^7.3.1", @@ -170,6 +173,8 @@ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="], @@ -220,6 +225,60 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="], + "@svgr/babel-plugin-add-jsx-attribute": ["@svgr/babel-plugin-add-jsx-attribute@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g=="], + + "@svgr/babel-plugin-remove-jsx-attribute": ["@svgr/babel-plugin-remove-jsx-attribute@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA=="], + + "@svgr/babel-plugin-remove-jsx-empty-expression": ["@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA=="], + + "@svgr/babel-plugin-replace-jsx-attribute-value": ["@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ=="], + + "@svgr/babel-plugin-svg-dynamic-title": ["@svgr/babel-plugin-svg-dynamic-title@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og=="], + + "@svgr/babel-plugin-svg-em-dimensions": ["@svgr/babel-plugin-svg-em-dimensions@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g=="], + + "@svgr/babel-plugin-transform-react-native-svg": ["@svgr/babel-plugin-transform-react-native-svg@8.1.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q=="], + + "@svgr/babel-plugin-transform-svg-component": ["@svgr/babel-plugin-transform-svg-component@8.0.0", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw=="], + + "@svgr/babel-preset": ["@svgr/babel-preset@8.1.0", "", { "dependencies": { "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", "@svgr/babel-plugin-transform-svg-component": "8.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug=="], + + "@svgr/core": ["@svgr/core@8.1.0", "", { "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", "camelcase": "^6.2.0", "cosmiconfig": "^8.1.3", "snake-case": "^3.0.4" } }, "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA=="], + + "@svgr/hast-util-to-babel-ast": ["@svgr/hast-util-to-babel-ast@8.0.0", "", { "dependencies": { "@babel/types": "^7.21.3", "entities": "^4.4.0" } }, "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q=="], + + "@svgr/plugin-jsx": ["@svgr/plugin-jsx@8.1.0", "", { "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", "@svgr/hast-util-to-babel-ast": "8.0.0", "svg-parser": "^2.0.4" }, "peerDependencies": { "@svgr/core": "*" } }, "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="], + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], @@ -282,6 +341,8 @@ "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], + "caniuse-lite": ["caniuse-lite@1.0.30001774", "", {}, "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -298,6 +359,8 @@ "cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="], + "cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], @@ -306,8 +369,18 @@ "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="], + "electron-to-chromium": ["electron-to-chromium@1.5.302", "", {}, "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg=="], + "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], + "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -332,6 +405,8 @@ "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -358,6 +433,8 @@ "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], @@ -370,6 +447,8 @@ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], @@ -378,6 +457,8 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], @@ -388,6 +469,8 @@ "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], @@ -400,14 +483,44 @@ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="], + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "lucia": ["lucia@3.2.2", "", { "dependencies": { "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0" } }, "sha512-P1FlFBGCMPMXu+EGdVD9W4Mjm0DqsusmKgO7Xc33mI5X1bklmsQb0hfzPhXomQr9waWIBDsiOjvr1e6BTaUqpA=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -416,6 +529,8 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], "oauth4webapi": ["oauth4webapi@3.8.5", "", {}, "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg=="], @@ -428,12 +543,16 @@ "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], @@ -472,16 +591,26 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "svg-parser": ["svg-parser@2.0.4", "", {}, "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="], + + "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], + + "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -496,6 +625,8 @@ "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "vite-plugin-svgr": ["vite-plugin-svgr@5.2.0", "", { "dependencies": { "@rollup/pluginutils": "^5.3.0", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0" }, "peerDependencies": { "vite": ">=3.0.0" } }, "sha512-qj2eAKF8C6PZWemVTvQA0xgQIcP1hHU6Buh7fl6BhvayWwnuxE+z417miKxeDvRWbDrupQ1oK99hfxElopJ3sQ=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], @@ -516,6 +647,18 @@ "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], diff --git a/index.html b/index.html index c02f544..823da63 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,10 @@ cornell-loop + + + +
diff --git a/package.json b/package.json index 15a3d81..49aba10 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,12 @@ "@convex-dev/auth": "^0.0.91", "convex": "^1.32.0", "react": "^19.2.0", - "react-dom": "^19.2.0" + "react-dom": "^19.2.0", + "vite-plugin-svgr": "^5.2.0" }, "devDependencies": { "@eslint/js": "^9.39.1", + "@tailwindcss/vite": "^4.2.2", "@types/node": "^24.10.1", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", @@ -27,6 +29,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "tailwindcss": "^4.2.2", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", "vite": "^7.3.1" diff --git a/src/App.tsx b/src/App.tsx index 32ea328..855c3c8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,8 @@ import './App.css'; +import DesignSystem from './pages/DesignSystem'; function App() { - - return ( -
- App -
- ) -}; + return ; +} -export default App +export default App; diff --git a/src/assets/bookmark-filled.svg b/src/assets/bookmark-filled.svg new file mode 100644 index 0000000..36f9464 --- /dev/null +++ b/src/assets/bookmark-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/bookmark.svg b/src/assets/bookmark.svg new file mode 100644 index 0000000..a6b895d --- /dev/null +++ b/src/assets/bookmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/calendar.svg b/src/assets/calendar.svg new file mode 100644 index 0000000..ea2c6e7 --- /dev/null +++ b/src/assets/calendar.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/close_search.svg b/src/assets/close_search.svg new file mode 100644 index 0000000..44546b4 --- /dev/null +++ b/src/assets/close_search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/close_tags.svg b/src/assets/close_tags.svg new file mode 100644 index 0000000..85044ba --- /dev/null +++ b/src/assets/close_tags.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/external-link.svg b/src/assets/external-link.svg new file mode 100644 index 0000000..da56e26 --- /dev/null +++ b/src/assets/external-link.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/home-icon.svg b/src/assets/home-icon.svg new file mode 100644 index 0000000..e8b23a1 --- /dev/null +++ b/src/assets/home-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/location-pin.svg b/src/assets/location-pin.svg new file mode 100644 index 0000000..c698f90 --- /dev/null +++ b/src/assets/location-pin.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/loop-logo-mixed.svg b/src/assets/loop-logo-mixed.svg new file mode 100644 index 0000000..4f346d4 --- /dev/null +++ b/src/assets/loop-logo-mixed.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/loop_logo.svg b/src/assets/loop_logo.svg new file mode 100644 index 0000000..36a7601 --- /dev/null +++ b/src/assets/loop_logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/newspaper-outline.svg b/src/assets/newspaper-outline.svg new file mode 100644 index 0000000..128a467 --- /dev/null +++ b/src/assets/newspaper-outline.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/profile.svg b/src/assets/profile.svg new file mode 100644 index 0000000..8edd584 --- /dev/null +++ b/src/assets/profile.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/search_icon.svg b/src/assets/search_icon.svg new file mode 100644 index 0000000..fa30224 --- /dev/null +++ b/src/assets/search_icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/star.svg b/src/assets/star.svg new file mode 100644 index 0000000..cfe2de0 --- /dev/null +++ b/src/assets/star.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/subscriptions-icon.svg b/src/assets/subscriptions-icon.svg new file mode 100644 index 0000000..be32e9a --- /dev/null +++ b/src/assets/subscriptions-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx new file mode 100644 index 0000000..62f60e7 --- /dev/null +++ b/src/components/Avatar.tsx @@ -0,0 +1,171 @@ +/** + * Avatar — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Avatar (node 494:1719) + * + * Variants (Figma `property1`): + * • "Default" — single 32 px circle + organisation name label + * • "multiple" — N stacked 32 px circles (−8 px overlap, 2 px ring) + + * comma-separated name labels + * + * The variant is inferred from the length of the `avatars` array — pass one + * item for Default, two or more for the multiple stack. + * + * Image fallback: + * When an avatar item has no `src`, profile.svg from src/assets is shown on a + * Neutral/100 background. The icon is normalised to ~Neutral/900 via the + * --filter-icon-nav CSS custom property defined in tokens.css. + * + * All colours, spacing, and font values reference CSS custom properties from + * src/styles/tokens.css — nothing is hardcoded. + */ + +import ProfileIcon from '../assets/profile.svg?react'; + +// ─── Public types ───────────────────────────────────────────────────────────── + +export interface AvatarItem { + /** Photo / org image URL. Omit to show the profile.svg placeholder. */ + src?: string; + /** Display name. Shown in the label and used as alt text. */ + name: string; +} + +export interface AvatarProps { + /** + * One or more avatar items. + * `avatars.length === 1` → "Default" variant (single circle + single label) + * `avatars.length > 1` → "multiple" variant (stacked circles + comma labels) + */ + avatars: AvatarItem[]; + className?: string; +} + +// ─── AvatarCircle ───────────────────────────────────────────────────────────── + +interface AvatarCircleProps { + item: AvatarItem; + /** + * When true the circle gets the white 2 px ring separator and a negative + * left margin for stacking (Figma: mr-[-8px] pattern on every avatar after + * the first, implemented here as margin-left for cleaner DOM flow). + */ + stacked?: boolean; + /** CSS z-index so earlier avatars render on top of later ones. */ + zIndex?: number; +} + +function AvatarCircle({ item, stacked = false, zIndex }: AvatarCircleProps) { + return ( + + {item.src ? ( + {item.name} + ) : ( + /* + * Fallback: profile.svg rendered inline via SVGR. + * Sized to --space-5 (20 px) inside the 32 px circle. + * --filter-icon-nav normalises the hardcoded SVG stroke to ~Neutral/900. + */ + + ); +} + +// ─── Avatar ─────────────────────────────────────────────────────────────────── + +export function Avatar({ avatars, className }: AvatarProps) { + if (avatars.length === 0) return null; + + const isMultiple = avatars.length > 1; + + return ( + /* + * Figma outer container: flex gap-[8px] items-center px-[6px] + * --space-2 = 8 px (gap between circle stack and label) + * --space-1-5 = 6 px (horizontal padding) + */ +
+ {/* ── Circle(s) ── */} +
+ {avatars.map((avatar, i) => ( + 0} + /* Higher index = lower z-index so first avatar sits on top */ + zIndex={avatars.length - i} + /> + ))} +
+ + {/* ── Label ── */} + {/* + * Single variant: just the name + * Multiple variant: "Name1, Name2, Name3" — each name+comma is its own flex + * span matching Figma's gap-[4px] group pattern. + * Typography: DM Sans SemiBold 14 px, Neutral/700, −0.5 px tracking. + */} +
+ {avatars.map((avatar, i) => ( + + {avatar.name} + {/* Attach comma directly to name (no space) — matches Figma's

,

pattern */} + {i < avatars.length - 1 && ,} +
+ ))} +
+
+ ); +} diff --git a/src/components/Button.tsx b/src/components/Button.tsx new file mode 100644 index 0000000..31cef03 --- /dev/null +++ b/src/components/Button.tsx @@ -0,0 +1,139 @@ +/** + * Button — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Buttons (node 381:1681) + * + * Variants: + * primary — filled brand orange (Primary/700); default → hover → press → disabled + * secondary — outlined; white bg with Neutral/300 border; default → hover → press → disabled + * + * Sizes: + * sm — compact; px 16px, py 6px, body-2 Regular + * md — standard; px 16px, py 6px, body-2 SemiBold + * cta — full-width call-to-action; px 16px, py 8px, body-2 SemiBold (Figma node 390:793) + * + * Border-radius: --radius-button (16px) — from Figma `rounded-[16px]` on all button states. + * + * All colours, spacing, and font values reference CSS custom properties + * from src/styles/tokens.css — nothing is hardcoded. + */ + +import type { ComponentPropsWithoutRef } from 'react'; + +// ─── Public types ──────────────────────────────────────────────────────────── + +export type ButtonVariant = 'primary' | 'secondary'; +export type ButtonSize = 'sm' | 'md' | 'cta'; + +export interface ButtonProps extends ComponentPropsWithoutRef<'button'> { + /** Visual hierarchy level. Defaults to 'primary'. */ + variant?: ButtonVariant; + /** + * Size preset: + * sm — compact pill (py 6px), body-2 Regular — Figma Primary/Small + * md — standard (py 6px), body-2 SemiBold — standard interactive button + * cta — full-width CTA (py 8px), body-2 SemiBold — Figma CTA Buttons (node 390:793) + */ + size?: ButtonSize; +} + +// ─── Style lookup tables ───────────────────────────────────────────────────── +// +// Each entry must be a complete, static string so Tailwind's content scanner +// can detect every class at build time — do not build these strings dynamically. + +const BASE_CLASSES = + 'inline-flex items-center justify-center gap-[var(--space-2)] ' + + 'font-[family-name:var(--font-body)] rounded-[var(--radius-button)] ' + + 'whitespace-nowrap select-none cursor-pointer ' + + 'transition-[background-color,box-shadow,color] duration-150 ease-in-out ' + + 'focus-visible:outline-2 focus-visible:outline-offset-2 ' + + 'disabled:pointer-events-none'; + +const VARIANT_CLASSES: Record = { + /** + * Primary — filled brand orange. + * Default → Primary/700 (#eb7128) + * Hover → Primary/hover (#d35910) + glow ring + * Press → Primary/800 (#a74409) + glow ring + * Disabled → Neutral/500 (#adb5bd) + * Source: Figma Frame95 (node 382:822) and Frame94 CTA (node 390:793) + */ + primary: + 'bg-[var(--color-primary-700)] text-[var(--color-white)] ' + + 'hover:bg-[var(--color-primary-hover)] hover:shadow-[var(--shadow-primary-glow)] ' + + 'active:bg-[var(--color-primary-800)] active:shadow-[var(--shadow-primary-glow)] ' + + 'focus-visible:outline-[var(--color-primary-700)] ' + + 'disabled:bg-[var(--color-neutral-500)] disabled:shadow-none', + + /** + * Secondary — outlined. + * Default → white bg, Neutral/300 border, black text, font-normal + * Hover → Neutral/100 bg (surface-subtle) + * Press → Neutral/300 bg (fills with border colour) + * Disabled → Neutral/500 bg, Neutral/700 border + text + * Source: Figma Frame93 (node 383:415) + */ + secondary: + 'bg-[var(--color-surface)] border border-[var(--color-border)] text-[var(--color-black)] ' + + 'hover:bg-[var(--color-surface-subtle)] ' + + 'active:bg-[var(--color-border)] ' + + 'focus-visible:outline-[var(--color-neutral-900)] ' + + 'disabled:bg-[var(--color-neutral-500)] disabled:border-[var(--color-neutral-700)] disabled:text-[var(--color-neutral-700)]', +}; + +const SIZE_CLASSES: Record = { + /** + * sm — Figma Primary/Small (Frame95, node 382:822). + * px 16px, py 6px, body-2 Regular. + */ + sm: + 'px-[var(--space-4)] py-[var(--space-1-5)] ' + + 'text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] ' + + 'tracking-[var(--letter-spacing-body2)] font-normal', + + /** + * md — standard interactive size; same padding as sm, SemiBold weight. + */ + md: + 'px-[var(--space-4)] py-[var(--space-1-5)] ' + + 'text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] ' + + 'tracking-[var(--letter-spacing-body2)] font-semibold', + + /** + * cta — Figma CTA Buttons (Frame94, node 390:793). + * Full-width, py 8px, body-2 SemiBold. + */ + cta: + 'w-full px-[var(--space-4)] py-[var(--space-2)] ' + + 'text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] ' + + 'tracking-[var(--letter-spacing-body2)] font-semibold', +}; + +// ─── Component ─────────────────────────────────────────────────────────────── + +export function Button({ + variant = 'primary', + size = 'md', + className, + children, + ...rest +}: ButtonProps) { + return ( + + ); +} diff --git a/src/components/Cards/DashboardEventCard.tsx b/src/components/Cards/DashboardEventCard.tsx new file mode 100644 index 0000000..273c5fb --- /dev/null +++ b/src/components/Cards/DashboardEventCard.tsx @@ -0,0 +1,281 @@ +/** + * DashboardEventCard — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Cards + * Node: 515:3339 "Event Card - Dashboard" + * + * Displays a single event with title, datetime, location, description, tags, + * and RSVP / share / bookmark actions. + * + * The RSVP button intentionally uses --radius-card (16px) not --radius-input (8px); + * this matches the Figma spec where event-card pill buttons are more rounded than + * standard form inputs. + * + * "Show more" text colour: Figma uses #767676 which is not a named palette colour. + * --color-text-secondary (Neutral/600 #616972) is the closest available token. + * + * All colours, spacing, and font values reference CSS custom properties from + * src/styles/tokens.css — nothing is hardcoded. + */ + +import type { ComponentPropsWithoutRef } from 'react'; +import { Tag } from '../Tags'; +import type { TagColor } from '../Tags'; +import CalendarIcon from '../../assets/calendar.svg?react'; +import LocationPinIcon from '../../assets/location-pin.svg?react'; +import ExternalLinkIcon from '../../assets/external-link.svg?react'; +import BookmarkIcon from '../../assets/bookmark.svg?react'; +import BookmarkFilledIcon from '../../assets/bookmark-filled.svg?react'; + +// ─── Shared types (re-exported for use by DashboardPost) ───────────────────── + +export interface TagItem { + label: string; + /** Defaults to 'neutral'. Pass 'blue' for availability / highlight tags. */ + color?: TagColor; +} + +// ─── Shared class strings ───────────────────────────────────────────────────── + +const BODY2_CLASSES = + 'font-[family-name:var(--font-body)] font-normal ' + + 'text-[var(--font-size-body2)] leading-[var(--line-height-body2)] ' + + 'tracking-[var(--letter-spacing-body2)]'; + +// ─── Public types ───────────────────────────────────────────────────────────── + +export interface DashboardEventCardProps extends ComponentPropsWithoutRef<'article'> { + title: string; + datetime: string; + location: string; + description: string; + /** + * When true the description is clamped to 3 lines and a "Show more" trigger + * is rendered. Pass `onShowMore` to handle the action. Defaults to true. + */ + descriptionTruncated?: boolean; + onShowMore?: () => void; + tags?: TagItem[]; + onRsvp?: () => void; + onShare?: () => void; + /** + * Whether the event is currently bookmarked / saved. + * true → bookmark-filled.svg (Primary/700 orange fill) — Figma "saved" state + * false → bookmark.svg (Neutral/500 gray outline) — Figma "Default" state + * Defaults to false. + */ + bookmarked?: boolean; + /** Called when the bookmark button is clicked. Toggle `bookmarked` in the parent. */ + onBookmark?: () => void; +} + +// ─── Component ──────────────────────────────────────────────────────────────── + +export function DashboardEventCard({ + title, + datetime, + location, + description, + descriptionTruncated = true, + onShowMore, + tags = [], + onRsvp, + onShare, + bookmarked = false, + onBookmark, + className, + ...rest +}: DashboardEventCardProps) { + return ( +
+ {/* ── Section 1: title row + meta row ── */} +
+ + {/* Title row: title text | share + bookmark icons | RSVP button */} +
+

+ {title} +

+ + {/* Action icons */} +
+ {/* + * Share — external-link.svg: stroke="#ADB5BD" (Neutral/500, muted by default). + * On hover, --filter-icon-close-default darkens it to ~Neutral/700 (#495057). + */} + + + {/* + * Bookmark — switches SVG based on `bookmarked` prop: + * false → BookmarkIcon stroke="#ADB5BD" (Neutral/500) — Figma "Default" + * true → BookmarkFilledIcon fill="#EB7128" (Primary/700) — Figma "saved" + * When unsaved, hover darkens the outline via --filter-icon-close-default. + * Source: Figma Icons section — Bookmark icon states (node 493:1041) + */} + +
+ + {/* + * RSVP button + * Figma: bg white, Neutral/300 border, rounded-[16px] (--radius-card), + * px 16px, py 6px, body-2 regular, black text. + * Uses --radius-card intentionally — Figma pills in this card are more rounded + * than standard inputs/buttons. + */} + +
+ + {/* Meta row: datetime + location */} + {/* calendar.svg & location-pin.svg use stroke="#495057" (Neutral/700) natively — no filter. */} +
+
+
+ +
+
+
+
+ + {/* ── Section 2: description + show-more ── */} +
+

+ {description} +

+ + {descriptionTruncated && onShowMore && ( + + )} +
+ + {/* ── Section 3: tags ── */} + {tags.length > 0 && ( +
+ {tags.map((tag, i) => ( + + {tag.label} + + ))} +
+ )} +
+ ); +} diff --git a/src/components/Cards/DashboardPost.tsx b/src/components/Cards/DashboardPost.tsx new file mode 100644 index 0000000..2093937 --- /dev/null +++ b/src/components/Cards/DashboardPost.tsx @@ -0,0 +1,208 @@ +/** + * DashboardPost — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Cards + * Node: 520:6160 "Dashboard Post" + * + * A feed post wrapper: an organisation attribution header (avatar stack + + * org names + an "indicator" badge + posted date) above a DashboardEventCard. + * + * Structure from Figma: + * • The outer container has NO background — it inherits from its feed parent. + * • The post header renders directly on the parent's surface. + * • The embedded DashboardEventCard has its own white card background. + * + * Avatar stack: 32px avatars (--space-8) with −8px overlap (--space-2). + * Avatars without a `url` fall back to an initial letter badge. + * + * Indicator badge bg: Figma uses #5f5f5f — approximated with --color-neutral-600 + * (#616972), the closest available token. + * + * All colours, spacing, and font values reference CSS custom properties from + * src/styles/tokens.css — nothing is hardcoded. + */ + +import { DashboardEventCard } from './DashboardEventCard'; +import type { DashboardEventCardProps, TagItem } from './DashboardEventCard'; +import StarIcon from '../../assets/star.svg?react'; + +export type { TagItem }; + +// ─── Public types ───────────────────────────────────────────────────────────── + +export interface Organization { + /** Display name shown beside the avatar stack. */ + name: string; + /** Optional avatar image URL. Falls back to an initial-letter badge. */ + avatarUrl?: string; +} + +export interface DashboardPostProps { + /** List of organisations that authored / shared this post. */ + organizations: Organization[]; + /** Human-readable posted date, e.g. "Feb 12". */ + postedAt: string; + // ── DashboardEventCard data ── + title: string; + datetime: string; + location: string; + description: string; + descriptionTruncated?: boolean; + onShowMore?: () => void; + tags?: DashboardEventCardProps['tags']; + onRsvp?: () => void; + onShare?: () => void; + onBookmark?: () => void; + className?: string; +} + +// ─── Shared body-2 class string ─────────────────────────────────────────────── + +const BODY2 = + 'font-[family-name:var(--font-body)] font-normal ' + + 'text-[var(--font-size-body2)] leading-[var(--line-height-body2)] ' + + 'tracking-[var(--letter-spacing-body2)]'; + +// ─── Component ──────────────────────────────────────────────────────────────── + +export function DashboardPost({ + organizations, + postedAt, + title, + datetime, + location, + description, + descriptionTruncated, + onShowMore, + tags, + onRsvp, + onShare, + onBookmark, + className, +}: DashboardPostProps) { + const cardProps: DashboardEventCardProps = { + title, + datetime, + location, + description, + descriptionTruncated, + onShowMore, + tags, + onRsvp, + onShare, + onBookmark, + }; + + return ( +
+ {/* ── Post header ── */} +
+ + {/* Left: avatar stack + org names + indicator badge */} +
+ + {/* Stacked avatars — overlap with negative margin on all but first */} + {organizations.length > 0 && ( +
+ {organizations.map((org, i) => ( + 0 ? '[margin-left:calc(-1*var(--space-2))]' : '', + ] + .filter(Boolean) + .join(' ')} + style={{ zIndex: organizations.length - i }} + > + {org.avatarUrl ? ( + {org.name} + ) : ( + /* Fallback: initial letter badge using secondary palette */ + + {org.name.charAt(0).toUpperCase()} + + )} + + ))} +
+ )} + + {/* Org names — Figma: DM Sans SemiBold 14px, Neutral/700 */} + + {organizations.map((o) => o.name).join(', ')} + + + {/* + * Indicator badge + * Figma: small circular icon button, bg #5f5f5f ≈ --color-neutral-600. + * Appears to be a "shared" or "featured" signal in the feed. + */} + +
+ + {/* Right: separator bullet + date */} +
+ {/* Bullet — Figma: Neutral/500 */} + + + {postedAt} + +
+
+ + {/* ── Embedded event card ── */} + +
+ ); +} diff --git a/src/components/Cards/ExtensionEventCard.tsx b/src/components/Cards/ExtensionEventCard.tsx new file mode 100644 index 0000000..7aa723a --- /dev/null +++ b/src/components/Cards/ExtensionEventCard.tsx @@ -0,0 +1,296 @@ +/** + * ExtensionEventCard — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Event Card (extension) + * Node: 493:800 + * + * The Figma page documents three related pieces shown in the "EVENT CARD" and + * "THUMBNAIL" sections: + * + * ┌──────────────────────────────────────────────────────────────┐ + * │ Thumbnail (Frame166) │ + * │ property1 = "date" → DateBadge with day number + month │ + * │ property1 = "news" → DateBadge with newspaper icon │ + * ├──────────────────────────────────────────────────────────────┤ + * │ Event Row (Frame45) │ + * │ DateBadge + title/description text + bookmark icon │ + * │ property1 = "Default" | "hover" │ + * │ → hover implemented as CSS hover (bg-surface-subtle) │ + * ├──────────────────────────────────────────────────────────────┤ + * │ Event Card (Frame90) │ + * │ Org header (avatar circle + name) + list of Event Rows │ + * │ border Neutral/300, rounded-[12px] (--space-3), p-[12px] │ + * └──────────────────────────────────────────────────────────────┘ + * + * All three are exported so they can be composed independently. + * + * Bookmark states (per Figma Icons section): + * bookmarked=false → bookmark.svg (gray outline, Neutral/500) + * bookmarked=false + hover → darkens via --filter-icon-close-default + * bookmarked=true → bookmark-filled.svg (orange fill, Primary/700) + * + * SVG assets used (all via SVGR ?react): + * bookmark.svg, bookmark-filled.svg — from existing design system + * newspaper-outline.svg — created for the "news" thumbnail variant + * + * All colours, spacing, and font values reference CSS custom properties from + * src/styles/tokens.css — nothing is hardcoded. + */ + +import type { ComponentPropsWithoutRef } from 'react'; +import BookmarkIcon from '../../assets/bookmark.svg?react'; +import BookmarkFilledIcon from '../../assets/bookmark-filled.svg?react'; +import { DateBadge } from '../DateBadge'; +import type { ThumbnailVariant } from '../DateBadge'; +export type { ThumbnailVariant, DateBadgeProps } from '../DateBadge'; +export { DateBadge }; + +// ─── Shared typography class strings ────────────────────────────────────────── + +const BODY2_SEMIBOLD = + 'font-[family-name:var(--font-body)] font-semibold ' + + 'text-[var(--font-size-body2)] leading-[var(--line-height-body2)] ' + + 'tracking-[var(--letter-spacing-body2)]'; + +const BODY3 = + 'font-[family-name:var(--font-body)] font-normal ' + + 'text-[var(--font-size-body3)] leading-[var(--line-height-body3)] ' + + 'tracking-[var(--letter-spacing-body3)]'; + +// DateBadge, ThumbnailVariant, and DateBadgeProps are re-exported above +// from src/components/DateBadge.tsx. + +// ─── Public types for rows and cards ────────────────────────────────────────── + +export interface ExtensionEventItem { + /** Controls the thumbnail badge type. Defaults to "date". */ + thumbnailVariant?: ThumbnailVariant; + /** Day-of-month for the "date" thumbnail (e.g. 24). */ + day?: number | string; + /** Abbreviated month for the "date" thumbnail (e.g. "Mar"). */ + month?: string; + /** Event title — DM Sans SemiBold 14 px, Neutral/900. */ + title: string; + /** Short supporting text shown below the title. DM Sans Regular 12 px, Neutral/700. */ + description?: string; + /** + * Whether this event is bookmarked / saved. + * true → bookmark-filled.svg (Primary/700 orange) — Figma "saved" state + * false → bookmark.svg (Neutral/500 gray) — Figma "Default" state + * Defaults to false. + */ + bookmarked?: boolean; + /** Called when the bookmark button is clicked. */ + onBookmark?: () => void; +} + +// ─── ExtensionEventRow ──────────────────────────────────────────────────────── +// +// Single compact event row (Figma Frame45, node 493:919). +// Layout: [DateBadge] [title + description flex-1] [BookmarkIcon] +// +// Figma property1: +// "Default" → transparent bg, normal bookmark +// "hover" → bg-surface-subtle (implemented as CSS hover — no prop needed) + +export interface ExtensionEventRowProps + extends ExtensionEventItem, + Omit, 'title'> {} + +export function ExtensionEventRow({ + thumbnailVariant = 'date', + day, + month, + title, + description, + bookmarked = false, + onBookmark, + className, + ...rest +}: ExtensionEventRowProps) { + return ( + /* + * Interactive states — Figma property1 "Default" | "hover": + * Default → bg white (surface) + * Hover → bg-surface-subtle (#f8f9fa, Neutral/100) + * Transition matches the nav-tab and RSVP-row hover pattern. + */ +
+ {/* Thumbnail badge */} + + + {/* Text content */} +
+

+ {title} +

+ + {description && ( +

+ {description} +

+ )} +
+ + {/* + * Bookmark button — switches SVG based on `bookmarked` prop. + * Same pattern as DashboardEventCard: + * false → BookmarkIcon (stroke="#ADB5BD", gray outline) + * true → BookmarkFilledIcon (fill="#EB7128", orange filled) + * On hover when unsaved: --filter-icon-close-default darkens to ~Neutral/700. + * Source: Figma Icons section — Bookmark icon states (node 493:1041) + */} + +
+ ); +} + +// ─── ExtensionEventCard ─────────────────────────────────────────────────────── +// +// Grouped card with an org attribution header above a list of event rows. +// Figma Frame90, node 494:576. +// +// Layout: +// ┌──────────────────────────────────┐ +// │ [avatar] Org name │ ← org header (gap-[8px], px-[6px]) +// │ ───────────────────────────── │ +// │ [badge] Title Description │ ← event row × N +// │ [badge] Title Description │ +// └──────────────────────────────────┘ +// +// Figma spec: +// bg white, border Neutral/300 (#dee2e6 = --color-border), rounded-[12px], +// p-[12px], gap-[16px], overflow-hidden + +export interface ExtensionEventCardProps + extends ComponentPropsWithoutRef<'div'> { + /** Organisation name shown in the card header. */ + orgName: string; + /** URL for the org's circular avatar image. Falls back to an initials badge. */ + orgAvatarUrl?: string; + /** List of events shown as rows inside the card. */ + events?: ExtensionEventItem[]; +} + +export function ExtensionEventCard({ + orgName, + orgAvatarUrl, + events = [], + className, + ...rest +}: ExtensionEventCardProps) { + return ( + /* + * Figma: bg white, 1px border --color-border (Neutral/300 #dee2e6), + * rounded-[12px] (--space-3 = 0.75rem = 12px), p-[12px], gap-[16px], + * overflow-hidden. + */ +
+ {/* ── Org header ── */} +
+ {/* Org avatar — 32 px circle */} +
+ {orgAvatarUrl ? ( + {orgName} + ) : ( + /* Initials fallback */ + + {orgName.charAt(0).toUpperCase()} + + )} +
+ + {/* Org name — Figma: DM Sans SemiBold 14px, Neutral/700 */} + + {orgName} + +
+ + {/* ── Event rows ── */} + {events.length > 0 && ( +
+ {events.map((event, i) => ( + + ))} +
+ )} +
+ ); +} diff --git a/src/components/Cards/LoopSummary.tsx b/src/components/Cards/LoopSummary.tsx new file mode 100644 index 0000000..a9be705 --- /dev/null +++ b/src/components/Cards/LoopSummary.tsx @@ -0,0 +1,109 @@ +/** + * LoopSummary — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Cards + * Node: 520:5930 "Loop Summary" + * + * An AI / org summary card that stands apart from event cards via its + * Primary/600 (#ffa26b) border and Shadow 2 elevation. + * + * Figma spec: + * Container: bg white, border Primary/600, Shadow 2, rounded-[16px], + * px 24px, py 16px. + * Header: Loop logo icon (16 × 16) + "Loop Summary" title in Primary/900. + * Body: DM Sans Regular 14px, Neutral/700. + * + * The Loop logo is a brand asset served from a remote Figma URL. + * Pass `logoSrc` to override with a local asset; omit it to use the inline + * SVG placeholder that approximates the brand icon shape. + * + * All colours, spacing, and font values reference CSS custom properties from + * src/styles/tokens.css — nothing is hardcoded. + */ + +import type { ComponentPropsWithoutRef } from 'react'; +// Note: the file uses an underscore (loop_logo.svg), not a hyphen. +import LoopLogo from '../../assets/loop_logo.svg?react'; +// ─── Public types ───────────────────────────────────────────────────────────── + +export interface LoopSummaryProps extends ComponentPropsWithoutRef<'section'> { + /** The summary text body. */ + summary: string; + /** + * Optional src for the Loop logo image. + * Falls back to an inline SVG approximation of the brand icon. + */ + logoSrc?: string; +} + +// ─── Component ──────────────────────────────────────────────────────────────── + +export function LoopSummary({ + summary, + logoSrc, + className, + ...rest +}: LoopSummaryProps) { + return ( +
+ {/* ── Header: logo + title ── */} +
+ {logoSrc ? ( + /* Caller-supplied override (e.g. a branded variant) */ + Loop + ) : ( + /* Default: loop_logo.svg rendered inline via SVGR */ +
+ + {/* ── Body text — Figma: DM Sans Regular 14px, Neutral/700 ── */} +

+ {summary} +

+
+ ); +} diff --git a/src/components/Cards/index.ts b/src/components/Cards/index.ts new file mode 100644 index 0000000..0a34505 --- /dev/null +++ b/src/components/Cards/index.ts @@ -0,0 +1,14 @@ +export { DashboardEventCard } from './DashboardEventCard'; +export type { DashboardEventCardProps, TagItem } from './DashboardEventCard'; + +export { DashboardPost } from './DashboardPost'; +export type { DashboardPostProps, Organization } from './DashboardPost'; + +export { LoopSummary } from './LoopSummary'; +export type { LoopSummaryProps } from './LoopSummary'; + +export { DateBadge } from '../DateBadge'; +export type { ThumbnailVariant, DateBadgeProps } from '../DateBadge'; + +export { ExtensionEventRow, ExtensionEventCard } from './ExtensionEventCard'; +export type { ExtensionEventItem, ExtensionEventRowProps, ExtensionEventCardProps } from './ExtensionEventCard'; diff --git a/src/components/DateBadge.tsx b/src/components/DateBadge.tsx new file mode 100644 index 0000000..df9e136 --- /dev/null +++ b/src/components/DateBadge.tsx @@ -0,0 +1,124 @@ +/** + * DateBadge — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Event Card (extension) + * Node: 493:1011 (date variant) / 493:1013 (news variant) + * + * A 55 × 55 px thumbnail widget used in event rows. + * The outer element is Primary/700 orange; a cream (Primary/400) inner element + * covers the interior except for a narrow left orange strip (~4 px). + * The cream layer extends 1.5 px beyond the top/bottom/right padding edges so + * it paints over the orange border on those sides — leaving orange only on the + * left accent strip (matches Figma node 493:1006 top: -1.5px). + * + * Two variants: + * "date" — shows a day-of-month number + abbreviated month label + * "news" — shows a newspaper-outline icon (for news / article events) + * + * Used by: + * • ExtensionEventCard (src/components/Cards/ExtensionEventCard.tsx) + * • SearchPanel (src/components/SearchPanel.tsx) + * + * All colours, spacing, and font values reference CSS custom properties from + * src/styles/tokens.css — nothing is hardcoded. + */ + +import NewspaperIcon from '../assets/newspaper-outline.svg?react'; + +// ─── Public types ───────────────────────────────────────────────────────────── + +export type ThumbnailVariant = 'date' | 'news'; + +export interface DateBadgeProps { + /** Controls which content fills the badge. Defaults to "date". */ + variant?: ThumbnailVariant; + /** Day-of-month shown in the "date" variant (e.g. 24). */ + day?: number | string; + /** Abbreviated month label shown in the "date" variant (e.g. "Mar"). */ + month?: string; + className?: string; +} + +// ─── Component ──────────────────────────────────────────────────────────────── + +export function DateBadge({ + variant = 'date', + day, + month, + className, +}: DateBadgeProps) { + return ( +
+ {/* + * Cream body — extends 1.5px beyond the padding edge on top/bottom/right + * so it paints over the orange border on those sides. + * Only the left strip (--space-1 ≈ 4px) stays orange. + * overflow-hidden on the parent clips everything to the border-box boundary. + */} + + ); +} diff --git a/src/components/Logo.tsx b/src/components/Logo.tsx new file mode 100644 index 0000000..cf77121 --- /dev/null +++ b/src/components/Logo.tsx @@ -0,0 +1,88 @@ +import LoopMarkIcon from '../assets/loop_logo.svg?react'; +import LoopMarkMixedIcon from '../assets/loop-logo-mixed.svg?react'; + +/** + * Logo variants matching the Figma "Logo" section (node 381:1325): + * + * wordmark → mark (orange) + "Loop" in black — Figma "Favicon" + * wordmark-light → mark (orange) + "Loop" in white — Figma "Logo - dark background" + * mark → mark (orange) only — Figma "Variant2" + * mark-mixed → mark (mixed/cream) only — Figma "Favicon - Orange Backgroud" + */ +export type LogoVariant = + | 'wordmark' + | 'wordmark-light' + | 'mark' + | 'mark-mixed'; + +export interface LoopLogoProps { + /** + * Which Figma logo variant to render. + * @default 'wordmark' + */ + variant?: LogoVariant; + /** + * Controls the height of the logo mark (and scales the wordmark text proportionally). + * Defaults to 'md' (~42px mark, matching the Figma Logo section). + * Use 'sm' for the compact sidebar presentation. + */ + size?: 'sm' | 'md' | 'lg'; + className?: string; +} + +const markSizeClass: Record, string> = { + sm: 'size-[var(--space-6)]', /* 24px — sidebar compact */ + md: 'size-[2.625rem]', /* 42px — Figma Logo section standard mark */ + lg: 'size-[3.4375rem]', /* 55px — display / hero usage */ +}; + +const wordmarkSizeClass: Record, string> = { + sm: 'text-[length:var(--font-size-wordmark)]', /* 32px */ + md: 'text-[length:var(--font-size-wordmark-display)]', /* 55px */ + lg: 'text-[length:var(--font-size-wordmark-display)]', /* 55px — same, let container scale */ +}; + +export function LoopLogo({ + variant = 'wordmark', + size = 'md', + className, +}: LoopLogoProps) { + const showText = variant === 'wordmark' || variant === 'wordmark-light'; + const textColorClass = + variant === 'wordmark-light' ? 'text-white' : 'text-[var(--color-neutral-900)]'; + + const MarkComponent = + variant === 'mark-mixed' ? LoopMarkMixedIcon : LoopMarkIcon; + + return ( +
+
+ ); +} + +export default LoopLogo; diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx new file mode 100644 index 0000000..30bcecd --- /dev/null +++ b/src/components/SearchBar.tsx @@ -0,0 +1,197 @@ +/** + * SearchBar — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Search Bar (node 389:124) + * Input variants: node 389:169 (Blank) and node 390:192 (with input) + * + * Figma exposes one structural prop on the component: + * property1: "Blank" | "with input" + * + * In this implementation that maps directly to whether the input is empty or has a value: + * • Empty input → "Blank" — search icon + placeholder, no clear button + * • Non-empty → "with input" — search icon + value, clear (×) button visible + * + * Both controlled and uncontrolled usage are supported (value / defaultValue pattern). + * Hover and focus states are CSS-only (no extra props required). + * + * All colours, spacing, and font values reference CSS custom properties from + * src/styles/tokens.css — nothing is hardcoded. + */ + +import { + useCallback, + useRef, + useState, + type ChangeEvent, + type InputHTMLAttributes, +} from 'react'; + +// Note: the file is search_icon.svg (not search.svg). +import SearchIcon from '../assets/search_icon.svg?react'; +import CloseSearchIcon from '../assets/close_search.svg?react'; + +// ─── Public types ───────────────────────────────────────────────────────────── + +export interface SearchBarProps + extends Omit, 'onChange' | 'value' | 'defaultValue'> { + /** Controlled value. Omit for uncontrolled usage (pair with `defaultValue`). */ + value?: string; + /** Initial value for uncontrolled usage. */ + defaultValue?: string; + /** Called with the new string value on every change. */ + onChange?: (value: string) => void; + /** Called when the user clicks the × clear button. */ + onClear?: () => void; + /** Input placeholder. Defaults to "Search". */ + placeholder?: string; + /** Extra classes applied to the outer wrapper. */ + className?: string; +} + +// ─── Component ──────────────────────────────────────────────────────────────── + +export function SearchBar({ + value, + defaultValue = '', + onChange, + onClear, + placeholder = 'Search', + className, + disabled, + ...rest +}: SearchBarProps) { + const isControlled = value !== undefined; + const [localValue, setLocalValue] = useState(defaultValue); + const displayValue = isControlled ? value : localValue; + const hasValue = (displayValue ?? '').length > 0; + + const inputRef = useRef(null); + + const handleChange = useCallback( + (e: ChangeEvent) => { + if (!isControlled) setLocalValue(e.target.value); + onChange?.(e.target.value); + }, + [isControlled, onChange], + ); + + const handleClear = useCallback(() => { + if (!isControlled) setLocalValue(''); + onChange?.(''); + onClear?.(); + inputRef.current?.focus(); + }, [isControlled, onChange, onClear]); + + return ( + /* + * Wrapper — Figma: bg white, Neutral/300 border, radius 16px, px 16px, py 8px + * focus-within: promotes a visible ring when the inner is focused. + */ + + ); +} diff --git a/src/components/SearchPanel.tsx b/src/components/SearchPanel.tsx new file mode 100644 index 0000000..a54f0e7 --- /dev/null +++ b/src/components/SearchPanel.tsx @@ -0,0 +1,473 @@ +/** + * SearchPanel — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Search Panel (node 390:201) + * The Figma page is marked "WIP"; visual hierarchy is finalised. + * + * This file exports TWO components: + * + * SearchPanel — the persistent right-sidebar panel showing: + * • "Your RSVPs" — RSVP'd events grouped by time period, + * each row has a DateBadge + title + description. + * • "Your Clubs" — subscribed club logos in a wrap grid, + * with optional notification count badge. + * + * SearchResultList — the autocomplete result card shown in Figma's "CARDS" section: + * • Grouped event results (period label + item rows) + * • Each row: title, org name, optional indicator badge, optional tag + * • Optional "Show more" link + * + * Interactive states (Figma "tab appearance modes"): + * RSVP rows : default → hover:bg-surface-subtle (bg highlight) + * Club items : default → hover:opacity-80 (fade) + * Result rows : default → hover:bg-surface-subtle (bg highlight) + * "Show more" : default link colour → hover:underline + * + * Icon: star.svg (used inside indicator badges) — imported via SVGR (?react). + * + * All colours, spacing, and font values reference CSS custom properties from + * src/styles/tokens.css — nothing is hardcoded. + */ + +import type { ComponentPropsWithoutRef } from 'react'; +import StarIcon from '../assets/star.svg?react'; +import { Tag } from './Tags'; +import { DateBadge } from './DateBadge'; + +// ─── Public types ───────────────────────────────────────────────────────────── + +export interface RsvpEvent { + /** Day-of-month shown on the DateBadge (e.g. 24). */ + day: number | string; + /** Abbreviated month shown on the DateBadge (e.g. "Mar"). */ + month: string; + title: string; + /** Short description shown below the title. Truncated at 2 lines. */ + description?: string; +} + +export interface RsvpGroup { + /** + * Time-bucket label displayed above the group. + * Examples: "Today" | "This week" | "Next week" + */ + period: string; + events: RsvpEvent[]; +} + +export interface Club { + id: string; + name: string; + /** Circle avatar image URL. Falls back to an initial-letter badge when omitted. */ + avatarUrl?: string; + /** When > 0 an orange notification pill is rendered over the avatar. */ + notificationCount?: number; +} + +export interface SearchPanelProps extends ComponentPropsWithoutRef<'aside'> { + /** Grouped RSVP events. Groups are rendered in the order they are supplied. */ + rsvpGroups?: RsvpGroup[]; + /** Subscribed clubs shown in a wrap grid. */ + clubs?: Club[]; +} + +// ── SearchResultList types ──────────────────────────────────────────────────── + +export interface SearchResultItem { + title: string; + orgName: string; + /** + * When true a small neutral indicator badge (star icon) is shown beside + * the org name — matches the Figma "Icon button" element. + */ + hasIndicator?: boolean; + /** + * Optional tag label to the right of the org row. + * Rendered using the shared Tag component (blue colour). + */ + tagLabel?: string; +} + +export interface SearchResultGroup { + /** Time-period label shown above this group of results (e.g. "This week"). */ + period: string; + items: SearchResultItem[]; +} + +export interface SearchResultListProps extends ComponentPropsWithoutRef<'div'> { + groups?: SearchResultGroup[]; + /** When provided, a "Show more" link is rendered at the bottom. */ + showMoreLabel?: string; + onShowMore?: () => void; +} + +// ─── Shared typography class strings ────────────────────────────────────────── + +const BODY2_SEMIBOLD = + 'font-[family-name:var(--font-body)] font-semibold ' + + 'text-[var(--font-size-body2)] leading-[var(--line-height-body2)] ' + + 'tracking-[var(--letter-spacing-body2)]'; + +const BODY2_REGULAR = + 'font-[family-name:var(--font-body)] font-normal ' + + 'text-[var(--font-size-body2)] leading-[var(--line-height-body2)] ' + + 'tracking-[var(--letter-spacing-body2)]'; + +const BODY3 = + 'font-[family-name:var(--font-body)] font-normal ' + + 'text-[var(--font-size-body3)] leading-[var(--line-height-body3)] ' + + 'tracking-[var(--letter-spacing-body3)]'; + +const SECTION_TITLE = + 'font-[family-name:var(--font-body)] font-bold ' + + 'text-[var(--font-size-sub2)] leading-[var(--line-height-sub2)] ' + + 'tracking-[var(--letter-spacing-body1)] ' + + 'text-[var(--color-neutral-900)] whitespace-nowrap'; + +// ─── RsvpEventRow ───────────────────────────────────────────────────────────── + +function RsvpEventRow({ event }: { event: RsvpEvent }) { + return ( + /* + * Interactive states: + * Normal → white bg, no shadow + * Hover → surface-subtle bg (Figma: the same subtle wash used in nav tabs) + * Matches Figma node 510:706 (RSVP card row) + */ +
+ + +
+

+ {event.title} +

+ + {event.description && ( +

+ {event.description} +

+ )} +
+
+ ); +} + +// ─── ClubItem ───────────────────────────────────────────────────────────────── + +function ClubItem({ club }: { club: Club }) { + const count = club.notificationCount ?? 0; + + return ( + /* + * Interactive states: + * Normal → full opacity + * Hover → 80% opacity (subtle fade-down affordance) + */ +
+ {/* + * Avatar + badge wrapper. + * `relative` here (NOT on the overflow-hidden circle) so the badge + * is not clipped. Figma (node 506:9897) places the badge as a sibling + * of the avatar img div, inside the outer relative container. + */} +
+ {/* Circle avatar — overflow-hidden ONLY wraps the image/initials */} +
+ {club.avatarUrl ? ( + {club.name} + ) : ( + /* Initials fallback */ + + {club.name.charAt(0).toUpperCase()} + + )} +
+ + {/* + * Notification badge — sibling of the avatar circle, NOT inside + * overflow-hidden so it renders outside the clipped boundary. + * + * Figma (node 506:10236): + * bg Primary/700, rounded-[100px] (pill), white text, ~10px font + * positioned at top-right corner, slightly overlapping outside the avatar. + * + * top-[-2px] right-[-2px] matches Figma's top: -1px with a 1px margin. + */} + {count > 0 && ( + + {count} + + )} +
+ + {/* Club name label */} + + {club.name} + +
+ ); +} + +// ─── SearchPanel ────────────────────────────────────────────────────────────── + +export function SearchPanel({ + rsvpGroups = [], + clubs = [], + className, + ...rest +}: SearchPanelProps) { + return ( + + ); +} + +// ─── SearchResultList ───────────────────────────────────────────────────────── +// +// The autocomplete result card shown in Figma's "CARDS" section (node 390:207). +// Renders a list of event search results grouped by time period. + +function SearchResultRow({ item }: { item: SearchResultItem }) { + return ( + /* + * Interactive states: + * Normal → transparent bg + * Hover → surface-subtle bg (matches RSVP row hover pattern) + */ +
+ {/* Event title — truncated at one line */} +

+ {item.title} +

+ + {/* Org row: name + optional indicator badge + optional tag */} +
+
+ + {item.orgName} + + + {/* + * Indicator badge — small neutral pill with a star icon. + * Figma bg: #949494 ≈ --color-neutral-600 (consistent with DashboardPost). + * star.svg uses fill="white"/stroke="white" — renders correctly on dark bg. + */} + {item.hasIndicator && ( + + )} +
+ + {item.tagLabel && ( + {item.tagLabel} + )} +
+
+ ); +} + +export function SearchResultList({ + groups = [], + showMoreLabel = 'Show more', + onShowMore, + className, + ...rest +}: SearchResultListProps) { + if (groups.length === 0 && !onShowMore) return null; + + return ( + /* + * Figma: bg white, border --color-border, rounded-[16px], px 16px, py 12px. + * Width is intentionally left to flex/fill — the 254px in Figma is the + * documentation snapshot width, not a constraint. + */ +
+ {groups.map((group) => ( +
+ {/* Period label */} +

+ {group.period} +

+ +
+ {group.items.map((item, i) => ( + + ))} +
+
+ ))} + + {/* + * "Show more" link — Figma: Inter Regular 16px, #0074bc. + * --color-link maps to --color-secondary-600 (#427fb4), the closest token. + */} + {onShowMore && ( + + )} +
+ ); +} diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx new file mode 100644 index 0000000..9310154 --- /dev/null +++ b/src/components/SideBar.tsx @@ -0,0 +1,244 @@ +/** + * SideBar — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Side bar (node 385:495) + * + * Structure (from Figma Frame28, node 386:693): + * ┌──────────────────────┐ + * │ 🔁 Loop │ ← brand wordmark + * │ ─────────────── │ + * │ 🏠 Home │ + * │ 🔖 Bookmarks │ ← primary nav items + * │ ✉ Subscriptions │ + * │ ──────────────── │ ← divider + * │ │ + * │ 👤 Profile │ ← bottom / account nav + * └──────────────────────┘ + * + * Tab states (Figma "Tabs" section, node 388:727): + * • Default: transparent bg, rounded-[16px] (--radius-card), text Neutral/900 + * • Hover: Neutral/100 bg, rounded-[12px] (--space-3), text Neutral/900 + * • Selected: Primary/400 bg, rounded-[12px] (--space-3), text Primary/800, icon orange + * + * Icon colouring: + * SVG assets in src/assets/ use hardcoded stroke colours. CSS filters defined in + * tokens.css (--filter-icon-nav, --filter-icon-nav-selected) normalise them to the + * correct colour for each state. + * + * All colours, spacing, and font values reference CSS custom properties from + * src/styles/tokens.css — nothing is hardcoded. + */ + +import type { ComponentPropsWithoutRef, FC, SVGProps } from 'react'; + +// ── SVG imports — ?react suffix gives us inline React components via SVGR ──── +import HomeIcon from '../assets/home-icon.svg?react'; +import BookmarkIcon from '../assets/bookmark.svg?react'; +import SubscriptionsIcon from '../assets/subscriptions-icon.svg?react'; +import ProfileIcon from '../assets/profile.svg?react'; +import LoopLogo from '../assets/loop_logo.svg?react'; + +// ─── Types ──────────────────────────────────────────────────────────────────── + +export type NavItemId = 'home' | 'bookmarks' | 'subscriptions'; +export type BottomItemId = 'profile'; +export type SideBarItemId = NavItemId | BottomItemId; + +type SvgIcon = FC>; + +interface NavItemDef { + id: NavItemId; + label: string; + Icon: SvgIcon; +} + +const PRIMARY_NAV: NavItemDef[] = [ + { id: 'home', label: 'Home', Icon: HomeIcon }, + { id: 'bookmarks', label: 'Bookmarks', Icon: BookmarkIcon }, + { id: 'subscriptions', label: 'Subscriptions', Icon: SubscriptionsIcon }, +]; + +export interface SideBarProps extends ComponentPropsWithoutRef<'nav'> { + /** + * The currently active nav item id. + * Controls which tab renders in the "selected" state. + */ + activeItem?: SideBarItemId; + /** Called with the item id when any nav or profile tab is clicked. */ + onNavigate?: (id: SideBarItemId) => void; +} + +// ─── SideBarTab sub-component ───────────────────────────────────────────────── + +interface SideBarTabProps { + id: SideBarItemId; + label: string; + Icon: SvgIcon; + isSelected: boolean; + /** 'compact' reduces vertical padding; used for the Profile tab (Figma: py 4px vs 6px). */ + size?: 'default' | 'compact'; + onClick: () => void; +} + +function SideBarTab({ id, label, Icon, isSelected, size = 'default', onClick }: SideBarTabProps) { + const pyClass = size === 'compact' ? 'py-[var(--space-1)]' : 'py-[var(--space-1-5)]'; + + return ( + + ); +} + +// ─── SideBar ────────────────────────────────────────────────────────────────── + +export function SideBar({ + activeItem, + onNavigate, + className, + ...rest +}: SideBarProps) { + const handleClick = (id: SideBarItemId) => { + onNavigate?.(id); + }; + + return ( + + ); +} diff --git a/src/components/Tags.tsx b/src/components/Tags.tsx new file mode 100644 index 0000000..060bf36 --- /dev/null +++ b/src/components/Tags.tsx @@ -0,0 +1,137 @@ +/** + * Tag — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Tags (node 385:205) + * Tag frame: node 385:211 + * + * Figma exposes two props on the component: + * secondary: "Secondary" (neutral/gray palette) | "Primary" (blue palette) + * state: "Default" | "Hover" | "Focus" + * + * In this implementation: + * • `color` maps to Figma's `secondary` — renamed for clarity + * 'neutral' = Figma "Secondary" (Neutral/200 → Neutral/300 on hover) + * 'blue' = Figma "Primary" (Secondary/300 → Secondary/400 on hover) + * • `onDismiss` maps to Figma's "Focus" state — this state shows a × dismiss button. + * Pass a handler to render the dismiss icon; omit it for a plain tag. + * • Hover is handled entirely by CSS (no prop needed). + * + * All colours, spacing, and font values reference CSS custom properties from + * src/styles/tokens.css — nothing is hardcoded. + */ + +import type { ComponentPropsWithoutRef } from 'react'; +import CloseIcon from '../assets/close_tags.svg?react'; + +// ─── Public types ──────────────────────────────────────────────────────────── + +export type TagColor = 'neutral' | 'blue'; + +export interface TagProps extends Omit, 'color'> { + /** + * Colour palette. + * 'neutral' — gray (Figma: Secondary) default bg Neutral/200, hover Neutral/300 + * 'blue' — blue (Figma: Primary) default bg Secondary/300, hover Secondary/400 + * Defaults to 'neutral'. + */ + color?: TagColor; + /** + * When provided the tag renders a dismiss × button. + * Corresponds to Figma's "Focus" state. + * Called when the user clicks the × icon. + */ + onDismiss?: () => void; + /** Accessible label for the dismiss button; defaults to "Remove tag". */ + dismissLabel?: string; +} + +// ─── Style lookup tables ────────────────────────────────────────────────────── +// +// Complete static strings so Tailwind's content scanner detects every class. + +const BASE_CLASSES = + 'inline-flex items-center gap-[var(--space-2)] ' + + 'px-[var(--space-3)] py-[var(--space-0-5)] ' + + 'rounded-[var(--radius-input)] ' + + 'font-[family-name:var(--font-body)] font-medium ' + + 'text-[var(--font-size-body2)] leading-[var(--space-6)] ' + + 'tracking-[var(--letter-spacing-body2)] ' + + 'text-[var(--color-neutral-700)] ' + + 'whitespace-nowrap select-none transition-colors duration-150'; + +/** + * Background colours per colour variant. + * + * Figma values (confirmed from get_design_context): + * neutral default → Neutral/200 #edeff1 + * neutral hover → Neutral/300 #dee2e6 + * blue default → Secondary/300 (Blue/300) #eaf3fc + * blue hover → Secondary/400 (Blue/400) #acd6fb + * + * Note: Figma's "Focus" (dismissible) state uses the same background as + * Default for blue and the same background as Hover for neutral. Since the + * close button is structurally present (controlled by `onDismiss`), the + * background transition is handled naturally by CSS hover without special-casing. + */ +const COLOR_CLASSES: Record = { + neutral: + 'bg-[var(--color-neutral-200)] hover:bg-[var(--color-neutral-300)]', + blue: + 'bg-[var(--color-secondary-300)] hover:bg-[var(--color-secondary-400)]', +}; + +// ─── Component ─────────────────────────────────────────────────────────────── + +export function Tag({ + color = 'neutral', + onDismiss, + dismissLabel = 'Remove tag', + className, + children, + ...rest +}: TagProps) { + return ( + + {children} + + {onDismiss && ( + /* + * group on the button so responds to the button's hover state. + * close_tags.svg has hardcoded stroke="#ADB5BD"; brightness(0) first normalises + * it to black, then --filter-icon-close-default → ~Neutral/700 (#495057). + * On hover --filter-icon-close-hover keeps it at full black (#000000). + * Source: Figma Icons section — Close icon Default / hover states (node 493:1041) + */ + + )} + + ); +} diff --git a/src/components/Toggle.tsx b/src/components/Toggle.tsx new file mode 100644 index 0000000..ea61d28 --- /dev/null +++ b/src/components/Toggle.tsx @@ -0,0 +1,148 @@ +/** + * Toggle — Loop Design System + * + * Source: Figma "Incubator-design-file" › Design System › Buttons › Toggle (node 381:1685) + * + * A segmented pill control where exactly one option is active at a time. + * Used for switching between top-level views (e.g. "Feed" / "Bookmarks"). + * + * Two size variants (from Figma): + * compact — Figma "Extension" toggle (node 382:519) + * gap 8px between options, py 8px per option — more touch-friendly + * default — Figma "Desktop" toggle (node 382:544) + * gap 32px between options, py 6px per option — wider horizontal layout + * + * Active tab: Primary/700 bg, Shadow 1, --radius-button (16px), white SemiBold text + * Inactive tab: transparent bg, --color-text-secondary, rounded-full (pill ends) + * + * This is always a controlled component — supply `value` and `onChange`. + * + * All colours, spacing, and font values reference CSS custom properties from + * src/styles/tokens.css — nothing is hardcoded. + */ + +import type { ComponentPropsWithoutRef } from 'react'; + +// ─── Public types ───────────────────────────────────────────────────────────── + +export type ToggleSize = 'compact' | 'default'; + +export interface ToggleOption { + /** The value identifier for this option. */ + value: string; + /** Display label rendered inside the tab. */ + label: string; +} + +export interface ToggleProps extends Omit, 'onChange'> { + /** + * Ordered list of options to render as tabs. + * Accepts either plain strings (used as both value and label) + * or explicit { value, label } objects. + */ + options: (string | ToggleOption)[]; + /** Currently active value (controlled). */ + value: string; + /** Called with the new value when a tab is clicked. */ + onChange: (value: string) => void; + /** + * Size variant: + * compact — Figma "Extension" toggle: gap 8px, py 8px per option + * default — Figma "Desktop" toggle: gap 32px, py 6px per option + * Defaults to 'default'. + */ + size?: ToggleSize; +} + +// ─── Style constants ────────────────────────────────────────────────────────── + +const CONTAINER_BASE = + 'inline-flex items-center ' + + 'p-[var(--space-1-5)] ' + + 'bg-[var(--color-surface)] ' + + 'border border-[var(--color-border)] ' + + 'rounded-[var(--radius-toggle)]'; + +const CONTAINER_SIZE: Record = { + compact: 'gap-[var(--space-2)]', /* 8px — Figma Extension toggle */ + default: 'gap-[var(--space-8)]', /* 32px — Figma Desktop toggle */ +}; + +const OPTION_BASE = + 'flex-1 flex items-center justify-center ' + + 'px-[var(--space-5)] ' + + 'font-[family-name:var(--font-body)] font-semibold ' + + 'text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] ' + + 'tracking-[var(--letter-spacing-body2)] ' + + 'whitespace-nowrap select-none cursor-pointer ' + + 'transition-all duration-150'; + +const OPTION_SIZE_PY: Record = { + compact: 'py-[var(--space-2)]', /* 8px — Figma Extension */ + default: 'py-[var(--space-1-5)]', /* 6px — Figma Desktop */ +}; + +const OPTION_ACTIVE = + 'rounded-[var(--radius-button)] ' + + 'bg-[var(--color-primary-700)] ' + + 'text-[var(--color-white)] ' + + 'shadow-[var(--shadow-1)]'; + +const OPTION_INACTIVE = + 'rounded-full ' + + 'text-[var(--color-text-secondary)] ' + + 'hover:text-[var(--color-text-default)]'; + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +function normalise(opt: string | ToggleOption): ToggleOption { + return typeof opt === 'string' ? { value: opt, label: opt } : opt; +} + +// ─── Component ──────────────────────────────────────────────────────────────── + +export function Toggle({ + options, + value, + onChange, + size = 'default', + className, + ...rest +}: ToggleProps) { + const normalised = options.map(normalise); + + return ( +
+ {normalised.map((opt) => { + const isActive = opt.value === value; + return ( + + ); + })} +
+ ); +} diff --git a/src/index.css b/src/index.css index 08a3ac9..bb1217f 100644 --- a/src/index.css +++ b/src/index.css @@ -1,68 +1,27 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; +@import 'tailwindcss'; +@import './styles/tokens.css'; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; +/* ─── Base reset / global defaults ──────────────────────────────────────── */ - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; +*, +*::before, +*::after { + box-sizing: border-box; } body { margin: 0; - display: flex; - place-items: center; - min-width: 320px; min-height: 100vh; + font-family: var(--font-body); + font-size: var(--font-size-body1); + line-height: var(--line-height-body1); + color: var(--color-text-default); + background-color: var(--color-surface); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; } -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } +#root { + min-height: 100vh; } diff --git a/src/pages/DesignSystem.tsx b/src/pages/DesignSystem.tsx new file mode 100644 index 0000000..bcb2595 --- /dev/null +++ b/src/pages/DesignSystem.tsx @@ -0,0 +1,757 @@ +/** + * DesignSystem — dev review page + * + * Renders every design token and every component in the Loop design system. + * All styling uses Tailwind classes backed by tokens.css — nothing hardcoded. + */ + +import { useState } from 'react'; + +import { Button } from '../components/Button'; +import { Toggle } from '../components/Toggle'; +import { Tag } from '../components/Tags'; +import { SearchBar } from '../components/SearchBar'; +import { Avatar } from '../components/Avatar'; +import { SideBar } from '../components/SideBar'; +import type { SideBarItemId } from '../components/SideBar'; +import { LoopLogo } from '../components/Logo'; + +import { DashboardEventCard } from '../components/Cards/DashboardEventCard'; +import { DashboardPost } from '../components/Cards/DashboardPost'; +import { LoopSummary } from '../components/Cards/LoopSummary'; +import { + DateBadge, + ExtensionEventRow, + ExtensionEventCard, +} from '../components/Cards/ExtensionEventCard'; + +import { SearchPanel, SearchResultList } from '../components/SearchPanel'; + +// ─── Page-internal layout helpers ───────────────────────────────────────────── +// These are NOT new design-system components — they are private page layout +// utilities used only within this file. + +function DS_Section({ + id, + title, + children, +}: { + id: string; + title: string; + children: React.ReactNode; +}) { + return ( +
+

+ {title} +

+ {children} +
+ ); +} + +function DS_Label({ children }: { children: React.ReactNode }) { + return ( +

+ {children} +

+ ); +} + +function DS_Row({ + label, + children, +}: { + label: string; + children: React.ReactNode; +}) { + return ( +
+ {label} +
+ {children} +
+
+ ); +} + +// ─── Color swatch data ───────────────────────────────────────────────────────── +// hex values are displayed as text metadata — the swatch bg uses the CSS var. + +type Swatch = { token: string; hex: string }; +type SwatchGroup = { group: string; swatches: Swatch[] }; + +const COLOR_GROUPS: SwatchGroup[] = [ + { + group: 'Primary', + swatches: [ + { token: '--color-primary-900', hex: '#592100' }, + { token: '--color-primary-800', hex: '#a74409' }, + { token: '--color-primary-700', hex: '#eb7128' }, + { token: '--color-primary-hover', hex: '#d35910' }, + { token: '--color-primary-600', hex: '#ffa26b' }, + { token: '--color-primary-500', hex: '#ffcaaa' }, + { token: '--color-primary-400', hex: '#fff2ea' }, + ], + }, + { + group: 'Secondary', + swatches: [ + { token: '--color-secondary-900', hex: '#13324e' }, + { token: '--color-secondary-700', hex: '#285781' }, + { token: '--color-secondary-600', hex: '#427fb4' }, + { token: '--color-secondary-500', hex: '#63a9e7' }, + { token: '--color-secondary-400', hex: '#acd6fb' }, + { token: '--color-secondary-300', hex: '#eaf3fc' }, + ], + }, + { + group: 'Neutral', + swatches: [ + { token: '--color-neutral-900', hex: '#212529' }, + { token: '--color-neutral-700', hex: '#495057' }, + { token: '--color-neutral-600', hex: '#616972' }, + { token: '--color-neutral-500', hex: '#adb5bd' }, + { token: '--color-neutral-400', hex: '#ced4da' }, + { token: '--color-neutral-300', hex: '#dee2e6' }, + { token: '--color-neutral-200', hex: '#edeff1' }, + { token: '--color-neutral-100', hex: '#f8f9fa' }, + { token: '--color-black', hex: '#000000' }, + { token: '--color-white', hex: '#ffffff' }, + ], + }, + { + group: 'Semantic aliases', + swatches: [ + { token: '--color-brand', hex: '→ primary-700' }, + { token: '--color-brand-light', hex: '→ primary-400' }, + { token: '--color-brand-dark', hex: '→ primary-900' }, + { token: '--color-text-default', hex: '→ neutral-900' }, + { token: '--color-text-secondary', hex: '→ neutral-600' }, + { token: '--color-text-muted', hex: '→ neutral-500' }, + { token: '--color-text-inverse', hex: '→ white' }, + { token: '--color-surface', hex: '→ white' }, + { token: '--color-surface-subtle', hex: '→ neutral-100' }, + { token: '--color-surface-raised', hex: '→ neutral-200' }, + { token: '--color-border', hex: '→ neutral-300' }, + { token: '--color-border-strong', hex: '→ neutral-400' }, + { token: '--color-link', hex: '→ secondary-600' }, + ], + }, +]; + +// ─── Typography data ─────────────────────────────────────────────────────────── + +const TYPE_STYLES = [ + { + label: 'H1 — 60 px / Bold', + className: + 'font-[family-name:var(--font-heading)] font-bold ' + + 'text-[length:var(--font-size-h1)] leading-[var(--line-height-h1)] ' + + 'tracking-[var(--letter-spacing-h1)] text-[var(--color-text-default)]', + sample: 'Heading One', + }, + { + label: 'H2 — 48 px / Bold', + className: + 'font-[family-name:var(--font-heading)] font-bold ' + + 'text-[length:var(--font-size-h2)] leading-[var(--line-height-h2)] ' + + 'tracking-[var(--letter-spacing-h2)] text-[var(--color-text-default)]', + sample: 'Heading Two', + }, + { + label: 'H3 — 40 px / Bold', + className: + 'font-[family-name:var(--font-heading)] font-bold ' + + 'text-[length:var(--font-size-h3)] leading-[var(--line-height-h3)] ' + + 'tracking-[var(--letter-spacing-h3)] text-[var(--color-text-default)]', + sample: 'Heading Three', + }, + { + label: 'Sub 1 — 28 px / SemiBold', + className: + 'font-[family-name:var(--font-body)] font-semibold ' + + 'text-[length:var(--font-size-sub1)] leading-[var(--line-height-sub1)] ' + + 'tracking-[var(--letter-spacing-sub1)] text-[var(--color-text-default)]', + sample: 'Subtitle One', + }, + { + label: 'Sub 2 — 18 px / SemiBold', + className: + 'font-[family-name:var(--font-body)] font-semibold ' + + 'text-[length:var(--font-size-sub2)] leading-[var(--line-height-sub2)] ' + + 'tracking-[var(--letter-spacing-sub2)] text-[var(--color-text-default)]', + sample: 'Subtitle Two', + }, + { + label: 'Body 1 — 18 px / Regular', + className: + 'font-[family-name:var(--font-body)] font-normal ' + + 'text-[length:var(--font-size-body1)] leading-[var(--line-height-body1)] ' + + 'tracking-[var(--letter-spacing-body1)] text-[var(--color-text-default)]', + sample: 'Body text at 18 px. Used for prominent paragraph copy.', + }, + { + label: 'Body 2 — 14 px / Regular', + className: + 'font-[family-name:var(--font-body)] font-normal ' + + 'text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] ' + + 'tracking-[var(--letter-spacing-body2)] text-[var(--color-text-default)]', + sample: 'Body text at 14 px. The standard UI copy size across cards, inputs, and labels.', + }, + { + label: 'Body 3 — 12 px / Regular', + className: + 'font-[family-name:var(--font-body)] font-normal ' + + 'text-[length:var(--font-size-body3)] leading-[var(--line-height-body3)] ' + + 'tracking-[var(--letter-spacing-body3)] text-[var(--color-text-secondary)]', + sample: 'Body text at 12 px. Used for captions, timestamps, and metadata.', + }, + { + label: 'Brand wordmark (compact) — Manrope Bold 32 px', + className: + 'font-[family-name:var(--font-brand)] font-bold ' + + 'text-[length:var(--font-size-wordmark)] leading-none ' + + 'text-[var(--color-text-default)]', + sample: 'Loop', + }, + { + label: 'Brand wordmark (display) — Manrope Bold 55 px', + className: + 'font-[family-name:var(--font-brand)] font-bold ' + + 'text-[length:var(--font-size-wordmark-display)] leading-none ' + + 'text-[var(--color-text-default)]', + sample: 'Loop', + }, +]; + +// ─── Sample data for components ──────────────────────────────────────────────── + +const SAMPLE_TAGS = [ + { label: 'CS', color: 'neutral' as const }, + { label: 'Open to all', color: 'blue' as const }, +]; + +const SAMPLE_EVENT_PROPS = { + title: 'Intro to Machine Learning Workshop', + datetime: 'Fri, Mar 14 · 6:00 – 8:00 PM', + location: 'Gates Hall 122', + description: + 'Join us for a hands-on intro to ML covering linear regression, decision trees, and neural networks. No prior experience required — laptops welcome.', + tags: SAMPLE_TAGS, +}; + +const SAMPLE_ORGS = [ + { name: 'CDS', avatarUrl: undefined }, + { name: 'Cornell AI', avatarUrl: undefined }, +]; + +const SAMPLE_EXT_EVENTS = [ + { + thumbnailVariant: 'date' as const, + day: 14, + month: 'Mar', + title: 'ML Workshop', + description: 'Gates Hall 122 · 6 – 8 PM', + bookmarked: false, + }, + { + thumbnailVariant: 'date' as const, + day: 21, + month: 'Mar', + title: 'Cornell Hackathon Kickoff', + description: 'Duffield Atrium · 5 PM', + bookmarked: true, + }, + { + thumbnailVariant: 'news' as const, + title: 'Spring 2025 Newsletter', + description: 'CDS · published today', + bookmarked: false, + }, +]; + +const SAMPLE_RSVP_GROUPS = [ + { + period: 'Today', + events: [ + { day: 14, month: 'Mar', title: 'ML Workshop', description: 'Gates Hall 122 · 6 PM' }, + ], + }, + { + period: 'This Week', + events: [ + { day: 16, month: 'Mar', title: 'Hackathon Kickoff', description: 'Duffield · 5 PM' }, + { day: 17, month: 'Mar', title: 'Startup Pitch Night', description: 'Statler · 7 PM' }, + ], + }, +]; + +const SAMPLE_CLUBS = [ + { id: 'cds', name: 'CDS', notificationCount: 3 }, + { id: 'cai', name: 'Cornell AI', notificationCount: 0 }, + { id: 'dtx', name: 'DTX' }, + { id: 'cuse', name: 'CUSE' }, +]; + +const SAMPLE_RESULT_GROUPS = [ + { + period: 'Top Results', + items: [ + { title: 'ML Workshop', orgName: 'CDS', hasIndicator: true, tag: { label: 'CS', color: 'neutral' as const } }, + { title: 'Hackathon Kickoff', orgName: 'Cornell AI', hasIndicator: false, tag: { label: 'Open to all', color: 'blue' as const } }, + { title: 'Pitch Night', orgName: 'eLab', hasIndicator: false }, + ], + }, +]; + +// ─── Page ────────────────────────────────────────────────────────────────────── + +export default function DesignSystem() { + const [sidebarActive, setSidebarActive] = useState('home'); + const [bookmarked, setBookmarked] = useState(false); + const [toggleCompact, setToggleCompact] = useState('Feed'); + const [toggleDefault, setToggleDefault] = useState('Feed'); + + return ( +
+ {/* ── Page title ── */} +
+

+ Loop Design System +

+

+ Testing Page +

+
+ + {/* ════════════════════════════════════════ + 1. COLORS + ════════════════════════════════════════ */} + + {COLOR_GROUPS.map(({ group, swatches }) => ( +
+ {group} +
+ {swatches.map(({ token, hex }) => ( +
+
+

+ {token} +

+

+ {hex} +

+
+ ))} +
+
+ ))} + + + {/* ════════════════════════════════════════ + 2. TYPOGRAPHY + ════════════════════════════════════════ */} + + {TYPE_STYLES.map(({ label, className, sample }) => ( +
+ {label} +

{sample}

+
+ ))} +
+ + {/* ════════════════════════════════════════ + 3. LOGO + ════════════════════════════════════════ */} + + + {/* ════════════════════════════════════════ + 4. BUTTON + ════════════════════════════════════════ */} + + + + + + + +
+ +
+
+ + + + + + + +
+ +
+
+ + + + + + +
+ + {/* ════════════════════════════════════════ + 5. TOGGLE + ════════════════════════════════════════ */} + + + + + + + + + + + + + + + {/* ════════════════════════════════════════ + 6. TAG + ════════════════════════════════════════ */} + + + Computer Science + Open to all + + + + Cornell Daily Sun + Free food + + + + {}}>Dismissible neutral + {}}>Dismissible blue + + + + {/* ════════════════════════════════════════ + 7. SEARCH BAR + ════════════════════════════════════════ */} + + +
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+
+ + {/* ════════════════════════════════════════ + 8. AVATAR + ════════════════════════════════════════ */} + + + + + + + + + + + + + + + + + + + {/* ════════════════════════════════════════ + 9. SIDE BAR + ════════════════════════════════════════ */} + + + Active item: {sidebarActive} — click tabs to change state + +
+ +
+
+ + {/* ════════════════════════════════════════ + 10. CARDS — DashboardEventCard + ════════════════════════════════════════ */} + + +
+ +
+
+ + +
+ setBookmarked((b) => !b)} + /> +
+
+
+ + {/* ════════════════════════════════════════ + 11. CARDS — DashboardPost + ════════════════════════════════════════ */} + + +
+ +
+
+
+ + {/* ════════════════════════════════════════ + 12. CARDS — Loop Summary + ════════════════════════════════════════ */} + + +
+ +
+
+
+ + {/* ════════════════════════════════════════ + 13. CARDS — Extension Event Card + ════════════════════════════════════════ */} + + + + + + + + + + + +
+ + + +
+
+ + +
+ +
+
+
+ + {/* ════════════════════════════════════════ + 14. SEARCH PANEL + ════════════════════════════════════════ */} + + + + + + + {/* ════════════════════════════════════════ + 15. SEARCH RESULT LIST + ════════════════════════════════════════ */} + + +
+ {}} + /> +
+
+
+
+ ); +} diff --git a/src/styles/tokens.css b/src/styles/tokens.css new file mode 100644 index 0000000..2c6da58 --- /dev/null +++ b/src/styles/tokens.css @@ -0,0 +1,197 @@ +/* + * Loop Design System — CSS Custom Properties + * Source: Figma "Incubator-design-file" (Design System page, node 378:109) + * Sections extracted: Colors, Typography, Effects (Shadows + Border Radius) + */ + +:root { + /* ───────────────────────────────────────────── + * PRIMARY PALETTE (warm orange-brown brand) + * ───────────────────────────────────────────── */ + --color-primary-900: #592100; + --color-primary-800: #a74409; + --color-primary-700: #eb7128; /* brand default */ + --color-primary-hover: #d35910; /* interactive hover — between 700 and 800; from Figma button hover state */ + --color-primary-600: #ffa26b; + --color-primary-500: #ffcaaa; + --color-primary-400: #fff2ea; + + /* ───────────────────────────────────────────── + * SECONDARY PALETTE (cool blue) + * ───────────────────────────────────────────── */ + --color-secondary-900: #13324e; + --color-secondary-700: #285781; + --color-secondary-600: #427fb4; + --color-secondary-500: #63a9e7; + --color-secondary-400: #acd6fb; + --color-secondary-300: #eaf3fc; + + /* ───────────────────────────────────────────── + * NEUTRAL PALETTE + * ───────────────────────────────────────────── */ + --color-neutral-900: #212529; + --color-neutral-700: #495057; + --color-neutral-600: #616972; + --color-neutral-500: #adb5bd; + --color-neutral-400: #ced4da; + --color-neutral-300: #dee2e6; + --color-neutral-200: #edeff1; + --color-neutral-100: #f8f9fa; + --color-black: #000000; + --color-white: #ffffff; + + /* ───────────────────────────────────────────── + * TRANSPARENCY SCALE + * Apply these opacity multipliers to any palette colour. + * Use as: rgba(var(--color-neutral-900-rgb) / 0.8) or pair with + * the opacity utilities in your styling system. + * ───────────────────────────────────────────── */ + --opacity-100: 1; + --opacity-80: 0.8; + --opacity-40: 0.4; + --opacity-8: 0.08; + --opacity-4: 0.04; + + /* ───────────────────────────────────────────── + * SEMANTIC / ALIAS COLOUR TOKENS + * Map high-level intent to palette values. + * ───────────────────────────────────────────── */ + --color-brand: var(--color-primary-700); + --color-brand-light: var(--color-primary-400); + --color-brand-dark: var(--color-primary-900); + + --color-text-default: var(--color-neutral-900); + --color-text-secondary: var(--color-neutral-600); + --color-text-muted: var(--color-neutral-500); + --color-text-inverse: var(--color-white); + + --color-surface: var(--color-white); + --color-surface-subtle: var(--color-neutral-100); + --color-surface-raised: var(--color-neutral-200); + + --color-border: var(--color-neutral-300); + --color-border-strong: var(--color-neutral-400); + + /* ───────────────────────────────────────────── + * TYPOGRAPHY — FONT FAMILIES + * Primary typeface: DM Sans (headings + body copy) + * Secondary typeface: Inter (UI labels, metadata) + * Brand wordmark: Manrope (sidebar logo) + * ───────────────────────────────────────────── */ + --font-heading: 'DM Sans', sans-serif; + --font-body: 'DM Sans', sans-serif; + --font-brand: 'Manrope', sans-serif; + + /* ───────────────────────────────────────────── + * TYPOGRAPHY — FONT SIZES (px) + * ───────────────────────────────────────────── */ + --font-size-wordmark: 2rem; /* 32px — compact wordmark (sidebar "Loop" text); from Figma 31.9px */ + --font-size-wordmark-display: 3.4375rem; /* 55px — display wordmark (Logo section); from Figma 55.07px */ + --font-size-h1: 3.75rem; /* 60px */ + --font-size-h2: 3rem; /* 48px */ + --font-size-h3: 2.5rem; /* 40px */ + --font-size-sub1: 1.75rem; /* 28px */ + --font-size-sub2: 1.125rem; /* 18px */ + --font-size-body1: 1.125rem; /* 18px */ + --font-size-body2: 0.875rem; /* 14px */ + --font-size-body3: 0.75rem; /* 12px */ + + /* ───────────────────────────────────────────── + * TYPOGRAPHY — LINE HEIGHTS + * ───────────────────────────────────────────── */ + --line-height-h1: 4.5rem; /* 72px */ + --line-height-h2: 4rem; /* 64px */ + --line-height-h3: 3rem; /* 48px */ + --line-height-sub1: normal; + --line-height-sub2: 1.75rem; /* 28px */ + --line-height-body1: 1.75rem; /* 28px */ + --line-height-body2: 1.2625rem; /* 20.2px */ + --line-height-body3: normal; + + /* ───────────────────────────────────────────── + * TYPOGRAPHY — LETTER SPACING + * ───────────────────────────────────────────── */ + --letter-spacing-h1: -0.9px; + --letter-spacing-h2: -0.5px; + --letter-spacing-h3: -1px; + --letter-spacing-sub1: -1px; + --letter-spacing-sub2: -1px; + --letter-spacing-body1: -0.5px; + --letter-spacing-body2: -0.5px; + --letter-spacing-body3: -0.5px; + + /* ───────────────────────────────────────────── + * TYPOGRAPHY — FONT WEIGHTS + * ───────────────────────────────────────────── */ + --font-weight-regular: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + + /* ───────────────────────────────────────────── + * SHADOWS + * Shadow 1 — stronger, for elevated cards/modals + * Shadow 2 — subtle, for resting surfaces/inputs + * ───────────────────────────────────────────── */ + --shadow-1: 0px 3px 6px 0px rgba(33, 37, 41, 0.15), + 0px 0px 1px 0px rgba(33, 37, 41, 0.32); + --shadow-2: 0px 2px 2px 0px rgba(33, 37, 41, 0.06), + 0px 0px 1px 0px rgba(33, 37, 41, 0.08); + --shadow-primary-glow: 0px 0px 8px 0px rgba(211, 89, 16, 0.6); /* primary button press/hover glow; from Figma */ + + /* ───────────────────────────────────────────── + * BORDER RADIUS + * ───────────────────────────────────────────── */ + --radius-card: 16px; /* cards, panels */ + --radius-button: 1rem; /* 16px — button border-radius; from Figma buttons (node 381:1681) */ + --radius-toggle: 1.25rem; /* 20px — toggle pill container; from Figma toggle (node 382:519) */ + --radius-input: 8px; /* inputs */ + + /* ───────────────────────────────────────────── + * SEMANTIC LINK COLOUR + * Source: Figma "Show more" link in Search Panel CARDS section. + * #0074bc is closest to --color-secondary-600 (#427fb4) in the token scale. + * ───────────────────────────────────────────── */ + --color-link: var(--color-secondary-600); + + /* ───────────────────────────────────────────── + * LAYOUT + * ───────────────────────────────────────────── */ + --sidebar-width: 215px; /* from Figma "Side bar" component */ + --search-panel-width: 334px; /* from Figma "Search Panel" component (node 390:526) */ + + /* Component-specific fixed sizes not on the 4px grid */ + --size-date-badge: 3.4375rem; /* 55px — calendar date widget in RSVP items */ + --size-club-avatar: 3.5rem; /* 56px ≈ Figma 56.7px — club logo circle */ + --font-size-badge: 0.625rem; /* 10px — notification count badge (Figma node 506:10236) */ + + /* ───────────────────────────────────────────── + * ICON FILTERS + * Applied to SVG icons to recolor them via CSS. + * --filter-icon-nav: normalises any SVG stroke to ~Neutral/900 (#212529) + * --filter-icon-nav-selected: recolours to ~Primary/800 (#a74409) for active nav items + * ───────────────────────────────────────────── */ + --filter-icon-nav: + brightness(0) opacity(0.87); + --filter-icon-nav-selected: + brightness(0) saturate(100%) invert(28%) sepia(84%) + saturate(1180%) hue-rotate(8deg) brightness(99%) contrast(108%); + + /* ───────────────────────────────────────────── + * SPACING SCALE (4px base grid) + * Observed from Figma layout measurements. + * ───────────────────────────────────────────── */ + --space-0-5: 0.125rem; /* 2px — tag vertical padding; from Figma Tag py */ + --space-1: 0.25rem; /* 4px */ + --space-1-5: 0.375rem; /* 6px — button vertical padding (sm/md); from Figma button py */ + --space-2: 0.5rem; /* 8px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-8: 2rem; /* 32px */ + --space-9: 2.25rem; /* 36px */ + --space-10: 2.5rem; /* 40px */ + --space-12: 3rem; /* 48px */ + --space-16: 4rem; /* 64px */ +} diff --git a/tsconfig.app.json b/tsconfig.app.json index a9b5a59..18bdc9d 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -5,7 +5,7 @@ "useDefineForClassFields": true, "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", - "types": ["vite/client"], + "types": ["vite/client", "vite-plugin-svgr/client"], "skipLibCheck": true, /* Bundler mode */ diff --git a/vite.config.ts b/vite.config.ts index 8b0f57b..c9fbebb 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,13 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' +import svgr from 'vite-plugin-svgr' // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + tailwindcss(), + svgr(), + ], }) From 85e16a88babaa17653be5dd174d73a2ce56677c6 Mon Sep 17 00:00:00 2001 From: Annie Chen Date: Tue, 14 Apr 2026 02:27:13 -0400 Subject: [PATCH 06/25] restructure repo to accomodate monorepo structure - accomodate shared design system components - have dashboard + extension split --- .../dashboard/convex}/_generated/api.d.ts | 0 .../dashboard/convex}/_generated/api.js | 0 .../convex}/_generated/dataModel.d.ts | 0 .../dashboard/convex}/_generated/server.d.ts | 0 .../dashboard/convex}/_generated/server.js | 0 .../dashboard/convex}/auth.config.ts | 0 {convex => apps/dashboard/convex}/auth.ts | 0 {convex => apps/dashboard/convex}/http.ts | 0 {convex => apps/dashboard/convex}/schema.ts | 0 index.html => apps/dashboard/index.html | 0 apps/dashboard/package.json | 38 +++++++++++++++++++ {public => apps/dashboard/public}/vite.svg | 0 {src => apps/dashboard/src}/App.css | 0 {src => apps/dashboard/src}/App.tsx | 0 {src => apps/dashboard/src}/Auth.tsx | 0 {src => apps/dashboard/src}/index.css | 2 +- {src => apps/dashboard/src}/main.tsx | 0 .../dashboard/src}/pages/DesignSystem.tsx | 29 +++++++------- .../dashboard/tsconfig.app.json | 8 +++- tsconfig.json => apps/dashboard/tsconfig.json | 0 .../dashboard/tsconfig.node.json | 0 .../dashboard/vite.config.ts | 10 +++++ bun.lock | 22 +++++++++++ package.json | 38 ++++--------------- shared/ui/package.json | 19 ++++++++++ .../ui/src}/assets/bookmark-filled.svg | 0 {src => shared/ui/src}/assets/bookmark.svg | 0 {src => shared/ui/src}/assets/calendar.svg | 0 .../ui/src}/assets/close_search.svg | 0 {src => shared/ui/src}/assets/close_tags.svg | 0 .../ui/src}/assets/external-link.svg | 0 {src => shared/ui/src}/assets/home-icon.svg | 0 .../ui/src}/assets/location-pin.svg | 0 .../ui/src}/assets/loop-logo-mixed.svg | 0 {src => shared/ui/src}/assets/loop_logo.svg | 0 .../ui/src}/assets/newspaper-outline.svg | 0 {src => shared/ui/src}/assets/profile.svg | 0 {src => shared/ui/src}/assets/react.svg | 0 {src => shared/ui/src}/assets/search_icon.svg | 0 {src => shared/ui/src}/assets/star.svg | 0 .../ui/src}/assets/subscriptions-icon.svg | 0 {src => shared/ui/src}/components/Avatar.tsx | 0 {src => shared/ui/src}/components/Button.tsx | 0 .../components/Cards/DashboardEventCard.tsx | 0 .../src}/components/Cards/DashboardPost.tsx | 0 .../components/Cards/ExtensionEventCard.tsx | 0 .../ui/src}/components/Cards/LoopSummary.tsx | 0 .../ui/src}/components/Cards/index.ts | 0 .../ui/src}/components/DateBadge.tsx | 0 {src => shared/ui/src}/components/Logo.tsx | 0 .../ui/src}/components/SearchBar.tsx | 0 .../ui/src}/components/SearchPanel.tsx | 0 {src => shared/ui/src}/components/SideBar.tsx | 0 {src => shared/ui/src}/components/Tags.tsx | 0 {src => shared/ui/src}/components/Toggle.tsx | 0 shared/ui/src/index.ts | 13 +++++++ {src => shared/ui/src}/styles/tokens.css | 0 shared/ui/tsconfig.json | 26 +++++++++++++ 58 files changed, 158 insertions(+), 47 deletions(-) rename {convex => apps/dashboard/convex}/_generated/api.d.ts (100%) rename {convex => apps/dashboard/convex}/_generated/api.js (100%) rename {convex => apps/dashboard/convex}/_generated/dataModel.d.ts (100%) rename {convex => apps/dashboard/convex}/_generated/server.d.ts (100%) rename {convex => apps/dashboard/convex}/_generated/server.js (100%) rename {convex => apps/dashboard/convex}/auth.config.ts (100%) rename {convex => apps/dashboard/convex}/auth.ts (100%) rename {convex => apps/dashboard/convex}/http.ts (100%) rename {convex => apps/dashboard/convex}/schema.ts (100%) rename index.html => apps/dashboard/index.html (100%) create mode 100644 apps/dashboard/package.json rename {public => apps/dashboard/public}/vite.svg (100%) rename {src => apps/dashboard/src}/App.css (100%) rename {src => apps/dashboard/src}/App.tsx (100%) rename {src => apps/dashboard/src}/Auth.tsx (100%) rename {src => apps/dashboard/src}/index.css (94%) rename {src => apps/dashboard/src}/main.tsx (100%) rename {src => apps/dashboard/src}/pages/DesignSystem.tsx (97%) rename tsconfig.app.json => apps/dashboard/tsconfig.app.json (79%) rename tsconfig.json => apps/dashboard/tsconfig.json (100%) rename tsconfig.node.json => apps/dashboard/tsconfig.node.json (100%) rename vite.config.ts => apps/dashboard/vite.config.ts (51%) create mode 100644 shared/ui/package.json rename {src => shared/ui/src}/assets/bookmark-filled.svg (100%) rename {src => shared/ui/src}/assets/bookmark.svg (100%) rename {src => shared/ui/src}/assets/calendar.svg (100%) rename {src => shared/ui/src}/assets/close_search.svg (100%) rename {src => shared/ui/src}/assets/close_tags.svg (100%) rename {src => shared/ui/src}/assets/external-link.svg (100%) rename {src => shared/ui/src}/assets/home-icon.svg (100%) rename {src => shared/ui/src}/assets/location-pin.svg (100%) rename {src => shared/ui/src}/assets/loop-logo-mixed.svg (100%) rename {src => shared/ui/src}/assets/loop_logo.svg (100%) rename {src => shared/ui/src}/assets/newspaper-outline.svg (100%) rename {src => shared/ui/src}/assets/profile.svg (100%) rename {src => shared/ui/src}/assets/react.svg (100%) rename {src => shared/ui/src}/assets/search_icon.svg (100%) rename {src => shared/ui/src}/assets/star.svg (100%) rename {src => shared/ui/src}/assets/subscriptions-icon.svg (100%) rename {src => shared/ui/src}/components/Avatar.tsx (100%) rename {src => shared/ui/src}/components/Button.tsx (100%) rename {src => shared/ui/src}/components/Cards/DashboardEventCard.tsx (100%) rename {src => shared/ui/src}/components/Cards/DashboardPost.tsx (100%) rename {src => shared/ui/src}/components/Cards/ExtensionEventCard.tsx (100%) rename {src => shared/ui/src}/components/Cards/LoopSummary.tsx (100%) rename {src => shared/ui/src}/components/Cards/index.ts (100%) rename {src => shared/ui/src}/components/DateBadge.tsx (100%) rename {src => shared/ui/src}/components/Logo.tsx (100%) rename {src => shared/ui/src}/components/SearchBar.tsx (100%) rename {src => shared/ui/src}/components/SearchPanel.tsx (100%) rename {src => shared/ui/src}/components/SideBar.tsx (100%) rename {src => shared/ui/src}/components/Tags.tsx (100%) rename {src => shared/ui/src}/components/Toggle.tsx (100%) create mode 100644 shared/ui/src/index.ts rename {src => shared/ui/src}/styles/tokens.css (100%) create mode 100644 shared/ui/tsconfig.json diff --git a/convex/_generated/api.d.ts b/apps/dashboard/convex/_generated/api.d.ts similarity index 100% rename from convex/_generated/api.d.ts rename to apps/dashboard/convex/_generated/api.d.ts diff --git a/convex/_generated/api.js b/apps/dashboard/convex/_generated/api.js similarity index 100% rename from convex/_generated/api.js rename to apps/dashboard/convex/_generated/api.js diff --git a/convex/_generated/dataModel.d.ts b/apps/dashboard/convex/_generated/dataModel.d.ts similarity index 100% rename from convex/_generated/dataModel.d.ts rename to apps/dashboard/convex/_generated/dataModel.d.ts diff --git a/convex/_generated/server.d.ts b/apps/dashboard/convex/_generated/server.d.ts similarity index 100% rename from convex/_generated/server.d.ts rename to apps/dashboard/convex/_generated/server.d.ts diff --git a/convex/_generated/server.js b/apps/dashboard/convex/_generated/server.js similarity index 100% rename from convex/_generated/server.js rename to apps/dashboard/convex/_generated/server.js diff --git a/convex/auth.config.ts b/apps/dashboard/convex/auth.config.ts similarity index 100% rename from convex/auth.config.ts rename to apps/dashboard/convex/auth.config.ts diff --git a/convex/auth.ts b/apps/dashboard/convex/auth.ts similarity index 100% rename from convex/auth.ts rename to apps/dashboard/convex/auth.ts diff --git a/convex/http.ts b/apps/dashboard/convex/http.ts similarity index 100% rename from convex/http.ts rename to apps/dashboard/convex/http.ts diff --git a/convex/schema.ts b/apps/dashboard/convex/schema.ts similarity index 100% rename from convex/schema.ts rename to apps/dashboard/convex/schema.ts diff --git a/index.html b/apps/dashboard/index.html similarity index 100% rename from index.html rename to apps/dashboard/index.html diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json new file mode 100644 index 0000000..63eff83 --- /dev/null +++ b/apps/dashboard/package.json @@ -0,0 +1,38 @@ +{ + "name": "@app/dashboard", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "@app/ui": "workspace:*", + "@auth/core": "0.37.0", + "@convex-dev/auth": "^0.0.91", + "convex": "^1.32.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "vite-plugin-svgr": "^5.2.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@tailwindcss/vite": "^4.2.2", + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "tailwindcss": "^4.2.2", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1" + } +} diff --git a/public/vite.svg b/apps/dashboard/public/vite.svg similarity index 100% rename from public/vite.svg rename to apps/dashboard/public/vite.svg diff --git a/src/App.css b/apps/dashboard/src/App.css similarity index 100% rename from src/App.css rename to apps/dashboard/src/App.css diff --git a/src/App.tsx b/apps/dashboard/src/App.tsx similarity index 100% rename from src/App.tsx rename to apps/dashboard/src/App.tsx diff --git a/src/Auth.tsx b/apps/dashboard/src/Auth.tsx similarity index 100% rename from src/Auth.tsx rename to apps/dashboard/src/Auth.tsx diff --git a/src/index.css b/apps/dashboard/src/index.css similarity index 94% rename from src/index.css rename to apps/dashboard/src/index.css index bb1217f..0021896 100644 --- a/src/index.css +++ b/apps/dashboard/src/index.css @@ -1,5 +1,5 @@ @import 'tailwindcss'; -@import './styles/tokens.css'; +@import '@app/ui/styles/tokens.css'; /* ─── Base reset / global defaults ──────────────────────────────────────── */ diff --git a/src/main.tsx b/apps/dashboard/src/main.tsx similarity index 100% rename from src/main.tsx rename to apps/dashboard/src/main.tsx diff --git a/src/pages/DesignSystem.tsx b/apps/dashboard/src/pages/DesignSystem.tsx similarity index 97% rename from src/pages/DesignSystem.tsx rename to apps/dashboard/src/pages/DesignSystem.tsx index bcb2595..e1e31f9 100644 --- a/src/pages/DesignSystem.tsx +++ b/apps/dashboard/src/pages/DesignSystem.tsx @@ -7,25 +7,24 @@ import { useState } from 'react'; -import { Button } from '../components/Button'; -import { Toggle } from '../components/Toggle'; -import { Tag } from '../components/Tags'; -import { SearchBar } from '../components/SearchBar'; -import { Avatar } from '../components/Avatar'; -import { SideBar } from '../components/SideBar'; -import type { SideBarItemId } from '../components/SideBar'; -import { LoopLogo } from '../components/Logo'; - -import { DashboardEventCard } from '../components/Cards/DashboardEventCard'; -import { DashboardPost } from '../components/Cards/DashboardPost'; -import { LoopSummary } from '../components/Cards/LoopSummary'; import { + Button, + Toggle, + Tag, + SearchBar, + Avatar, + SideBar, + LoopLogo, + DashboardEventCard, + DashboardPost, + LoopSummary, DateBadge, ExtensionEventRow, ExtensionEventCard, -} from '../components/Cards/ExtensionEventCard'; - -import { SearchPanel, SearchResultList } from '../components/SearchPanel'; + SearchPanel, + SearchResultList, +} from '@app/ui'; +import type { SideBarItemId } from '@app/ui'; // ─── Page-internal layout helpers ───────────────────────────────────────────── // These are NOT new design-system components — they are private page layout diff --git a/tsconfig.app.json b/apps/dashboard/tsconfig.app.json similarity index 79% rename from tsconfig.app.json rename to apps/dashboard/tsconfig.app.json index 18bdc9d..6a3c718 100644 --- a/tsconfig.app.json +++ b/apps/dashboard/tsconfig.app.json @@ -16,6 +16,12 @@ "noEmit": true, "jsx": "react-jsx", + /* Path aliases */ + "paths": { + "@app/ui": ["../../shared/ui/src/index.ts"], + "@app/ui/*": ["../../shared/ui/src/*"] + }, + /* Linting */ "strict": true, "noUnusedLocals": true, @@ -24,5 +30,5 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, - "include": ["src"] + "include": ["src", "../../shared/ui/src"] } diff --git a/tsconfig.json b/apps/dashboard/tsconfig.json similarity index 100% rename from tsconfig.json rename to apps/dashboard/tsconfig.json diff --git a/tsconfig.node.json b/apps/dashboard/tsconfig.node.json similarity index 100% rename from tsconfig.node.json rename to apps/dashboard/tsconfig.node.json diff --git a/vite.config.ts b/apps/dashboard/vite.config.ts similarity index 51% rename from vite.config.ts rename to apps/dashboard/vite.config.ts index c9fbebb..e417cf6 100644 --- a/vite.config.ts +++ b/apps/dashboard/vite.config.ts @@ -2,6 +2,10 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' import svgr from 'vite-plugin-svgr' +import path from 'path' +import { fileURLToPath } from 'url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) // https://vite.dev/config/ export default defineConfig({ @@ -10,4 +14,10 @@ export default defineConfig({ tailwindcss(), svgr(), ], + resolve: { + alias: { + '@app/ui': path.resolve(__dirname, '../../shared/ui/src'), + }, + dedupe: ['react', 'react-dom'], + }, }) diff --git a/bun.lock b/bun.lock index 7bc8bd9..e67a799 100644 --- a/bun.lock +++ b/bun.lock @@ -4,7 +4,12 @@ "workspaces": { "": { "name": "cornell-loop", + }, + "apps/dashboard": { + "name": "@app/dashboard", + "version": "0.0.0", "dependencies": { + "@app/ui": "workspace:*", "@auth/core": "0.37.0", "@convex-dev/auth": "^0.0.91", "convex": "^1.32.0", @@ -29,8 +34,25 @@ "vite": "^7.3.1", }, }, + "shared/ui": { + "name": "@app/ui", + "version": "0.0.0", + "devDependencies": { + "@types/react": "^19.2.7", + "react": "^19.2.0", + "typescript": "~5.9.3", + "vite-plugin-svgr": "^5.2.0", + }, + "peerDependencies": { + "react": ">=18", + }, + }, }, "packages": { + "@app/dashboard": ["@app/dashboard@workspace:apps/dashboard"], + + "@app/ui": ["@app/ui@workspace:shared/ui"], + "@auth/core": ["@auth/core@0.37.0", "", { "dependencies": { "@panva/hkdf": "^1.2.1", "@types/cookie": "0.6.0", "cookie": "0.7.1", "jose": "^5.9.3", "oauth4webapi": "^3.0.0", "preact": "10.11.3", "preact-render-to-string": "5.2.3" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "nodemailer": "^6.8.0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-LybAgfFC5dta3Mu3al0UbnzMGVBpZRqLMvvXupQOfETtPNlL7rXgTO13EVRTCdvPqMQrVYjODUDvgVfQM1M3Qg=="], "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], diff --git a/package.json b/package.json index 49aba10..4e3ec68 100644 --- a/package.json +++ b/package.json @@ -2,36 +2,14 @@ "name": "cornell-loop", "private": true, "version": "0.0.0", - "type": "module", + "workspaces": [ + "apps/*", + "shared/*" + ], "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview", - "type-check": "tsc --noEmit" - }, - "dependencies": { - "@auth/core": "0.37.0", - "@convex-dev/auth": "^0.0.91", - "convex": "^1.32.0", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "vite-plugin-svgr": "^5.2.0" - }, - "devDependencies": { - "@eslint/js": "^9.39.1", - "@tailwindcss/vite": "^4.2.2", - "@types/node": "^24.10.1", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.1.1", - "eslint": "^9.39.1", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.4.24", - "globals": "^16.5.0", - "tailwindcss": "^4.2.2", - "typescript": "~5.9.3", - "typescript-eslint": "^8.48.0", - "vite": "^7.3.1" + "dev": "bun run --cwd apps/dashboard dev", + "build": "bun run --cwd apps/dashboard build", + "lint": "bun run --cwd apps/dashboard lint", + "type-check": "bun run --cwd apps/dashboard type-check" } } diff --git a/shared/ui/package.json b/shared/ui/package.json new file mode 100644 index 0000000..895e6ad --- /dev/null +++ b/shared/ui/package.json @@ -0,0 +1,19 @@ +{ + "name": "@app/ui", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": { + ".": "./src/index.ts", + "./styles/*": "./src/styles/*" + }, + "peerDependencies": { + "react": ">=18" + }, + "devDependencies": { + "@types/react": "^19.2.7", + "react": "^19.2.0", + "typescript": "~5.9.3", + "vite-plugin-svgr": "^5.2.0" + } +} diff --git a/src/assets/bookmark-filled.svg b/shared/ui/src/assets/bookmark-filled.svg similarity index 100% rename from src/assets/bookmark-filled.svg rename to shared/ui/src/assets/bookmark-filled.svg diff --git a/src/assets/bookmark.svg b/shared/ui/src/assets/bookmark.svg similarity index 100% rename from src/assets/bookmark.svg rename to shared/ui/src/assets/bookmark.svg diff --git a/src/assets/calendar.svg b/shared/ui/src/assets/calendar.svg similarity index 100% rename from src/assets/calendar.svg rename to shared/ui/src/assets/calendar.svg diff --git a/src/assets/close_search.svg b/shared/ui/src/assets/close_search.svg similarity index 100% rename from src/assets/close_search.svg rename to shared/ui/src/assets/close_search.svg diff --git a/src/assets/close_tags.svg b/shared/ui/src/assets/close_tags.svg similarity index 100% rename from src/assets/close_tags.svg rename to shared/ui/src/assets/close_tags.svg diff --git a/src/assets/external-link.svg b/shared/ui/src/assets/external-link.svg similarity index 100% rename from src/assets/external-link.svg rename to shared/ui/src/assets/external-link.svg diff --git a/src/assets/home-icon.svg b/shared/ui/src/assets/home-icon.svg similarity index 100% rename from src/assets/home-icon.svg rename to shared/ui/src/assets/home-icon.svg diff --git a/src/assets/location-pin.svg b/shared/ui/src/assets/location-pin.svg similarity index 100% rename from src/assets/location-pin.svg rename to shared/ui/src/assets/location-pin.svg diff --git a/src/assets/loop-logo-mixed.svg b/shared/ui/src/assets/loop-logo-mixed.svg similarity index 100% rename from src/assets/loop-logo-mixed.svg rename to shared/ui/src/assets/loop-logo-mixed.svg diff --git a/src/assets/loop_logo.svg b/shared/ui/src/assets/loop_logo.svg similarity index 100% rename from src/assets/loop_logo.svg rename to shared/ui/src/assets/loop_logo.svg diff --git a/src/assets/newspaper-outline.svg b/shared/ui/src/assets/newspaper-outline.svg similarity index 100% rename from src/assets/newspaper-outline.svg rename to shared/ui/src/assets/newspaper-outline.svg diff --git a/src/assets/profile.svg b/shared/ui/src/assets/profile.svg similarity index 100% rename from src/assets/profile.svg rename to shared/ui/src/assets/profile.svg diff --git a/src/assets/react.svg b/shared/ui/src/assets/react.svg similarity index 100% rename from src/assets/react.svg rename to shared/ui/src/assets/react.svg diff --git a/src/assets/search_icon.svg b/shared/ui/src/assets/search_icon.svg similarity index 100% rename from src/assets/search_icon.svg rename to shared/ui/src/assets/search_icon.svg diff --git a/src/assets/star.svg b/shared/ui/src/assets/star.svg similarity index 100% rename from src/assets/star.svg rename to shared/ui/src/assets/star.svg diff --git a/src/assets/subscriptions-icon.svg b/shared/ui/src/assets/subscriptions-icon.svg similarity index 100% rename from src/assets/subscriptions-icon.svg rename to shared/ui/src/assets/subscriptions-icon.svg diff --git a/src/components/Avatar.tsx b/shared/ui/src/components/Avatar.tsx similarity index 100% rename from src/components/Avatar.tsx rename to shared/ui/src/components/Avatar.tsx diff --git a/src/components/Button.tsx b/shared/ui/src/components/Button.tsx similarity index 100% rename from src/components/Button.tsx rename to shared/ui/src/components/Button.tsx diff --git a/src/components/Cards/DashboardEventCard.tsx b/shared/ui/src/components/Cards/DashboardEventCard.tsx similarity index 100% rename from src/components/Cards/DashboardEventCard.tsx rename to shared/ui/src/components/Cards/DashboardEventCard.tsx diff --git a/src/components/Cards/DashboardPost.tsx b/shared/ui/src/components/Cards/DashboardPost.tsx similarity index 100% rename from src/components/Cards/DashboardPost.tsx rename to shared/ui/src/components/Cards/DashboardPost.tsx diff --git a/src/components/Cards/ExtensionEventCard.tsx b/shared/ui/src/components/Cards/ExtensionEventCard.tsx similarity index 100% rename from src/components/Cards/ExtensionEventCard.tsx rename to shared/ui/src/components/Cards/ExtensionEventCard.tsx diff --git a/src/components/Cards/LoopSummary.tsx b/shared/ui/src/components/Cards/LoopSummary.tsx similarity index 100% rename from src/components/Cards/LoopSummary.tsx rename to shared/ui/src/components/Cards/LoopSummary.tsx diff --git a/src/components/Cards/index.ts b/shared/ui/src/components/Cards/index.ts similarity index 100% rename from src/components/Cards/index.ts rename to shared/ui/src/components/Cards/index.ts diff --git a/src/components/DateBadge.tsx b/shared/ui/src/components/DateBadge.tsx similarity index 100% rename from src/components/DateBadge.tsx rename to shared/ui/src/components/DateBadge.tsx diff --git a/src/components/Logo.tsx b/shared/ui/src/components/Logo.tsx similarity index 100% rename from src/components/Logo.tsx rename to shared/ui/src/components/Logo.tsx diff --git a/src/components/SearchBar.tsx b/shared/ui/src/components/SearchBar.tsx similarity index 100% rename from src/components/SearchBar.tsx rename to shared/ui/src/components/SearchBar.tsx diff --git a/src/components/SearchPanel.tsx b/shared/ui/src/components/SearchPanel.tsx similarity index 100% rename from src/components/SearchPanel.tsx rename to shared/ui/src/components/SearchPanel.tsx diff --git a/src/components/SideBar.tsx b/shared/ui/src/components/SideBar.tsx similarity index 100% rename from src/components/SideBar.tsx rename to shared/ui/src/components/SideBar.tsx diff --git a/src/components/Tags.tsx b/shared/ui/src/components/Tags.tsx similarity index 100% rename from src/components/Tags.tsx rename to shared/ui/src/components/Tags.tsx diff --git a/src/components/Toggle.tsx b/shared/ui/src/components/Toggle.tsx similarity index 100% rename from src/components/Toggle.tsx rename to shared/ui/src/components/Toggle.tsx diff --git a/shared/ui/src/index.ts b/shared/ui/src/index.ts new file mode 100644 index 0000000..ac45534 --- /dev/null +++ b/shared/ui/src/index.ts @@ -0,0 +1,13 @@ +export * from './components/Avatar'; +export * from './components/Button'; +export * from './components/DateBadge'; +export * from './components/Logo'; +export * from './components/SearchBar'; +export * from './components/SearchPanel'; +export * from './components/SideBar'; +export * from './components/Tags'; +export * from './components/Toggle'; +export * from './components/Cards/DashboardEventCard'; +export * from './components/Cards/DashboardPost'; +export * from './components/Cards/ExtensionEventCard'; +export * from './components/Cards/LoopSummary'; diff --git a/src/styles/tokens.css b/shared/ui/src/styles/tokens.css similarity index 100% rename from src/styles/tokens.css rename to shared/ui/src/styles/tokens.css diff --git a/shared/ui/tsconfig.json b/shared/ui/tsconfig.json new file mode 100644 index 0000000..7ed7088 --- /dev/null +++ b/shared/ui/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite-plugin-svgr/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} From 0139d2d61f62d817c1eadc0ad0c0a3bb098946c9 Mon Sep 17 00:00:00 2001 From: Megan Yap Date: Tue, 14 Apr 2026 16:34:28 -0400 Subject: [PATCH 07/25] chore: add formatter --- .gitignore | 3 + .prettierignore | 4 + .prettierrc | 8 + package.json | 6 +- pnpm-lock.yaml | 3236 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 3256 insertions(+), 1 deletion(-) create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 pnpm-lock.yaml diff --git a/.gitignore b/.gitignore index a547bf3..9b9ddb2 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ dist-ssr *.njsproj *.sln *.sw? + +# AI +CLAUDE.md \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..7f080e6 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +node_modules +dist +pnpm-lock.yaml +convex/_generated diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..6487723 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 80, + "tabWidth": 2, + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/package.json b/package.json index 49aba10..1edaa06 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview", - "type-check": "tsc --noEmit" + "type-check": "tsc --noEmit", + "format": "prettier --write .", + "format:check": "prettier --check ." }, "dependencies": { "@auth/core": "0.37.0", @@ -29,6 +31,8 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "prettier": "^3.8.2", + "prettier-plugin-tailwindcss": "^0.7.2", "tailwindcss": "^4.2.2", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..c4f7518 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,3236 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@auth/core': + specifier: 0.37.0 + version: 0.37.0 + '@convex-dev/auth': + specifier: ^0.0.91 + version: 0.0.91(@auth/core@0.37.0)(convex@1.35.1(react@19.2.5))(react@19.2.5) + convex: + specifier: ^1.32.0 + version: 1.35.1(react@19.2.5) + react: + specifier: ^19.2.0 + version: 19.2.5 + react-dom: + specifier: ^19.2.0 + version: 19.2.5(react@19.2.5) + vite-plugin-svgr: + specifier: ^5.2.0 + version: 5.2.0(rollup@4.60.1)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)) + devDependencies: + '@eslint/js': + specifier: ^9.39.1 + version: 9.39.4 + '@tailwindcss/vite': + specifier: ^4.2.2 + version: 4.2.2(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)) + '@types/node': + specifier: ^24.10.1 + version: 24.12.2 + '@types/react': + specifier: ^19.2.7 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^5.1.1 + version: 5.2.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)) + eslint: + specifier: ^9.39.1 + version: 9.39.4(jiti@2.6.1) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-react-refresh: + specifier: ^0.4.24 + version: 0.4.26(eslint@9.39.4(jiti@2.6.1)) + globals: + specifier: ^16.5.0 + version: 16.5.0 + prettier: + specifier: ^3.8.2 + version: 3.8.2 + prettier-plugin-tailwindcss: + specifier: ^0.7.2 + version: 0.7.2(prettier@3.8.2) + tailwindcss: + specifier: ^4.2.2 + version: 4.2.2 + typescript: + specifier: ~5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.48.0 + version: 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + vite: + specifier: ^7.3.1 + version: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0) + +packages: + + '@auth/core@0.37.0': + resolution: {integrity: sha512-LybAgfFC5dta3Mu3al0UbnzMGVBpZRqLMvvXupQOfETtPNlL7rXgTO13EVRTCdvPqMQrVYjODUDvgVfQM1M3Qg==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + nodemailer: ^6.8.0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@convex-dev/auth@0.0.91': + resolution: {integrity: sha512-wLD4hszo3IhhMkwPs6ozWf0cUauwmhOvjUVn0g//kC338n/jApOjeDYWKCrn/qYUkveyDsbag5zrY8mVzA09Qg==} + hasBin: true + peerDependencies: + '@auth/core': ^0.37.0 + convex: ^1.17.0 + react: ^18.2.0 || ^19.0.0-0 + peerDependenciesMeta: + react: + optional: true + + '@esbuild/aix-ppc64@0.27.0': + resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.0': + resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.0': + resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.0': + resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.0': + resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.0': + resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.0': + resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.0': + resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.0': + resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.0': + resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.0': + resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.0': + resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.0': + resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.0': + resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.0': + resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.0': + resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.0': + resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.0': + resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.0': + resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.0': + resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.0': + resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.0': + resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.0': + resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.0': + resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.0': + resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.0': + resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@oslojs/asn1@1.0.0': + resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==} + + '@oslojs/binary@1.0.0': + resolution: {integrity: sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==} + + '@oslojs/crypto@1.0.1': + resolution: {integrity: sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==} + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + + '@panva/hkdf@1.2.1': + resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + + '@rolldown/pluginutils@1.0.0-rc.3': + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.60.1': + resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.1': + resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.1': + resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.1': + resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.1': + resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.1': + resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.60.1': + resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.60.1': + resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.60.1': + resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.60.1': + resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.60.1': + resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.1': + resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.1': + resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.1': + resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} + cpu: [x64] + os: [win32] + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/core@8.1.0': + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} + engines: {node: '>=14'} + + '@svgr/plugin-jsx@8.1.0': + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@tailwindcss/node@4.2.2': + resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} + + '@tailwindcss/oxide-android-arm64@4.2.2': + resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.2': + resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.2': + resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.2': + resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.2.2': + resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.12.2': + resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@typescript-eslint/eslint-plugin@8.58.2': + resolution: {integrity: sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.58.2 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.58.2': + resolution: {integrity: sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.58.2': + resolution: {integrity: sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.58.2': + resolution: {integrity: sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.58.2': + resolution: {integrity: sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.58.2': + resolution: {integrity: sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.58.2': + resolution: {integrity: sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.58.2': + resolution: {integrity: sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.58.2': + resolution: {integrity: sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.58.2': + resolution: {integrity: sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@5.2.0': + resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.19: + resolution: {integrity: sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==} + engines: {node: '>=6.0.0'} + hasBin: true + + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001788: + resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + convex@1.35.1: + resolution: {integrity: sha512-g23KrTjBiXqRHzWIN0PVFagKjrmFxWUaOSiBsAWPTpXX2rXl0L1F4PR0YpAcMJEzMgfZR9AGymJvLTM+KA6lsQ==} + engines: {node: '>=18.0.0', npm: '>=7.0.0'} + hasBin: true + peerDependencies: + '@auth0/auth0-react': ^2.0.1 + '@clerk/clerk-react': ^4.12.8 || ^5.0.0 + '@clerk/react': ^6.0.0 + react: ^18.0.0 || ^19.0.0-0 || ^19.0.0 + peerDependenciesMeta: + '@auth0/auth0-react': + optional: true + '@clerk/clerk-react': + optional: true + '@clerk/react': + optional: true + react: + optional: true + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + + electron-to-chromium@1.5.336: + resolution: {integrity: sha512-AbH9q9J455r/nLmdNZes0G0ZKcRX73FicwowalLs6ijwOmCJSRRrLX63lcAlzy9ux3dWK1w1+1nsBJEWN11hcQ==} + + enhanced-resolve@5.20.1: + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + esbuild@0.27.0: + resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.26: + resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-network-error@1.3.1: + resolution: {integrity: sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + jose@5.10.0: + resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucia@3.2.2: + resolution: {integrity: sha512-P1FlFBGCMPMXu+EGdVD9W4Mjm0DqsusmKgO7Xc33mI5X1bklmsQb0hfzPhXomQr9waWIBDsiOjvr1e6BTaUqpA==} + deprecated: This package has been deprecated. Please see https://lucia-auth.com/lucia-v3/migrate. + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + + node-releases@2.0.37: + resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + + oauth4webapi@3.8.5: + resolution: {integrity: sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss@8.5.9: + resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==} + engines: {node: ^10 || ^12 || >=14} + + preact-render-to-string@5.2.3: + resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + peerDependencies: + preact: '>=10' + + preact@10.11.3: + resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-tailwindcss@0.7.2: + resolution: {integrity: sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA==} + engines: {node: '>=20.19'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-hermes': + optional: true + '@prettier/plugin-oxc': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.8.2: + resolution: {integrity: sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.5: + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} + peerDependencies: + react: ^19.2.5 + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} + + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rollup@4.60.1: + resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + server-only@0.0.1: + resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + svg-parser@2.0.4: + resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + + tailwindcss@4.2.2: + resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} + + tapable@2.3.2: + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} + engines: {node: '>=6'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.58.2: + resolution: {integrity: sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite-plugin-svgr@5.2.0: + resolution: {integrity: sha512-qj2eAKF8C6PZWemVTvQA0xgQIcP1hHU6Buh7fl6BhvayWwnuxE+z417miKxeDvRWbDrupQ1oK99hfxElopJ3sQ==} + peerDependencies: + vite: '>=3.0.0' + + vite@7.3.2: + resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@auth/core@0.37.0': + dependencies: + '@panva/hkdf': 1.2.1 + '@types/cookie': 0.6.0 + cookie: 0.7.1 + jose: 5.10.0 + oauth4webapi: 3.8.5 + preact: 10.11.3 + preact-render-to-string: 5.2.3(preact@10.11.3) + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@convex-dev/auth@0.0.91(@auth/core@0.37.0)(convex@1.35.1(react@19.2.5))(react@19.2.5)': + dependencies: + '@auth/core': 0.37.0 + '@oslojs/crypto': 1.0.1 + '@oslojs/encoding': 1.1.0 + convex: 1.35.1(react@19.2.5) + cookie: 1.1.1 + is-network-error: 1.3.1 + jose: 5.10.0 + jwt-decode: 4.0.0 + lucia: 3.2.2 + oauth4webapi: 3.8.5 + path-to-regexp: 6.3.0 + server-only: 0.0.1 + optionalDependencies: + react: 19.2.5 + + '@esbuild/aix-ppc64@0.27.0': + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.0': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.0': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.0': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.0': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.0': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.0': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.0': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.0': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.0': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.0': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.0': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.0': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.0': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.0': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.0': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.0': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.0': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.0': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.0': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.0': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.0': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.0': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.0': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.0': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.0': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': + dependencies: + eslint: 9.39.4(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@oslojs/asn1@1.0.0': + dependencies: + '@oslojs/binary': 1.0.0 + + '@oslojs/binary@1.0.0': {} + + '@oslojs/crypto@1.0.1': + dependencies: + '@oslojs/asn1': 1.0.0 + '@oslojs/binary': 1.0.0 + + '@oslojs/encoding@1.1.0': {} + + '@panva/hkdf@1.2.1': {} + + '@rolldown/pluginutils@1.0.0-rc.3': {} + + '@rollup/pluginutils@5.3.0(rollup@4.60.1)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.60.1 + + '@rollup/rollup-android-arm-eabi@4.60.1': + optional: true + + '@rollup/rollup-android-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-x64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.1': + optional: true + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@svgr/babel-preset@8.1.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.0) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.0) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.0) + + '@svgr/core@8.1.0(typescript@5.9.3)': + dependencies: + '@babel/core': 7.29.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.9.3) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.29.0 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': + dependencies: + '@babel/core': 7.29.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) + '@svgr/core': 8.1.0(typescript@5.9.3) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + + '@tailwindcss/node@4.2.2': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.20.1 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.2 + + '@tailwindcss/oxide-android-arm64@4.2.2': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.2': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide@4.2.2': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-x64': 4.2.2 + '@tailwindcss/oxide-freebsd-x64': 4.2.2 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-x64-musl': 4.2.2 + '@tailwindcss/oxide-wasm32-wasi': 4.2.2 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 + + '@tailwindcss/vite@4.2.2(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0))': + dependencies: + '@tailwindcss/node': 4.2.2 + '@tailwindcss/oxide': 4.2.2 + tailwindcss: 4.2.2 + vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0) + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/cookie@0.6.0': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.12.2': + dependencies: + undici-types: 7.16.0 + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/type-utils': 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.58.2 + eslint: 9.39.4(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.58.2 + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.58.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@5.9.3) + '@typescript-eslint/types': 8.58.2 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.58.2': + dependencies: + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/visitor-keys': 8.58.2 + + '@typescript-eslint/tsconfig-utils@8.58.2(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.58.2': {} + + '@typescript-eslint/typescript-estree@8.58.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.58.2(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@5.9.3) + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/visitor-keys': 8.58.2 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.58.2 + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.58.2': + dependencies: + '@typescript-eslint/types': 8.58.2 + eslint-visitor-keys: 5.0.1 + + '@vitejs/plugin-react@5.2.0(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-rc.3 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0) + transitivePeerDependencies: + - supports-color + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.19: {} + + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.19 + caniuse-lite: 1.0.30001788 + electron-to-chromium: 1.5.336 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + callsites@3.1.0: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001788: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + convex@1.35.1(react@19.2.5): + dependencies: + esbuild: 0.27.0 + prettier: 3.8.2 + ws: 8.18.0 + optionalDependencies: + react: 19.2.5 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + cookie@0.7.1: {} + + cookie@1.1.1: {} + + cosmiconfig@8.3.6(typescript@5.9.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.9.3 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + detect-libc@2.1.2: {} + + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + electron-to-chromium@1.5.336: {} + + enhanced-resolve@5.20.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.2 + + entities@4.5.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + esbuild@0.27.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.0 + '@esbuild/android-arm': 0.27.0 + '@esbuild/android-arm64': 0.27.0 + '@esbuild/android-x64': 0.27.0 + '@esbuild/darwin-arm64': 0.27.0 + '@esbuild/darwin-x64': 0.27.0 + '@esbuild/freebsd-arm64': 0.27.0 + '@esbuild/freebsd-x64': 0.27.0 + '@esbuild/linux-arm': 0.27.0 + '@esbuild/linux-arm64': 0.27.0 + '@esbuild/linux-ia32': 0.27.0 + '@esbuild/linux-loong64': 0.27.0 + '@esbuild/linux-mips64el': 0.27.0 + '@esbuild/linux-ppc64': 0.27.0 + '@esbuild/linux-riscv64': 0.27.0 + '@esbuild/linux-s390x': 0.27.0 + '@esbuild/linux-x64': 0.27.0 + '@esbuild/netbsd-arm64': 0.27.0 + '@esbuild/netbsd-x64': 0.27.0 + '@esbuild/openbsd-arm64': 0.27.0 + '@esbuild/openbsd-x64': 0.27.0 + '@esbuild/openharmony-arm64': 0.27.0 + '@esbuild/sunos-x64': 0.27.0 + '@esbuild/win32-arm64': 0.27.0 + '@esbuild/win32-ia32': 0.27.0 + '@esbuild/win32-x64': 0.27.0 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@7.0.1(eslint@9.39.4(jiti@2.6.1)): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.2 + eslint: 9.39.4(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.4.26(eslint@9.39.4(jiti@2.6.1)): + dependencies: + eslint: 9.39.4(jiti@2.6.1) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.5.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-arrayish@0.2.1: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-network-error@1.3.1: {} + + isexe@2.0.0: {} + + jiti@2.6.1: {} + + jose@5.10.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jwt-decode@4.0.0: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucia@3.2.2: + dependencies: + '@oslojs/crypto': 1.0.1 + '@oslojs/encoding': 1.1.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.14 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + + node-releases@2.0.37: {} + + oauth4webapi@3.8.5: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-to-regexp@6.3.0: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + postcss@8.5.9: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact-render-to-string@5.2.3(preact@10.11.3): + dependencies: + preact: 10.11.3 + pretty-format: 3.8.0 + + preact@10.11.3: {} + + prelude-ls@1.2.1: {} + + prettier-plugin-tailwindcss@0.7.2(prettier@3.8.2): + dependencies: + prettier: 3.8.2 + + prettier@3.8.2: {} + + pretty-format@3.8.0: {} + + punycode@2.3.1: {} + + react-dom@19.2.5(react@19.2.5): + dependencies: + react: 19.2.5 + scheduler: 0.27.0 + + react-refresh@0.18.0: {} + + react@19.2.5: {} + + resolve-from@4.0.0: {} + + rollup@4.60.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.1 + '@rollup/rollup-android-arm64': 4.60.1 + '@rollup/rollup-darwin-arm64': 4.60.1 + '@rollup/rollup-darwin-x64': 4.60.1 + '@rollup/rollup-freebsd-arm64': 4.60.1 + '@rollup/rollup-freebsd-x64': 4.60.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 + '@rollup/rollup-linux-arm-musleabihf': 4.60.1 + '@rollup/rollup-linux-arm64-gnu': 4.60.1 + '@rollup/rollup-linux-arm64-musl': 4.60.1 + '@rollup/rollup-linux-loong64-gnu': 4.60.1 + '@rollup/rollup-linux-loong64-musl': 4.60.1 + '@rollup/rollup-linux-ppc64-gnu': 4.60.1 + '@rollup/rollup-linux-ppc64-musl': 4.60.1 + '@rollup/rollup-linux-riscv64-gnu': 4.60.1 + '@rollup/rollup-linux-riscv64-musl': 4.60.1 + '@rollup/rollup-linux-s390x-gnu': 4.60.1 + '@rollup/rollup-linux-x64-gnu': 4.60.1 + '@rollup/rollup-linux-x64-musl': 4.60.1 + '@rollup/rollup-openbsd-x64': 4.60.1 + '@rollup/rollup-openharmony-arm64': 4.60.1 + '@rollup/rollup-win32-arm64-msvc': 4.60.1 + '@rollup/rollup-win32-ia32-msvc': 4.60.1 + '@rollup/rollup-win32-x64-gnu': 4.60.1 + '@rollup/rollup-win32-x64-msvc': 4.60.1 + fsevents: 2.3.3 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.7.4: {} + + server-only@0.0.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + + source-map-js@1.2.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + svg-parser@2.0.4: {} + + tailwindcss@4.2.2: {} + + tapable@2.3.2: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite-plugin-svgr@5.2.0(rollup@4.60.1)(typescript@5.9.3)(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)): + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) + '@svgr/core': 8.1.0(typescript@5.9.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) + vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0) + transitivePeerDependencies: + - rollup + - supports-color + - typescript + + vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0): + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.9 + rollup: 4.60.1 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 24.12.2 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.32.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + ws@8.18.0: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} From c4f03dcb2001089a18ca4099a79b873dcb536674 Mon Sep 17 00:00:00 2001 From: Megan Yap Date: Tue, 14 Apr 2026 17:19:17 -0400 Subject: [PATCH 08/25] ui: add explicit type hints to text --- .cursor/rules/frontend/figma.md | 6 +- README.md | 22 +- bun.lock | 8 +- convex/schema.ts | 6 +- eslint.config.js | 18 +- index.html | 5 +- src/App.tsx | 4 +- src/Auth.tsx | 20 +- src/assets/profile-avatar.svg | 4 + src/components/Avatar.tsx | 77 ++- src/components/Button.tsx | 64 +-- src/components/Cards/DashboardEventCard.tsx | 130 ++--- src/components/Cards/DashboardPost.tsx | 71 ++- src/components/Cards/ExtensionEventCard.tsx | 109 +++-- src/components/Cards/LoopSummary.tsx | 40 +- src/components/Cards/index.ts | 24 +- src/components/DateBadge.tsx | 51 +- src/components/Logo.tsx | 52 +- src/components/SearchBar.tsx | 96 ++-- src/components/SearchPanel.tsx | 203 ++++---- src/components/SideBar.tsx | 112 +++-- src/components/Tags.tsx | 84 ++-- src/components/Toggle.tsx | 69 ++- src/index.css | 4 +- src/main.tsx | 12 +- src/pages/DesignSystem.tsx | 508 +++++++++++--------- src/styles/tokens.css | 140 +++--- vite.config.ts | 16 +- 28 files changed, 1037 insertions(+), 918 deletions(-) create mode 100644 src/assets/profile-avatar.svg diff --git a/.cursor/rules/frontend/figma.md b/.cursor/rules/frontend/figma.md index 696ffe5..c2ba3c0 100644 --- a/.cursor/rules/frontend/figma.md +++ b/.cursor/rules/frontend/figma.md @@ -1,7 +1,9 @@ ## Figma MCP Integration Rules + These rules define how to translate Figma inputs into code for this project and must be followed for every Figma-driven change. ### Required flow (do not skip) + 1. Run get_design_context first to fetch the structured representation for the exact node(s). 2. If the response is too large or truncated, run get_metadata to get the high‑level node map and then re‑fetch only the required node(s) with get_design_context. 3. Run get_screenshot for a visual reference of the node variant being implemented. @@ -10,6 +12,7 @@ These rules define how to translate Figma inputs into code for this project and 6. Validate against Figma for 1:1 look and behavior before marking complete. ### Implementation rules + - Treat the Figma MCP output (React + Tailwind) as a representation of design and behavior, not as final code style. - Replace Tailwind utility classes with the project’s preferred utilities/design‑system tokens when applicable. - Reuse existing components (e.g., buttons, inputs, typography, icon wrappers) instead of duplicating functionality. @@ -19,7 +22,8 @@ These rules define how to translate Figma inputs into code for this project and - Validate the final UI against the Figma screenshot for both look and behavior. ### Figma MCP server rules +   - The Figma MCP server provides an assets endpoint which can serve image and SVG assets   - IMPORTANT: If the Figma MCP server returns a localhost source for an image or an SVG, use that image or SVG source directly   - IMPORTANT: DO NOT import/add new icon packages, all the assets should be in the Figma payload -  - IMPORTANT: do NOT use or create placeholders if a localhost source is provided \ No newline at end of file +  - IMPORTANT: do NOT use or create placeholders if a localhost source is provided diff --git a/README.md b/README.md index d2e7761..8c2d760 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ If you are developing a production application, we recommend updating the config ```js export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(["dist"]), { - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], extends: [ // Other configs... @@ -34,40 +34,40 @@ export default defineConfig([ ], languageOptions: { parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], + project: ["./tsconfig.node.json", "./tsconfig.app.json"], tsconfigRootDir: import.meta.dirname, }, // other options... }, }, -]) +]); ``` You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: ```js // eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' +import reactX from "eslint-plugin-react-x"; +import reactDom from "eslint-plugin-react-dom"; export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(["dist"]), { - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], extends: [ // Other configs... // Enable lint rules for React - reactX.configs['recommended-typescript'], + reactX.configs["recommended-typescript"], // Enable lint rules for React DOM reactDom.configs.recommended, ], languageOptions: { parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], + project: ["./tsconfig.node.json", "./tsconfig.app.json"], tsconfigRootDir: import.meta.dirname, }, // other options... }, }, -]) +]); ``` diff --git a/bun.lock b/bun.lock index 7bc8bd9..4b3cb85 100644 --- a/bun.lock +++ b/bun.lock @@ -23,6 +23,8 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "prettier": "^3.8.2", + "prettier-plugin-tailwindcss": "^0.7.2", "tailwindcss": "^4.2.2", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", @@ -565,7 +567,9 @@ "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], + "prettier": ["prettier@3.8.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q=="], + + "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.2", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA=="], "pretty-format": ["pretty-format@3.8.0", "", {}, "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="], @@ -669,6 +673,8 @@ "convex/esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="], + "convex/prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.3", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA=="], "convex/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="], diff --git a/convex/schema.ts b/convex/schema.ts index 96b6449..3b25390 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -1,9 +1,9 @@ import { defineSchema } from "convex/server"; import { authTables } from "@convex-dev/auth/server"; - + const schema = defineSchema({ ...authTables, // Your other tables... }); - -export default schema; \ No newline at end of file + +export default schema; diff --git a/eslint.config.js b/eslint.config.js index 5e6b472..75d3c46 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,14 +1,14 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' -import { defineConfig, globalIgnores } from 'eslint/config' +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; +import { defineConfig, globalIgnores } from "eslint/config"; export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(["dist"]), { - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], extends: [ js.configs.recommended, tseslint.configs.recommended, @@ -20,4 +20,4 @@ export default defineConfig([ globals: globals.browser, }, }, -]) +]); diff --git a/index.html b/index.html index 823da63..112112e 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,10 @@ - +
diff --git a/src/App.tsx b/src/App.tsx index 855c3c8..ef082fd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ -import './App.css'; -import DesignSystem from './pages/DesignSystem'; +import "./App.css"; +import DesignSystem from "./pages/DesignSystem"; function App() { return ; diff --git a/src/Auth.tsx b/src/Auth.tsx index 2935f3d..47b25bb 100644 --- a/src/Auth.tsx +++ b/src/Auth.tsx @@ -1,21 +1,29 @@ import { useAuthActions } from "@convex-dev/auth/react"; - + export function SignIn() { const { signIn } = useAuthActions(); return ( - + }} + > + Sign in with Google + ); } export function SignOut() { const { signOut } = useAuthActions(); return ( - + }} + > + Sign Out + ); -} \ No newline at end of file +} diff --git a/src/assets/profile-avatar.svg b/src/assets/profile-avatar.svg new file mode 100644 index 0000000..4598188 --- /dev/null +++ b/src/assets/profile-avatar.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 62f60e7..d0870a4 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -13,14 +13,37 @@ * * Image fallback: * When an avatar item has no `src`, profile.svg from src/assets is shown on a - * Neutral/100 background. The icon is normalised to ~Neutral/900 via the + * colour derived deterministically from the avatar name using design-system + * palette tokens. The icon is normalised to ~Neutral/900 via the * --filter-icon-nav CSS custom property defined in tokens.css. * * All colours, spacing, and font values reference CSS custom properties from * src/styles/tokens.css — nothing is hardcoded. */ -import ProfileIcon from '../assets/profile.svg?react'; +import ProfileIcon from "../assets/profile-avatar.svg?react"; + +// ─── Fallback colour palette ───────────────────────────────────────────────── +// Deterministic pastel backgrounds for avatars without an image, drawn from +// design-system token values to keep things visually cohesive. + +const FALLBACK_PALETTE: { bg: string; fg: string }[] = [ + { bg: "var(--color-primary-500)", fg: "var(--color-primary-800)" }, // peach / dark orange + { bg: "var(--color-secondary-400)", fg: "var(--color-secondary-700)" }, // light blue / navy + { bg: "var(--color-primary-400)", fg: "var(--color-primary-700)" }, // warm cream / brand orange + { bg: "var(--color-secondary-300)", fg: "var(--color-secondary-600)" }, // pale blue / medium blue + { bg: "var(--color-primary-600)", fg: "var(--color-primary-900)" }, // salmon / deep brown + { bg: "var(--color-secondary-500)", fg: "var(--color-secondary-900)" }, // sky blue / dark navy +]; + +/** Simple string hash → palette index so the same name always gets the same colour. */ +function fallbackColorsForName(name: string): { bg: string; fg: string } { + let hash = 0; + for (let i = 0; i < name.length; i++) { + hash = (hash * 31 + name.charCodeAt(i)) | 0; + } + return FALLBACK_PALETTE[Math.abs(hash) % FALLBACK_PALETTE.length]; +} // ─── Public types ───────────────────────────────────────────────────────────── @@ -60,12 +83,12 @@ function AvatarCircle({ item, stacked = false, zIndex }: AvatarCircleProps) { {item.src ? ( )} @@ -117,12 +146,12 @@ export function Avatar({ avatars, className }: AvatarProps) { */
{/* ── Circle(s) ── */}
@@ -146,16 +175,16 @@ export function Avatar({ avatars, className }: AvatarProps) { */}
{avatars.map((avatar, i) => ( diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 31cef03..b0580e4 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -18,14 +18,14 @@ * from src/styles/tokens.css — nothing is hardcoded. */ -import type { ComponentPropsWithoutRef } from 'react'; +import type { ComponentPropsWithoutRef } from "react"; // ─── Public types ──────────────────────────────────────────────────────────── -export type ButtonVariant = 'primary' | 'secondary'; -export type ButtonSize = 'sm' | 'md' | 'cta'; +export type ButtonVariant = "primary" | "secondary"; +export type ButtonSize = "sm" | "md" | "cta"; -export interface ButtonProps extends ComponentPropsWithoutRef<'button'> { +export interface ButtonProps extends ComponentPropsWithoutRef<"button"> { /** Visual hierarchy level. Defaults to 'primary'. */ variant?: ButtonVariant; /** @@ -43,12 +43,12 @@ export interface ButtonProps extends ComponentPropsWithoutRef<'button'> { // can detect every class at build time — do not build these strings dynamically. const BASE_CLASSES = - 'inline-flex items-center justify-center gap-[var(--space-2)] ' + - 'font-[family-name:var(--font-body)] rounded-[var(--radius-button)] ' + - 'whitespace-nowrap select-none cursor-pointer ' + - 'transition-[background-color,box-shadow,color] duration-150 ease-in-out ' + - 'focus-visible:outline-2 focus-visible:outline-offset-2 ' + - 'disabled:pointer-events-none'; + "inline-flex items-center justify-center gap-[var(--space-2)] " + + "font-[family-name:var(--font-body)] rounded-[var(--radius-button)] " + + "whitespace-nowrap select-none cursor-pointer " + + "transition-[background-color,box-shadow,color] duration-150 ease-in-out " + + "focus-visible:outline-2 focus-visible:outline-offset-2 " + + "disabled:pointer-events-none"; const VARIANT_CLASSES: Record = { /** @@ -60,11 +60,11 @@ const VARIANT_CLASSES: Record = { * Source: Figma Frame95 (node 382:822) and Frame94 CTA (node 390:793) */ primary: - 'bg-[var(--color-primary-700)] text-[var(--color-white)] ' + - 'hover:bg-[var(--color-primary-hover)] hover:shadow-[var(--shadow-primary-glow)] ' + - 'active:bg-[var(--color-primary-800)] active:shadow-[var(--shadow-primary-glow)] ' + - 'focus-visible:outline-[var(--color-primary-700)] ' + - 'disabled:bg-[var(--color-neutral-500)] disabled:shadow-none', + "bg-[var(--color-primary-700)] text-[color:var(--color-white)] " + + "hover:bg-[var(--color-primary-hover)] hover:shadow-[var(--shadow-primary-glow)] " + + "active:bg-[var(--color-primary-800)] active:shadow-[var(--shadow-primary-glow)] " + + "focus-visible:outline-[var(--color-primary-700)] " + + "disabled:bg-[var(--color-neutral-500)] disabled:shadow-none", /** * Secondary — outlined. @@ -75,11 +75,11 @@ const VARIANT_CLASSES: Record = { * Source: Figma Frame93 (node 383:415) */ secondary: - 'bg-[var(--color-surface)] border border-[var(--color-border)] text-[var(--color-black)] ' + - 'hover:bg-[var(--color-surface-subtle)] ' + - 'active:bg-[var(--color-border)] ' + - 'focus-visible:outline-[var(--color-neutral-900)] ' + - 'disabled:bg-[var(--color-neutral-500)] disabled:border-[var(--color-neutral-700)] disabled:text-[var(--color-neutral-700)]', + "bg-[var(--color-surface)] border border-[var(--color-border)] text-[color:var(--color-black)] " + + "hover:bg-[var(--color-surface-subtle)] " + + "active:bg-[var(--color-border)] " + + "focus-visible:outline-[var(--color-neutral-900)] " + + "disabled:bg-[var(--color-neutral-500)] disabled:border-[var(--color-neutral-700)] disabled:text-[color:var(--color-neutral-700)]", }; const SIZE_CLASSES: Record = { @@ -88,33 +88,33 @@ const SIZE_CLASSES: Record = { * px 16px, py 6px, body-2 Regular. */ sm: - 'px-[var(--space-4)] py-[var(--space-1-5)] ' + - 'text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] ' + - 'tracking-[var(--letter-spacing-body2)] font-normal', + "px-[var(--space-4)] py-[var(--space-1-5)] " + + "text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] " + + "tracking-[var(--letter-spacing-body2)] font-normal", /** * md — standard interactive size; same padding as sm, SemiBold weight. */ md: - 'px-[var(--space-4)] py-[var(--space-1-5)] ' + - 'text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] ' + - 'tracking-[var(--letter-spacing-body2)] font-semibold', + "px-[var(--space-4)] py-[var(--space-1-5)] " + + "text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] " + + "tracking-[var(--letter-spacing-body2)] font-semibold", /** * cta — Figma CTA Buttons (Frame94, node 390:793). * Full-width, py 8px, body-2 SemiBold. */ cta: - 'w-full px-[var(--space-4)] py-[var(--space-2)] ' + - 'text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] ' + - 'tracking-[var(--letter-spacing-body2)] font-semibold', + "w-full px-[var(--space-4)] py-[var(--space-2)] " + + "text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] " + + "tracking-[var(--letter-spacing-body2)] font-semibold", }; // ─── Component ─────────────────────────────────────────────────────────────── export function Button({ - variant = 'primary', - size = 'md', + variant = "primary", + size = "md", className, children, ...rest @@ -129,7 +129,7 @@ export function Button({ className, ] .filter(Boolean) - .join(' ')} + .join(" ")} style={{ fontVariationSettings: "'opsz' 14" }} {...rest} > diff --git a/src/components/Cards/DashboardEventCard.tsx b/src/components/Cards/DashboardEventCard.tsx index 273c5fb..261fff1 100644 --- a/src/components/Cards/DashboardEventCard.tsx +++ b/src/components/Cards/DashboardEventCard.tsx @@ -18,14 +18,14 @@ * src/styles/tokens.css — nothing is hardcoded. */ -import type { ComponentPropsWithoutRef } from 'react'; -import { Tag } from '../Tags'; -import type { TagColor } from '../Tags'; -import CalendarIcon from '../../assets/calendar.svg?react'; -import LocationPinIcon from '../../assets/location-pin.svg?react'; -import ExternalLinkIcon from '../../assets/external-link.svg?react'; -import BookmarkIcon from '../../assets/bookmark.svg?react'; -import BookmarkFilledIcon from '../../assets/bookmark-filled.svg?react'; +import type { ComponentPropsWithoutRef } from "react"; +import { Tag } from "../Tags"; +import type { TagColor } from "../Tags"; +import CalendarIcon from "../../assets/calendar.svg?react"; +import LocationPinIcon from "../../assets/location-pin.svg?react"; +import ExternalLinkIcon from "../../assets/external-link.svg?react"; +import BookmarkIcon from "../../assets/bookmark.svg?react"; +import BookmarkFilledIcon from "../../assets/bookmark-filled.svg?react"; // ─── Shared types (re-exported for use by DashboardPost) ───────────────────── @@ -38,13 +38,13 @@ export interface TagItem { // ─── Shared class strings ───────────────────────────────────────────────────── const BODY2_CLASSES = - 'font-[family-name:var(--font-body)] font-normal ' + - 'text-[var(--font-size-body2)] leading-[var(--line-height-body2)] ' + - 'tracking-[var(--letter-spacing-body2)]'; + "font-[family-name:var(--font-body)] font-normal " + + "text-[length:var(--font-size-body2)] leading-[var(--line-height-body2)] " + + "tracking-[var(--letter-spacing-body2)]"; // ─── Public types ───────────────────────────────────────────────────────────── -export interface DashboardEventCardProps extends ComponentPropsWithoutRef<'article'> { +export interface DashboardEventCardProps extends ComponentPropsWithoutRef<"article"> { title: string; datetime: string; location: string; @@ -89,28 +89,27 @@ export function DashboardEventCard({ return (
{/* ── Section 1: title row + meta row ── */} -
- +
{/* Title row: title text | share + bookmark icons | RSVP button */} -
+

@@ -118,7 +117,7 @@ export function DashboardEventCard({

{/* Action icons */} -
+
{/* * Share — external-link.svg: stroke="#ADB5BD" (Neutral/500, muted by default). * On hover, --filter-icon-close-default darkens it to ~Neutral/700 (#495057). @@ -132,9 +131,9 @@ export function DashboardEventCard({