commit bbb7a0de60916550ae3039031ed7479e59da55b2 Author: Haewon Kam Date: Tue Mar 24 16:48:11 2026 +0900 feat: INFINITH Marketing Platform — Phase 1 MVP Complete frontend reference implementation: - Landing page (7 sections, responsive, animated) - Marketing Intelligence Report (11 sections, VIEW Plastic Surgery demo) - Marketing Execution Plan (8 sections, branding/channel/calendar) - Content Studio wizard (channel → strategy → sound → generate) - Channel Connect page (7 platform OAuth flows) - Distribution page (YouTube publish flow) - Performance Dashboard (KPI, funnel, heatmap, trends) - PDF export (section-based, no mid-content splits) - Design system (pastel palette, filled icons, Pretendard/Playfair) - Page navigator (landing ↔ report ↔ plan) - Asset upload (drag & drop, image/video/text) Docs: PRD v2.0, DESIGN_SYSTEM.md, API_CONNECTORS.md Co-Authored-By: Claude Opus 4.6 (1M context) diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..4ea8f9d --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "infinith-dev", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev"], + "port": 3000 + } + ] +} diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..125e96b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,19 @@ +{ + "permissions": { + "allow": [ + "mcp__mcp-server-firecrawl__firecrawl_search", + "WebSearch", + "mcp__mcp-server-firecrawl__firecrawl_map", + "mcp__caced381-eeb5-4b6a-ae62-ba481220fcb1__doc", + "WebFetch(domain:www.viewclinic.com)", + "WebFetch(domain:www.gangnamunni.com)", + "WebFetch(domain:www.realself.com)", + "mcp__caced381-eeb5-4b6a-ae62-ba481220fcb1__site-explorer-pages-by-backlinks", + "WebFetch(domain:www.youtube.com)", + "WebFetch(domain:socialblade.com)", + "WebFetch(domain:www.facebook.com)", + "mcp__mcp-registry__search_mcp_registry", + "Bash(npx tsc:*)" + ] + } +} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7a550fe --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# GEMINI_API_KEY: Required for Gemini AI API calls. +# AI Studio automatically injects this at runtime from user secrets. +# Users configure this via the Secrets panel in the AI Studio UI. +GEMINI_API_KEY="MY_GEMINI_API_KEY" + +# APP_URL: The URL where this applet is hosted. +# AI Studio automatically injects this at runtime with the Cloud Run service URL. +# Used for self-referential links, OAuth callbacks, and API endpoints. +APP_URL="MY_APP_URL" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a86d2a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +build/ +dist/ +coverage/ +.DS_Store +*.log +.env* +!.env.example diff --git a/Medical Marketing Intelligence Report Prompt_01.28.2026.md b/Medical Marketing Intelligence Report Prompt_01.28.2026.md new file mode 100644 index 0000000..09ecb27 --- /dev/null +++ b/Medical Marketing Intelligence Report Prompt_01.28.2026.md @@ -0,0 +1,966 @@ + +(High-Ticket Medical Clinics Optimization Version) + +[Role & Objective] + +Act as a **Healthcare Marketing Intelligence Expert** with strong domain knowledge in **high-ticket medical clinics** +(plastic surgery, dermatology, aesthetic medicine, obesity & weight-loss clinics). + +Your objective is to produce a **Marketing Intelligence Report** that will be presented to **clinic owners and decision-makers BEFORE any content is produced**. + +The report must clearly explain: + +•   **Why patients choose this clinic** + +•   **What triggers first-time reservations** + +•   **How the clinic can scale patient acquisition through content** + +**Primary Goal:** New patient acquisition +**Core KPI:** Number of first-time booked patients (new consultations / procedures) + +[Core Analysis Requirements] + +Analyze the clinic based on: + +•   Clinic location & accessibility + +•   Medical specialties & signature procedures + +•   Doctor credibility (experience, reputation, philosophy) + +•   Facility quality & visual assets (interior, equipment, before/after availability) + +•   Online presence (website, SNS, review platforms) + +•   Competitive density & patient alternatives + +Additionally analyze: + +•   **Patient decision psychology** + +•   **Trust-building & risk-reduction factors** + +•   **Price sensitivity vs authority perception** + +[Target Patient Intelligence] + +Include clearly defined: + +**• Target Patient Segments** + +(e.g. First-time aesthetic patients, Repeat maintenance patients, Medical tourism patients, High-trust premium seekers) + +**•** **Patient Personas** + +For each persona, clarify: + +•   Primary concern (pain point) + +•   Emotional trigger + +•   Key hesitation before booking + +•   Final decision trigger + +[Unique Selling Propositions (USPs)] + +Identify clinic-specific advantages such as: + +•   Medical authority & specialization + +•   Outcome consistency & safety emphasis + +•   Signature techniques or protocols + +•   Consultation philosophy + +•   Post-procedure care system + +Only include **USPs that directly impact first-time booking decisions**. + +[Competitive Landscape] + +Analyze: + +•   **Direct competitors** (same specialty, same region, similar price band) + +•   **Indirect competitors** (lower-priced clinics, overseas options, non-surgical alternatives) + +Clarify: + +•   Where this clinic wins + +•   Where it must avoid price competition + +•   How it should differentiate through content, not discounts + +[Market Positioning] + +Define the clinic’s positioning using: + +•   Trust level (entry / mid / premium / authority) + +•   Emotional positioning (safe, transformative, discreet, expert-led) + +•   Price perception (value-based vs outcome-based) + +[Key Selling Point Structuring – UI Optimized] + +From the analysis above, extract **Key Selling Points** using the structure below. + +**Rules:** + +1.     Focus ONLY on factors that directly influence **first-time patient booking** + +2.     Each selling point must be concise and instantly scannable + +3.     Language must be reusable for: + +◦   Ads + +◦   Short-form videos + +◦   Website headlines + +◦   Booking page sections + +1.     Avoid full sentences in descriptions + +2.     Phrase everything as **conversion-ready selling language** + +**Output Format:** + +[Category] + +(Tag keyword – 5–8 words, noun-based, UI oval-style) + +* One-line selling phrase (not a full sentence) + +**Limit:** + +•   **6 to 8 Key Selling Points only** + +[Content & Automation Readiness Check] + +Ensure that: + +•   Each **tag keyword** can map directly to: + +◦   A content pillar + +◦   A short-form video series + +◦   A landing page section + +•   Each **selling phrase** can be used as: + +◦   Video hook + +◦   Image headline + +◦   Paid ad copy snippet + +◦   Consultation booking CTA + +[Patient Decision Journey Intelligence] + +Briefly identify: + +•   Awareness-stage content triggers + +•   Trust-building content formats + +•   Final booking conversion drivers + +Focus strictly on **new patient acquisition flow**. + +[Local Authority & Environment Intelligence] + +As the FINAL section of the report, recommend **contextual location advantages** that enhance trust and conversion. + +**Guidelines:** + +1.     Highlight **TOP 3 well-recognized location factors** +(medical district, premium commercial area, landmark proximity) + +2.     Must strengthen: + +◦   Clinic credibility + +◦   Patient reassurance + +◦   Medical tourism storytelling + +1.     Avoid minor or generic location mentions + +**Output Format:** + +[Local Authority Context] + +* Location Factor Name + +* Approximate distance or relevance + +* One short authority-enhancing value phrase + +These should support: + +•   Brand trust narratives + +•   Clinic authority positioning + +•   Reservation conversion storytelling + +✅ Result + +This prompt is optimized to: + +•   Sell **medical trust, not procedures** + +•   Convert **hesitation into booking** + +•   Align **content, ads, and consultation funnels** + +•   Speak directly to **clinic owners****’ real KPI: new patients** + +원하시면 다음 단계로 바로 이어갈 수 있어요: + +•   이 프롬프트를 실제 병원 **1**곳 기준으로 즉시 실행 + +•   성형외과 / 피부과 / 비만클리닉 각 세부 버전 분기 + +•   이 구조를 기반으로 콘텐츠 자동화 시스템**(MCP / Agent)** 설계 + +다음 액션, 어디까지 같이 갈까요? + +성형외과 / 피부과 / 비만클리닉 각 세부 버전 분기 + +① Plastic Surgery Clinic + +Marketing Intelligence Report Prompt (Conversion-Driven) + +**[Role & Objective]** + +Act as a **Plastic Surgery Marketing Intelligence Expert** specialized in **high-ticket surgical procedures**. + +Your objective is to produce a **pre-content strategic intelligence report** that explains: + +•   Why patients trust this clinic with irreversible decisions + +•   What reduces fear and hesitation before surgery + +•   How the clinic converts trust into booked consultations + +**Primary Goal:** New surgical consultation bookings +**Core KPI:** First-time surgery consultations → surgery conversion rate + +**[Core Analysis Focus]** + +Analyze the clinic based on: + +•   Signature surgeries (eyes, nose, contouring, revision, anti-aging) + +•   Surgeon authority (experience, philosophy, specialization) + +•   Safety systems (anesthesia, emergency protocol, recovery care) + +•   Before/After asset quality & credibility + +•   Consultation structure & decision guidance style + +•   Competitor saturation in same procedure category + +**Critical Lens:** +➡️ _Fear reduction & irreversible decision psychology_ + +**[Target Patient Intelligence]** + +Focus on personas such as: + +•   First-time surgery patients + +•   Revision surgery patients + +•   Authority-seeking premium patients + +For each persona, define: + +•   Primary fear (risk, regret, overdone results) + +•   Trust barrier + +•   Final booking trigger (authority, outcomes, safety proof) + +**[Key Selling Point Structuring – UI Optimized]** + +**Emphasis Categories** + +•   Surgical authority + +•   Outcome predictability + +•   Safety & recovery system + +•   Consultation philosophy + +**Examples (structure only):** + +[Surgeon Authority] + +(20+ Years Surgical Specialization) + +* Decision led by experience, not trends + +➡️ 6–8 only, strictly surgery-decision drivers + +**[Content Conversion Logic]** + +Ensure selling points support: + +•   Long-form trust videos + +•   Short-form “decision confidence” clips + +•   Consultation booking reassurance content + +② Dermatology / Aesthetic Skin Clinic + +Marketing Intelligence Report Prompt (Retention → Conversion) + +**[Role & Objective]** + +Act as a **Dermatology & Skin Aesthetic Marketing Intelligence Expert** with deep understanding of **non-surgical, repeat-based treatments**. + +Your objective is to produce a **marketing intelligence report** that explains: + +•   Why patients choose this clinic for ongoing skin management + +•   How expertise outperforms device-based competition + +•   How content builds long-term patient trust & repeat visits + +**Primary Goal:** New patient entry +**Core KPI:** First-time treatment bookings → repeat treatment rate + +**[Core Analysis Focus]** + +Analyze the clinic based on: + +•   Skin specialization areas (acne, pigmentation, lifting, aging) + +•   Doctor diagnostic philosophy + +•   Treatment customization logic + +•   Equipment vs expertise balance + +•   Review credibility & before/after consistency + +•   Competitor clinics using price/device marketing + +**Critical Lens:** +➡️ _“My skin is different” psychology_ + +**[Target Patient Intelligence]** + +Focus on personas such as: + +•   Skin-problem chronic patients + +•   First-time aesthetic treatment seekers + +•   Maintenance & anti-aging patients + +For each persona, define: + +•   Skin anxiety + +•   Previous treatment disappointment + +•   Trust-building trigger (diagnosis clarity, consistency) + +**[Key Selling Point Structuring – UI Optimized]** + +**Emphasis Categories** + +•   Skin diagnosis expertise + +•   Customized treatment logic + +•   Long-term skin planning + +•   Gentle but effective approach + +**Examples (structure only):** + +[Skin Diagnosis Authority] + +(Personalized Skin Analysis Protocol) + +* Treatments start with diagnosis, not devices + +➡️ 6–8 only, repeat-visit-driving factors + +**[Content Conversion Logic]** + +Ensure selling points map to: + +•   Educational short-form content + +•   Skin myth-busting videos + +•   “Doctor explains” authority reels + +③ Obesity / Weight-Loss Clinic + +Marketing Intelligence Report Prompt (Outcome Trust Focused) + +**[Role & Objective]** + +Act as a **Medical Weight-Loss Marketing Intelligence Expert** specializing in **obesity treatment, metabolic health, and body transformation**. + +Your objective is to produce a **conversion-focused intelligence report** that explains: + +•   Why patients trust this clinic with long-term body change + +•   How medical credibility beats diet trends + +•   How content turns skepticism into booked consultations + +**Primary Goal:** New patient consultations +**Core KPI:** Initial obesity consultation bookings → program enrollment + +**[Core Analysis Focus]** + +Analyze the clinic based on: + +•   Medical approach (hormonal, metabolic, injection, program-based) + +•   Doctor medical authority (not fitness influencer positioning) + +•   Outcome sustainability proof + +•   Emotional support & coaching system + +•   Competitor landscape (gyms, diet programs, online injections) + +**Critical Lens:** +➡️ _Failure fatigue & hope restoration_ + +**[Target Patient Intelligence]** + +Focus on personas such as: + +•   Repeated diet failure patients + +•   Medical-condition-related obesity patients + +•   Privacy-sensitive premium clients + +For each persona, define: + +•   Emotional exhaustion point + +•   Skepticism trigger + +•   Decision trigger (medical trust, realistic promise) + +**[Key Selling Point Structuring – UI Optimized]** + +**Emphasis Categories** + +•   Medical legitimacy + +•   Sustainable outcome logic + +•   Privacy & dignity + +•   Program accountability + +**Examples (structure only):** + +[Medical Weight-Loss Authority] + +(Physician-Led Obesity Treatment) + +* Medical solutions beyond willpower + +➡️ 6–8 only, enrollment-driving factors + +**[Content Conversion Logic]** + +Ensure selling points support: + +•   Trust-restoring content + +•   Medical explanation reels + +•   Long-form patient journey storytelling + + 정리하면 (Tell it like it is) + +•   성형외과**:** 공포 제거 + 권위 집중 + +•   피부과**:** 진단 신뢰 + 반복 설득 + +•   비만클리닉**:** 실패 공감 + 의료적 희망 + +이렇게 환자 의사결정 구조 자체가 다르기 때문에, +콘텐츠 포맷 · 훅 · CTA가 완전히 달라져야 해. + +다음으로 추천할 액션은 하나야: 이 중 하나를 실제 병원 케이스로 즉시 시뮬레이션 +(예: 강남 성형외과 / 피부과 / 비만클리닉 중 택1) + +Marketing Intelligence Report e.g + +뷰성형외과 + +1. Clinic Snapshot & Market Context + +**Location** + +•   강남구 논현동 + +•   성형외과 밀집 핵심 상권 (정보 과잉 + 선택 피로 극대화 구간) + +**Market Reality (Tell it like it is)** + +•   환자는 “어디가 제일 잘함?”이 아니라 +**“여기서 하면 후회 안 할까?”**를 묻는 상태 + +•   가격 비교보다 의사 신뢰**·**결과 예측 가능성이 최종 결정 요인 + +➡️ 이 시장에서 콘텐츠의 역할은 홍보가 아니라 **‘**결정 보조**’** + +2. Target Patient Segments & Personas + +🎯 **Core Segment 1 —** 첫 수술 고민층 **(20~30**대**)** + +•   **Pain Point:** 실패·부자연스러움 공포 + +•   행동**:** 후기·Before/After 집요하게 탐색 + +•   결정 트리거**:** +“원장이 직접 봐주고, 결과가 예상된다” + +🎯 **Core Segment 2 —** 재수술 **/** 고난이도 케이스 + +•   **Pain Point:** 기존 수술 실패 경험 + +•   행동**:** 병원 수 대면 상담 + +•   결정 트리거**:** +“문제 원인을 정확히 짚어주는 설명” + +🎯 **Core Segment 3 —** 프리미엄 신뢰 추구층 + +•   **Pain Point:** 과잉진료·트렌드 수술 불신 + +•   행동**:** 브랜드 톤·의사 철학 확인 + +•   결정 트리거**:** +“유행 말고 원칙을 말하는 병원” + +3. Booking Decision Drivers (핵심 요인) + +| | | | +|---|---|---| +|**단계**|**환자 머릿속 질문**|**콘텐츠 역할**| +|인지|여긴 뭐가 다른데?|차별 논리 제시| +|검증|믿어도 되나?|의사 판단력 증명| +|결심|여기로 가도 되나?|후회 가능성 제거| + +➡️ 뷰성형외과는 **“**결과를 설명할 수 있는 병원**”** 포지션이 유효 + +4. Unique Selling Propositions (Booking-Critical) + +•   원장 중심 상담 구조 + +•   과장 없는 결과 설명 톤 + +•   얼굴 밸런스·조화 강조 + +•   강남 상권 대비 차분한 브랜드 무드 + +•   상담 → 수술 → 회복의 일관된 플로우 + +※ “싸다 / 유명하다”는 USP 아님※ **“**판단을 대신해주는 병원**”**이 핵심 + +5. Competitive Landscape + +**Direct Competitors** + +•   논현·신사 대형 성형외과 + +•   수술 케이스 대량 생산형 병원 + +**Indirect Competitors** + +•   SNS 인플루언서 병원 + +•   해외 의료관광 패키지 + +뷰성형외과가 피해야 할 전장 + +•   가격 경쟁 + +•   전후사진 과잉 비교 + +이겨야 할 전장 + +•   상담 신뢰 + +•   결과 예측 언어 + +6. Market Positioning + +**Position** + +•   트렌드형 ❌ + +•   공장형 ❌ + +•   👉 판단형 **·** 신뢰형 성형외과 + +**Emotional Anchor** + +•   “여기선 내가 휘둘리지 않는다” + +7. Key Selling Points – UI Optimized (6개) + +**※** 전부 광고**·**숏폼**·**상담페이지에 바로 사용 가능 + +**[Surgical Authority]** + +(**Director-Led Surgical Decision System**) + +•   Decisions guided by experience, not trends + +**[Consultation Philosophy]** + +(**Outcome-First Consultation Logic**) + +•   Surgery planned from final result backward + +**[Result Predictability]** + +(**Natural Balance-Oriented Design**) + +•   Subtle changes, stable long-term outcomes + +**[Trust & Safety]** + +(**Structured Surgical Safety Protocol**) + +•   Risk minimized through process, not luck + +**[Patient Guidance]** + +(**Fear-Reduction Decision Support**) + +•   Clear answers before irreversible choices + +**[Location Advantage]** + +(**Gangnam Nonhyeon Medical Hub**) + +•   Access to Korea’s top medical infrastructure + +8. Content & Automation Readiness Check + +| | | | +|---|---|---| +|**Tag**|**콘텐츠 테마**|**활용**| +|Surgical Authority|원장 판단 설명|숏폼 / 인터뷰| +|Consultation Logic|상담 시뮬레이션|유튜브 / 릴스| +|Result Predictability|결과 예측 사례|전환 랜딩| +|Safety Protocol|수술 시스템 공개|신뢰 콘텐츠| +|Fear Reduction|고민 질문 답변|예약 유도| +|Location Authority|강남 신뢰 스토리|브랜딩| + +➡️ 콘텐츠 **=** 설득 자동화 장치 + +9. Nearby Attractions – Stay & Trust Storytelling + +**[Nearby Attractions]** + +•   가로수길 +약 5분 + +◦   Premium lifestyle & recovery-friendly walks + +•   압구정 로데오 +약 7분 + +◦   High-end beauty & fashion district + +•   청담동 명품거리 +약 10분 + +◦   Korea’s premium aesthetic epicenter + +➡️ 의료 **+** 라이프스타일 **=** 프리미엄 신뢰 서사 + +최종 한 줄 요약 (원장용) + +뷰성형외과는 **“**잘한다**”**를 말할 필요가 없다**. +**환자의 결정을 대신해줄 수 있는 병원이라는 점만 증명하면 된다**.** + +다음으로 바로 갈 수 있는 선택지는 세 가지야: +1️⃣ 이 리포트를 원장 발표용 슬라이드로 재구성 +2️⃣ Key Selling Point 기준으로 숏폼 콘텐츠 **30**개 자동 설계 +3️⃣ 상담 → 예약 전환율을 높이는 랜딩 **& CTA** 구조 설계 + +어디부터 실행할까? + +인스타그램과 페이스북에 올릴 릴스 콘텐츠를 이 분석 내용에 기반해서 기획, 비주얼요소(병원에서 제공한 마케팅 활용가능 비포애프터사진, 병원 내부 및 진료진 프로파일 등 소스), 텍스트(모션 그래픽 연출 포함), 위 제시한 다양한 콘텐츠 앵글 적용한 버전으로 제시, 내러티브 사용과 홍보성 노래 사용 두가지로 크게 나눈뒤 콘텐츠 세분화 + +좋아. 이제부터는 **“실제 집행 가능한 릴스 설계 문서”**로 간다. +아이디어 나열 말고, 촬영 → 편집 → 업로드까지 바로 이어지는 수준으로 정리할게. +(광고/유기 도달 모두 대응, 신환 예약 **KPI** 기준) + +아래는 뷰성형외과 +마케팅 인텔리전스 분석을 그대로 릴스 구조로 번역한 결과다. + +Instagram / Facebook Reels 콘텐츠 기획안 + +(Based on Marketing Intelligence Report) + +전체 구조 개요 + +콘텐츠 대분류 **(2** 트랙**)** +1️⃣ **Narrative** 기반 콘텐츠 → 신뢰·결정 보조 +2️⃣ **Promotional Music** 기반 콘텐츠 → 도달·관심 유도 + +각 트랙 안에서 +→ **Key Selling Point(USP) ×** 콘텐츠 앵글 **×** 비주얼 소스로 세분화 + +1️⃣ Narrative 기반 Reels + +🎯 목적: “여기서 해도 되겠다”라는 결정 확신 + +A-1. 상담 신뢰형 (Fear Reduction) + +🎥 콘텐츠 컨셉 + +**“**수술을 권하기 전에**,** 판단을 설명합니다**”** + +▸ 비주얼 소스 + +•   원장 상담 장면 (정면 or 측면 실루엣) + +•   상담실 내부 컷 (차분, 여백) + +•   얼굴 라인 스케치 / 시뮬레이션 일부 (과하지 않게) + +•   비포/애프터는 짧게**,** 흐릿한 전환으로 사용 + +▸ 내러티브 **(**자막 **+** 음성 가능**)** + +**Hook (0–3s)** + +“성형에서 가장 무서운 건, +설명 없이 결정되는 순간입니다.” + +**Body (4–****12s)** + +“왜 이 수술을 해야 하는지 +왜 이 정도가 적당한지 +그걸 먼저 설명합니다.” + +**Close (13–18s)** + +“결정은, 그 다음입니다.” + +▸ 모션 그래픽 텍스트 + +•   “Decision before Surgery” + +•   “Outcome-first consultation” + +•   타이핑 효과 or 페이드 인 + +▸ **CTA** + +•   “상담 예약하기” + +•   “원장 상담으로 시작하세요” + +A-2. 결과 예측형 (Outcome Predictability) + +🎥 콘텐츠 컨셉 + +**“**이 병원은 결과를 **‘**말**’**로 설명합니다**”** + +▸ 비주얼 소스 + +•   동일 케이스 비포/애프터 (정면 고정) + +•   애프터 컷은 짧게**,** 과도한 강조 금지 + +•   원장 설명 컷 삽입 + +▸ 내러티브 + +**Hook** + +“전후 사진만 보면, +불안해지는 이유가 있습니다.” + +**Body** + +“중요한 건 +어떤 결과를 목표로 했는지입니다.” + +**Close** + +“결과가 설명되는 수술.” + +▸ 텍스트 오버레이 + +•   “Predictable outcome” + +•   “No overcorrection” + +•   얇은 라인 그래픽 + +A-3. 재수술·고난이도 신뢰형 + +🎥 콘텐츠 컨셉 + +**“**다시 손대는 수술은**,** 더 조심해야 합니다**”** + +▸ 비주얼 소스 + +•   차분한 상담실 컷 + +•   원장 클로즈업 + +•   Before는 최소화, After는 디테일 컷 + +▸ 내러티브 + +“이미 한 번의 선택이 +쉽지 않았다는 걸 알고 있습니다.” + +“그래서 더 많은 설명이 필요합니다.” + +▸ **CTA** + +•   “재수술 상담” + +•   “고난이도 케이스 상담” + +2️⃣ Promotional Music 기반 Reels + +🎯 목적: 도달 **+** 첫 관심 유도 + +(유행 BGM / 저작권 문제 없는 홍보성 음악) + +B-1. 비포애프터 임팩트형 + +🎥 콘텐츠 컨셉 + +**“**과하지 않은 변화**”** + +▸ 비주얼 소스 + +•   동일 구도 비포 → 애프터 + +•   컷당 0.8~1.2초 + +•   자연광, 무표정 기준 + +▸ 텍스트 **(Full Sentence** ❌**)** + +•   “Natural balance” + +•   “No overdone look” + +•   “Designed, not rushed” + +▸ 편집 포인트 + +•   비트에 맞춘 컷 전환 + +•   줌·왜곡 효과 ❌ + +B-2. 병원 공간 & 무드형 + +🎥 콘텐츠 컨셉 + +**“**강남이지만**,** 조용한 이유**”** + +▸ 비주얼 소스 + +•   병원 내부 인테리어 + +•   대기 공간, 상담실, 복도 + +•   의료기기 디테일 컷 + +▸ 텍스트 + +•   “Private” + +•   “Calm” + +•   “Decision-focused clinic” + +▸ 활용 + +•   브랜드 무드 각인 + +•   광고용 크리에이티브에 최적 + +B-3. 의료진 프로파일형 + +🎥 콘텐츠 컨셉 + +**“**유행보다 판단**”** + +▸ 비주얼 소스 + +•   원장 정면 or 측면 + +•   진료 중 짧은 컷 + +•   수술 전 체크 장면 + +▸ 텍스트 + +•   “Director-led surgery” + +•   “Experience matters” + +•   “Results over trends” + +콘텐츠 앵글 × Key Selling Point 매핑 + +| | | +|---|---| +|**Key Selling Point**|**콘텐츠 타입**| +|Surgical Authority|A-1, B-3| +|Consultation Logic|A-1| +|Result Predictability|A-2, B-1| +|Safety Protocol|A-1| +|Fear Reduction|A-1, A-3| +|Gangnam Authority|B-2| + +업로드 운영 가이드 (핵심) + +•   **Narrative:** 주 2–3회 (저장·상담 전환 목적) + +•   **Music** 기반**:** 주 3–4회 (도달·리타겟 풀 확장) + +•   광고 집행 시 +→ Narrative = 리타겟 +→ Music = 신규 유입 + +마지막으로, 솔직하게 말하면 + +이 구조의 핵심은 **“자랑하지 않는 홍보”**야. +뷰성형외과는 +👉 보여주면 되는 병원이 아니라 +👉 설명해주면 선택되는 병원이야. \ No newline at end of file diff --git a/PRD.md b/PRD.md new file mode 100644 index 0000000..55a960f --- /dev/null +++ b/PRD.md @@ -0,0 +1,362 @@ +# INFINITH — Infinite Marketing Platform +## Product Requirements Document (PRD) +**Version:** 2.0 +**Date:** 2026-03-23 +**Author:** Product Owner, Vertical AI SaaS +**Status:** Phase 1 MVP Implemented (UI/UX Reference) + +--- + +## 1. Executive Summary + +INFINITH는 프리미엄 의료 비즈니스(피부과, 성형외과, 치과 등)와 병원 마케팅 대행사를 위한 **AI-Powered Marketing Automation SaaS**이다. 핵심 가치는 **AGDP Cycle** — Analysis → Generation → Distribution → Performance — 의 자율 순환 엔진을 통해 콘텐츠 품질(CTR)을 지속적으로 최적화하는 **Self-Improving Marketing Engine**을 제공하는 것이다. + +### 1.1 현재 구현 상태 (v2.0 기준) + +| 구분 | 상태 | 설명 | +|------|------|------| +| 랜딩 페이지 | **완료** | 7개 섹션, 반응형, 애니메이션 | +| 마케팅 분석 리포트 | **UI 완료** | 11개 섹션, 하드코딩 데이터 (뷰성형외과) | +| 콘텐츠 기획 페이지 | **UI 완료** | 8개 섹션, 하드코딩 데이터 | +| PDF 내보내기 | **완료** | html2canvas + jsPDF, 섹션 단위 분할 | +| 페이지 네비게이터 | **완료** | 3페이지 간 이동 (랜딩 ↔ 분석 ↔ 기획) | +| 에셋 업로드 | **UI 완료** | 드래그&드롭, 이미지/영상/텍스트 | +| 디자인 시스템 | **확립** | 컬러, 타이포, 아이콘, 컴포넌트 패턴 | +| 백엔드/인증/결제 | **미구현** | Phase 2에서 개발 예정 | + +--- + +## 2. Problem Statement + +### 2.1 고객 Pain Points +| # | Pain Point | Impact | +|---|-----------|--------| +| 1 | 콘텐츠 생산의 한계 | 블로그, SEO, 유튜브, 숏폼 — 인력/비용/시간 부족 | +| 2 | 영상 제작 비용 | 촬영·편집·기획 고비용, 숏폼 지속 제작 불가 | +| 3 | 데이터 기반 마케팅 부재 | 키워드·채널·콘텐츠 성과를 통합 파악 불가 | + +### 2.2 Market Gap +- 기존 마케팅 자동화 툴(HubSpot, Hootsuite 등)은 **한국 의료 마케팅** 특화 기능 부재 +- 네이버 블로그/플레이스/카페 등 **한국 로컬 플랫폼** 지원 미비 +- 의료광고 심의·규정 준수 자동화 없음 +- 텍스트→영상 자동 변환(숏폼) 파이프라인 부재 + +--- + +## 3. Target Users & Personas + +### Persona 1: 프리미엄 의료 비즈니스 원장 / 마케팅 담당자 +- **Who:** 피부과, 성형외과, 치과, 안과, 헬스케어 클리닉 +- **Need:** 검색 상위 노출, 환자 전환율 증가, 고품질 영상 대량 확대 +- **Behavior:** 월 100~500만원 마케팅 예산, 1-2명 내부 마케팅 인력 +- **Goal:** 비용 대비 ROI 극대화, 자연 검색 유입 확대 + +### Persona 2: 병원 마케팅 대행사 +- **Who:** 콘텐츠 마케팅 Agency, 영상 마케팅 Agency, 광고 운영 대행사 +- **Need:** 다수 클라이언트 통합 운영, 콘텐츠 생산성 극대화 +- **Behavior:** 10~50개 병원 클라이언트 관리, 3-10명 팀 +- **Goal:** 인건비 절감, 영상 제작 자동화, 성과 리포팅 효율화 + +--- + +## 4. Product Vision & AGDP Cycle + +``` +┌─────────────────────────────────────────────────┐ +│ AGDP Self-Improving Cycle │ +│ │ +│ ┌───────────┐ ┌───────────┐ │ +│ │ Analysis │──────▶│ Generation│ │ +│ │ (분석) │ │ (생성) │ │ +│ └─────▲─────┘ └─────┬─────┘ │ +│ │ │ │ +│ │ ▼ │ +│ ┌─────┴─────┐ ┌───────────┐ │ +│ │Performance│◀──────│Distribution│ │ +│ │ (성과) │ │ (배포) │ │ +│ └───────────┘ └───────────┘ │ +│ │ +│ 매 사이클마다 Performance 데이터가 Analysis에 │ +│ 피드백되어 콘텐츠 품질 자율 최적화 │ +└─────────────────────────────────────────────────┘ +``` + +--- + +## 5. 구현된 페이지 구조 & 사용자 흐름 + +### 5.1 Page Flow + +``` +Landing (/) + └─ URL 입력 → "Analyze Marketing Performance" 클릭 + └─ Analysis Loading (/report/loading) + └─ 5단계 체크리스트 애니메이션 (5.5초) + └─ Marketing Intelligence Report (/report/:id) + └─ "마케팅 기획" 버튼 클릭 + └─ Marketing Execution Plan (/plan/:id) +``` + +**PageNavigator:** 하단 고정 pill-shaped 바로 3페이지 간 자유 이동 + +### 5.2 Landing Page — 7 Sections + +| # | Component | 배경 | 핵심 기능 | +|---|-----------|------|----------| +| 1 | Hero | 라이트 (radial gradient) | URL 입력 → 분석 시작 CTA | +| 2 | TargetAudience | 화이트 | 2개 glass-card (병원 / 대행사) | +| 3 | Problems | 슬레이트 | 3가지 핵심 과제 카드 | +| 4 | Solution | **다크** (`#0A1128`) | AGDP 순환 다이어그램 (SVG 애니메이션) | +| 5 | Modules | 화이트 + 블롭 | 5개 모듈 카드 (Pentagon 레이아웃) | +| 6 | UseCases | 화이트 | 병원/대행사 유즈케이스 비교 | +| 7 | CTA | **다크** (`#0A1128`) | URL 입력 CTA (따뜻한 그라디언트 인풋) | + +### 5.3 Marketing Intelligence Report — 11 Sections + +| # | Component | 배경 | 데이터 소스 (Phase 2) | +|---|-----------|------|---------------------| +| 1 | ReportHeader | 파스텔 radial gradient | 종합 스코어, 의원 정보 | +| 2 | ClinicSnapshot | 라이트 | 의원 프로필, 의료진, 인증, 브랜드 컬러 | +| 3 | ChannelOverview | 라이트 | 6채널 스코어링 (ScoreRing + SeverityBadge) | +| 4 | YouTubeAudit | 라이트 | 구독자, 조회수, 인기 영상, 진단 | +| 5 | InstagramAudit | 라이트 | KR/EN 계정 분리 분석, Reels 진단 | +| 6 | FacebookAudit | 라이트 | 페이지 분석, 브랜드 일관성 맵, 통합 제안 | +| 7 | OtherChannels | 라이트 | 강남언니, 네이버, 웹사이트, 트래킹 픽셀 | +| 8 | ProblemDiagnosis | **다크** | 핵심 문제 진단 (severity별 분류) | +| 9 | TransformationProposal | 라이트 | As-Is → To-Be 5탭 (브랜드/콘텐츠/플랫폼/웹/신채널) | +| 10 | RoadmapTimeline | 라이트 | 90일 로드맵 (월별 태스크 체크리스트) | +| 11 | KPIDashboard | 라이트 | KPI 테이블 + CTA (PDF 다운로드 + 마케팅 기획) | + +### 5.4 Marketing Execution Plan — 8 Sections + +| # | Component | 배경 | 기능 | +|---|-----------|------|------| +| 1 | PlanHeader | 파스텔 radial gradient | 90 Days 배지, 의원 정보 | +| 2 | BrandingGuide | 라이트 | 4탭: 비주얼/톤앤매너/채널/일관성 | +| 3 | ChannelStrategy | **다크** | 7채널 전략 카드 (화이트 카드 on 다크 bg) | +| 4 | ContentStrategy | 라이트 | 4탭: 필러/유형/워크플로우/리퍼포징 | +| 5 | ContentCalendar | **다크** | 주간 캘린더 (화이트 카드 on 다크 bg) | +| 6 | AssetCollection | 라이트 | 에셋 카드 + 유튜브 리퍼포징 | +| 7 | MyAssetUpload | 라이트 | 드래그&드롭 업로드 (이미지/영상/텍스트) | +| 8 | PlanCTA | 독립 섹션 | 따뜻한 그라디언트 CTA + PDF 다운로드 | + +--- + +## 6. Core Modules — 기능 요구사항 + +### Module 1: Marketing Intelligence (Analysis) — Phase 1 MVP + +| Feature | Description | Priority | 구현 상태 | +|---------|-------------|----------|----------| +| URL 기반 초기 진단 | URL 입력 → 종합 분석 리포트 자동 생성 | P0 | **UI 완료** (하드코딩) | +| 채널별 스코어링 | YouTube/Instagram/Facebook/강남언니/웹사이트 점수화 | P0 | **UI 완료** | +| 브랜드 일관성 분석 | 채널 간 로고/컬러/네이밍 불일치 진단 | P0 | **UI 완료** | +| 문제 진단 | severity 기반 핵심 과제 도출 | P0 | **UI 완료** | +| As-Is → To-Be 변환 전략 | 5개 영역별 전환 제안 | P0 | **UI 완료** | +| 90일 로드맵 | 월별 실행 계획 | P0 | **UI 완료** | +| KPI 대시보드 | 3개월/12개월 목표 지표 | P0 | **UI 완료** | +| 스크린샷 근거 자료 | 분석 과정 캡쳐 → 리포트 첨부 | P1 | **컨텍스트 구축 완료** | +| PDF 내보내기 | 리포트/기획서 PDF 다운로드 | P0 | **구현 완료** | + +### Module 2: Content Planning (Generation 준비) + +| Feature | Description | Priority | 구현 상태 | +|---------|-------------|----------|----------| +| 브랜딩 가이드 빌드 | 컬러/타이포/톤앤매너/채널별 규칙 | P0 | **UI 완료** | +| 채널 전략 수립 | 7채널별 As-Is→To-Be, 콘텐츠 유형, 게시 빈도 | P0 | **UI 완료** | +| 콘텐츠 전략 | 필러/유형 매트릭스/워크플로우/리퍼포징 | P0 | **UI 완료** | +| 콘텐츠 캘린더 | 4주 주간 캘린더 + 월간 서머리 | P0 | **UI 완료** | +| 에셋 수집 | 홈페이지/SNS/유튜브 리퍼포징 가능 에셋 목록 | P0 | **UI 완료** | +| My Asset 업로드 | 사용자 에셋 업로드 (이미지/영상/텍스트) | P1 | **UI 완료** | + +### Module 3~5: 미구현 (Phase 2+) + +- Content Creation (AI 콘텐츠 생성 + Human-in-the-Loop) +- Video Automation (블로그→영상 변환) +- Distribution Engine (자동 배포) +- Performance Intelligence (성과 추적) + +--- + +## 7. 디자인 시스템 + +> 전체 디자인 시스템 스펙: **`docs/DESIGN_SYSTEM.md`** 참조 + +### 7.1 핵심 원칙 +- **No primary colors** — 빨강, 주황, 초록, 파랑 등 원색 사용 금지 +- **Pastel semantic palette** — 상태 표현은 반드시 INFINITH 파스텔 팔레트 사용 +- **Filled icons only** — 라인 아이콘 금지, SVG shape fill 스타일만 사용 +- **No emoji** — 아이콘은 벡터 shape 전용 +- **Dark/Light rhythm** — 섹션 교대로 다크(`#0A1128`)/라이트(`white`) 배경 +- **White cards on dark** — 다크 섹션 내 카드는 `bg-white rounded-2xl shadow` 사용 (glass 아님) + +### 7.2 INFINITH 브랜드 컬러 +| Token | Hex | 용도 | +|-------|-----|------| +| Primary Navy | `#0A1128` | 다크 섹션 bg, 본문 텍스트 | +| Gradient Start | `#4F1DA1` | 버튼/배지 그라디언트 시작 | +| Gradient End | `#021341` | 버튼/배지 그라디언트 끝 | +| Accent Purple | `#6C5CE7` | 강조, 활성 탭 보더, 아이콘 | +| Warm Peach | `#fff3eb` | CTA 그라디언트 시작 | +| Soft Lilac | `#e4cfff` | CTA 그라디언트 중간 | +| Cool White-Blue | `#f5f9ff` | CTA 그라디언트 끝 | + +### 7.3 타이포그래피 +| 용도 | 폰트 | Weight | 크기 | +|------|-------|--------|------| +| 영문 헤딩 | Playfair Display | Bold 700 | 3xl~6xl | +| 한글 헤딩 | Pretendard | Bold 700 | 2xl~4xl | +| 본문 | Pretendard / Inter | Regular 400 | sm~base | +| INFINITH 로고 | Playfair Display | Black 900 | 3xl, `tracking-[0.05em]` | + +--- + +## 8. External API/Connector 요구사항 + +| Category | Connector | Purpose | Priority | +|----------|-----------|---------|----------| +| 소셜미디어 스크래핑 | **Apify** | Instagram/Facebook/YouTube/TikTok 스크래핑, 스크린샷, 에셋 다운로드 | **P0** | +| 웹 스크래핑 | **Firecrawl** | 웹사이트 분석, 콘텐츠 추출 | P0 (크레딧 충전 필요) | +| YouTube | YouTube Data API | 채널 통계, 영상 목록 | P0 | +| SEO | **Ahrefs API** | 키워드 분석, 도메인 레이팅 | P1 (연결됨) | +| 한국 로컬 | Naver Search/Place API | 블로그/플레이스 분석 | P0 | +| AI | Claude API (Anthropic) | 콘텐츠 분석, 리포트 생성 | P0 | +| 영상 생성 | Creatomate API | 프로그래머블 영상 생성 | P1 | +| 결제 | Stripe + Toss Payments | 글로벌 + 국내 결제 | P1 | + +--- + +## 9. Technical Architecture + +### 9.1 현재 스택 (Phase 1 — 프론트엔드 레퍼런스) + +| Layer | Technology | +|-------|-----------| +| Frontend | React 19 + Vite 6 + TypeScript | +| Styling | Tailwind CSS 4 + 커스텀 디자인 시스템 | +| Routing | React Router 7 | +| Animation | Motion (Framer Motion) | +| Icons | Lucide React + 커스텀 FilledIcons (SVG) | +| PDF Export | html2canvas-pro + jsPDF | +| Data | 하드코딩 mock data (`src/data/`) | + +### 9.2 목표 스택 (Phase 2 — 풀스택 SaaS) + +| Layer | Technology | Rationale | +|-------|-----------|-----------| +| Frontend | Next.js 15 (App Router) | SSR/SSG, SEO 최적화 | +| Styling | Tailwind CSS + 현재 디자인 시스템 유지 | 마이그레이션 최소화 | +| Backend | Next.js API Routes + tRPC | 풀스택 타입 안전성 | +| Database | Supabase (PostgreSQL) | Auth + DB + Storage 통합 | +| Auth | Supabase Auth + NextAuth.js | Kakao/Google OAuth | +| AI Orchestration | LangGraph / Claude Agent SDK | 멀티 에이전트 | +| Queue | Inngest or Trigger.dev | 비동기 AI 작업 처리 | +| Hosting | Vercel | Next.js 최적화 | + +### 9.3 Data Model + +현재 TypeScript 타입 정의가 **API 계약서** 역할: +- `src/types/report.ts` — MarketingReport 및 모든 하위 타입 +- `src/types/plan.ts` — MarketingPlan 및 모든 하위 타입 + +``` +Organization (워크스페이스) +├── Members (User + Role) +├── Subscription (Plan + Usage) +├── Projects (마케팅 프로젝트) +│ ├── MarketingReport (분석 리포트) +│ │ ├── ClinicSnapshot +│ │ ├── ChannelScores +│ │ ├── YouTubeAudit / InstagramAudit / FacebookAudit +│ │ ├── ProblemDiagnosis +│ │ ├── TransformationProposal +│ │ └── KPIDashboard +│ ├── MarketingPlan (콘텐츠 기획) +│ │ ├── BrandGuide +│ │ ├── ChannelStrategy +│ │ ├── ContentStrategy +│ │ ├── ContentCalendar +│ │ └── AssetCollection +│ └── UserAssets (업로드된 에셋) +├── Channels (연동 채널) +└── AuditLogs +``` + +--- + +## 10. Development Phases + +### Phase 0: Foundation (완료 항목 + 잔여) +- [x] 랜딩 페이지 7개 섹션 +- [x] 디자인 시스템 확립 (컬러, 타이포, 아이콘, 컴포넌트) +- [x] 마케팅 분석 리포트 UI (11개 섹션) +- [x] 콘텐츠 기획 페이지 UI (8개 섹션) +- [x] PDF 내보내기 +- [x] 페이지 네비게이터 +- [x] 에셋 업로드 UI +- [x] 스크린샷 근거 자료 컨텍스트 (ScreenshotProvider) +- [ ] Next.js 마이그레이션 +- [ ] CI/CD 파이프라인 + +### Phase 1: Backend + 동적 데이터 (4주) +- [ ] Supabase 연동 (Auth + DB + Storage) +- [ ] Apify 연동 → 소셜미디어 스크래핑 파이프라인 +- [ ] YouTube Data API 연동 +- [ ] Naver Search/Place API 연동 +- [ ] 하드코딩 데이터 → 실제 API 교체 +- [ ] 스크린샷 캡쳐 → 리포트 자동 첨부 + +### Phase 2: AI Agent + Content Creation (4주) +- [ ] Claude API 연동 → AI 분석 리포트 생성 +- [ ] 콘텐츠 기획 AI 자동 생성 +- [ ] Human-in-the-Loop 에디터 +- [ ] 의료광고 규정 검수 Agent + +### Phase 3: Billing + Multi-tenancy (4주) +- [ ] 인증 시스템 (Email, Google, Kakao OAuth) +- [ ] Stripe + Toss Payments 결제 +- [ ] 에이전시 멀티 클라이언트 모드 +- [ ] RBAC (Owner/Admin/Editor/Viewer) + +### Phase 4~6: Distribution, Performance, Growth +- (기존 PRD v1.0 Phase 4~6 유지) + +--- + +## 11. Pricing Strategy + +| Plan | Target | Price (Monthly) | Limits | +|------|--------|----------------|--------| +| **Free** | Trial | ₩0 | URL 분석 1회, 블로그 3개/월, 영상 0 | +| **Starter** | 개인 병원 | ₩99,000 | 블로그 20개/월, 영상 5개/월, 채널 3개 | +| **Professional** | 중형 병원 | ₩299,000 | 블로그 50개/월, 영상 20개/월, 채널 무제한, 팀 5명 | +| **Agency** | 대행사 | ₩699,000 | 클라이언트 10개, 블로그 200개/월, 영상 50개/월, 팀 무제한 | +| **Enterprise** | 대형 대행사 | Custom | 무제한, SSO, 전용 지원, SLA | + +--- + +## 12. Success Metrics (KPIs) + +| Metric | Target (6개월) | Target (12개월) | +|--------|---------------|-----------------| +| MAU | 200 | 1,000 | +| Paid Conversion Rate | 5% | 8% | +| MRR | ₩20M | ₩100M | +| Churn Rate | < 8% | < 5% | +| AI 초안 승인율 | > 60% | > 75% | +| 평균 리포트 생성 시간 | < 5분 | < 2분 | + +--- + +## 13. 관련 문서 + +| 문서 | 경로 | 용도 | +|------|------|------| +| **Design System Spec** | `docs/DESIGN_SYSTEM.md` | 컬러, 타이포, 아이콘, 컴포넌트 규칙 | +| **TypeScript 타입 정의** | `src/types/report.ts`, `src/types/plan.ts` | API 계약서 | +| **Mock 데이터** | `src/data/mockReport.ts`, `src/data/mockPlan.ts` | 레퍼런스 데이터 구조 | +| **커스텀 아이콘** | `src/components/icons/FilledIcons.tsx` | Filled SVG 아이콘 세트 | + +--- + +*This PRD is a living document. Updated as product decisions are made and user feedback is incorporated.* diff --git a/README.md b/README.md new file mode 100644 index 0000000..5455a7f --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +
+GHBanner +
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/d446f5f5-e089-404a-93ce-32a72f2f5108 + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/docs/API_CONNECTORS.md b/docs/API_CONNECTORS.md new file mode 100644 index 0000000..416595d --- /dev/null +++ b/docs/API_CONNECTORS.md @@ -0,0 +1,262 @@ +# INFINITH — API & MCP Connector Registry +**Version:** 1.0 | **Updated:** 2026-03-24 | **Status:** Backend Development Ready + +--- + +## 1. Currently Connected (이미 연결됨) + +| Service | Purpose | Status | Dashboard | +|---------|---------|--------|-----------| +| [Firecrawl](https://docs.firecrawl.dev/introduction) | 웹사이트 스크래핑, 콘텐츠 추출, 스크린샷 | **Credits 소진** | [Recharge](https://www.firecrawl.dev/pricing) | +| [Ahrefs API](https://docs.ahrefs.com/docs/api/reference/introduction) | 키워드 분석, 백링크, 도메인 레이팅 | **Connected** | [Dashboard](https://ahrefs.com/dashboard) | +| [Figma MCP](https://developers.figma.com/) | 디자인 에셋 읽기, 브랜드 변수 추출 | **Connected** | [Developer](https://developers.figma.com/) | +| [Slack MCP](https://api.slack.com/) | 팀 알림, 채널 메시징 | **Connected** | [API](https://api.slack.com/) | +| [Notion MCP](https://developers.notion.com/) | 문서/DB 관리, 리포트 저장 | **Connected** | [Integrations](https://www.notion.so/my-integrations) | +| [Google Drive](https://console.cloud.google.com/) | 파일 저장/공유 | **Connected** | [Console](https://console.cloud.google.com/) | +| [Gemini (nano-banana-pro)](https://aistudio.google.com/) | AI 이미지 생성/편집 | **Connected** | [AI Studio](https://aistudio.google.com/) | + +--- + +## 2. P0 — 즉시 연결 필요 (Phase 1 Backend) + +### 2.1 Apify — 소셜미디어 통합 스크래핑 +| Item | Link | +|------|------| +| **가입** | https://console.apify.com/sign-up | +| **API 문서** | https://docs.apify.com/api/v2 | +| **가격** | https://apify.com/pricing | +| **MCP 서버 (GitHub)** | https://github.com/apify/apify-mcp-server | +| **MCP 서버 (npm)** | `npx @apify/actors-mcp-server` | +| **MCP 연동 가이드** | https://docs.apify.com/platform/integrations/mcp | +| **용도** | Instagram/Facebook/YouTube/TikTok 프로필 스크래핑, 스크린샷 배치 캡쳐, 에셋 다운로드 | +| **예상 비용** | $49/mo (Starter) — 100 Actor runs/day | + +### 2.2 YouTube Data API v3 — 채널 분석 +| Item | Link | +|------|------| +| **API 활성화** | https://console.cloud.google.com/apis/library/youtube.googleapis.com | +| **시작 가이드** | https://developers.google.com/youtube/v3/getting-started | +| **API 레퍼런스** | https://developers.google.com/youtube/v3/docs | +| **MCP 서버** | https://github.com/sparfenyuk/mcp-youtube (`npx yt-mcp`) | +| **용도** | 채널 통계, 구독자, 영상 목록, 조회수, 댓글 분석 | +| **예상 비용** | 무료 (일 10,000 units quota) | + +### 2.3 Naver Search API — 한국 로컬 검색 +| Item | Link | +|------|------| +| **개발자 센터** | https://developers.naver.com/ | +| **API 목록** | https://naver.github.io/naver-openapi-guide/apilist.html | +| **MCP 서버 (GitHub)** | https://github.com/isnow890/naver-search-mcp | +| **MCP 서버 (npm)** | `npx @isnow890/naver-search-mcp` | +| **Naver Shopping Insight MCP** | https://github.com/dataartai/naver-api-mcp | +| **용도** | 블로그/카페/뉴스 검색, 키워드 볼륨 분석 | +| **예상 비용** | 무료 (일 25,000건) | + +### 2.4 Supabase — 백엔드 인프라 (Auth + DB + Storage) +| Item | Link | +|------|------| +| **Dashboard** | https://supabase.com/dashboard | +| **문서** | https://supabase.com/docs | +| **MCP 서버 (GitHub)** | https://github.com/supabase-community/supabase-mcp | +| **MCP 서버 (npm)** | `npx @supabase/mcp-server-supabase` | +| **MCP 가이드** | https://supabase.com/docs/guides/getting-started/mcp | +| **용도** | PostgreSQL DB, 인증(OAuth), 파일 스토리지, Realtime | +| **예상 비용** | 무료 (Free tier: 500MB DB, 1GB Storage) | + +### 2.5 Claude / Anthropic API — AI 엔진 +| Item | Link | +|------|------| +| **Console (API Keys)** | https://console.anthropic.com/ | +| **API 문서** | https://docs.anthropic.com/en/docs/get-started | +| **Platform 문서** | https://platform.claude.com/docs/en/home | +| **용도** | 마케팅 분석 리포트 AI 생성, 콘텐츠 생성, 전략 수립 | +| **예상 비용** | Usage-based (Sonnet: $3/1M input, $15/1M output) | + +--- + +## 3. P1 — Phase 2 연결 (AI Agent + Content Creation) + +### 3.1 Perplexity API — AI 검색 + AEO +| Item | Link | +|------|------| +| **가입** | https://www.perplexity.ai/api-platform | +| **API Key 설정** | https://www.perplexity.ai/settings/api | +| **문서** | https://docs.perplexity.ai/ | +| **가격** | https://docs.perplexity.ai/docs/getting-started/pricing | +| **MCP 서버 (공식)** | https://github.com/perplexityai/modelcontextprotocol | +| **MCP 서버 (npm)** | `npx @perplexity-ai/mcp-server` | +| **용도** | AI 기반 웹 검색, AEO(Answer Engine Optimization) 최적화, 트렌드 분석 | + +### 3.2 Creatomate API — 영상/이미지 자동 생성 +| Item | Link | +|------|------| +| **가입** | https://creatomate.com/ | +| **API 문서** | https://creatomate.com/docs/api/introduction | +| **가격** | https://creatomate.com/pricing | +| **Developer Hub** | https://creatomate.com/developers | +| **용도** | 템플릿 기반 영상/이미지 생성, 숏폼 자동 제작, 소셜 포맷별 리사이즈 | +| **예상 비용** | $49/mo (Pro: 월 100 renders) | + +### 3.3 Instagram / Facebook Graph API — 공식 게시/분석 +| Item | Link | +|------|------| +| **Meta Developer Portal** | https://developers.facebook.com/ | +| **앱 생성** | https://developers.facebook.com/apps/ | +| **Instagram API 문서** | https://developers.facebook.com/docs/instagram-platform/ | +| **Instagram API Reference** | https://developers.facebook.com/docs/instagram-platform/reference/ | +| **Facebook Graph API 문서** | https://developers.facebook.com/docs/graph-api/ | +| **Graph API Get Started** | https://developers.facebook.com/docs/graph-api/get-started/ | +| **용도** | SNS 자동 게시, Reels 업로드, 인사이트 데이터 수집 | +| **예상 비용** | 무료 | + +### 3.4 Google Search Console API — SEO 성과 +| Item | Link | +|------|------| +| **API 활성화** | https://console.cloud.google.com/apis/library/searchconsole.googleapis.com | +| **문서** | https://developers.google.com/webmaster-tools | +| **용도** | 검색 노출량, CTR, 키워드 순위 변동 추적 | +| **예상 비용** | 무료 | + +### 3.5 Google Analytics 4 API — 웹 트래픽 +| Item | Link | +|------|------| +| **API 활성화** | https://console.cloud.google.com/apis/library/analyticsdata.googleapis.com | +| **Quickstart** | https://developers.google.com/analytics/devguides/reporting/data/v1/quickstart | +| **용도** | 웹사이트 유입 경로, 전환 데이터, 세션 분석 | +| **예상 비용** | 무료 | + +--- + +## 4. P2 — 고도화 단계 + +### 4.1 Naver Place / Map API +| Item | Link | +|------|------| +| **Naver Cloud** | https://www.ncloud.com/ | +| **Map API** | https://www.ncloud.com/v2/product/applicationService/maps | +| **API 문서** | https://api.ncloud-docs.com/docs/common-ncpapi | +| **MCP 서버** | https://github.com/num2k/naver-map-mcp | +| **용도** | 플레이스 정보, 병원 리뷰, 위치 데이터 | + +### 4.2 Google Maps Places API +| Item | Link | +|------|------| +| **API 활성화** | https://console.cloud.google.com/google/maps-apis/overview | +| **API Key 발급** | https://developers.google.com/maps/documentation/places/web-service/get-api-key | +| **Places API 문서** | https://developers.google.com/maps/documentation/places/web-service/overview | +| **용도** | 구글 리뷰, 평점, 위치 정보, 경쟁 병원 분석 | +| **예상 비용** | $17/1,000 requests | + +### 4.3 Google Vision API +| Item | Link | +|------|------| +| **API 활성화** | https://console.cloud.google.com/apis/library/vision.googleapis.com | +| **문서** | https://docs.cloud.google.com/vision/docs/setup | +| **가격** | https://cloud.google.com/vision/pricing | +| **용도** | 로고 감지, 브랜드 컬러 자동 추출, 이미지 분석 | +| **예상 비용** | $1.50/1,000 images | + +### 4.4 Brandfetch API +| Item | Link | +|------|------| +| **가입** | https://developers.brandfetch.com/register | +| **문서** | https://docs.brandfetch.com/ | +| **Brand API** | https://docs.brandfetch.com/brand-api/overview | +| **가격** | https://brandfetch.com/developers/pricing | +| **용도** | 도메인 → 브랜드 로고/컬러/폰트 자동 추출 | + +### 4.5 TikTok API +| Item | Link | +|------|------| +| **Developer Portal** | https://developers.tiktok.com/ | +| **문서** | https://developers.tiktok.com/doc/overview | +| **Business API** | https://business-api.tiktok.com/portal | +| **Business API 가입** | https://business-api.tiktok.com/portal/developer/register | +| **용도** | TikTok 영상 게시, 계정 통계, 트렌드 분석 | + +### 4.6 Canva Connect API +| Item | Link | +|------|------| +| **Developer Portal** | https://www.canva.dev/ | +| **문서** | https://www.canva.dev/docs/connect/ | +| **Quickstart** | https://www.canva.dev/docs/connect/quickstart/ | +| **용도** | 템플릿 기반 이미지 Autofill (Enterprise 전용) | + +### 4.7 Puppeteer (서버사이드 스크린샷) +| Item | Link | +|------|------| +| **npm** | https://www.npmjs.com/package/puppeteer | +| **문서** | https://pptr.dev/ | +| **GitHub** | https://github.com/puppeteer/puppeteer | +| **용도** | 서버사이드 웹페이지 스크린샷 캡쳐 (분석 근거 자료) | +| **예상 비용** | 무료 (OSS) | + +--- + +## 5. MCP 서버 Quick Reference + +### 즉시 연결 (P0) +```bash +# Apify — 소셜미디어 스크래핑 통합 +npx @apify/actors-mcp-server + +# Supabase — Auth + DB + Storage +npx @supabase/mcp-server-supabase + +# Naver Search — 한국 로컬 검색 +npx @isnow890/naver-search-mcp + +# YouTube — 채널 분석 +npx yt-mcp +``` + +### Phase 2 연결 (P1) +```bash +# Perplexity — AI 검색 +npx @perplexity-ai/mcp-server + +# Stripe — 글로벌 결제 (B2B 계약 전환 후) +npx @stripe/mcp + +# Naver Map — 플레이스 분석 +# https://github.com/num2k/naver-map-mcp +``` + +### 이미 연결됨 +``` +Firecrawl ── 웹 스크래핑 (크레딧 충전 필요) +Ahrefs ───── SEO/키워드 분석 +Figma ────── 디자인 에셋 +Slack ────── 팀 커뮤니케이션 +Notion ───── 문서/DB +Google Drive ── 파일 관리 +Gemini ───── AI 이미지 생성 +``` + +--- + +## 6. 월간 예상 비용 (MVP 기준) + +| Service | Plan | 월 비용 | 비고 | +|---------|------|--------|------| +| Supabase | Free | ₩0 | 500MB DB, 1GB Storage | +| YouTube Data API | Free | ₩0 | 일 10K units | +| Naver Search API | Free | ₩0 | 일 25K건 | +| Anthropic (Claude) | Usage | ~₩50,000 | 리포트 100건/월 기준 | +| Apify | Starter | ~₩65,000 | $49/mo | +| Firecrawl | Growth | ~₩26,000 | $19/mo | +| Creatomate | Pro | ~₩65,000 | $49/mo (Phase 2) | +| **Phase 1 합계** | | **~₩141,000/mo** | Supabase+YouTube+Naver+Claude+Apify+Firecrawl | +| **Phase 2 합계** | | **~₩206,000/mo** | + Creatomate | + +--- + +## 7. 결제 시스템 + +> **현재 전략: B2B 계약 우선** +> Stripe / Toss Payments 결제 시스템은 B2C 전환 시점에 개발 예정. +> 현재는 수동 청구서 발행으로 진행. + +--- + +*Last updated: 2026-03-24* diff --git a/docs/DESIGN_SYSTEM.md b/docs/DESIGN_SYSTEM.md new file mode 100644 index 0000000..caeb94f --- /dev/null +++ b/docs/DESIGN_SYSTEM.md @@ -0,0 +1,263 @@ +# INFINITH Design System Specification +**Version:** 1.0 +**Date:** 2026-03-23 + +--- + +## 1. Design Principles + +| 원칙 | 설명 | +|------|------| +| **No Primary Colors** | 빨강, 주황, 초록, 파랑 등 원색 사용 금지. 파스텔 변형만 허용 | +| **Filled Icons Only** | 라인(outlined) 아이콘 금지. SVG shape fill 스타일만 사용 | +| **No Emoji** | 모든 아이콘은 벡터 shape으로 표현 | +| **Dark/Light Rhythm** | 페이지 내 섹션은 다크/라이트 배경을 교대 배치 | +| **White Cards on Dark** | 다크 섹션 내 카드는 `bg-white` 사용 (glassmorphism 아님) | +| **Diagonal Shadows** | 카드/요소에 사선 방향 연한 그림자: `shadow-[3px_4px_12px_rgba(0,0,0,0.06)]` | +| **Pastel Semantics** | 상태 표현은 반드시 INFINITH 파스텔 팔레트의 bg/text/border/dot 세트 사용 | + +--- + +## 2. Color System + +### 2.1 INFINITH Brand Colors + +| Token | Hex | CSS Variable | 용도 | +|-------|-----|-------------|------| +| Primary Navy | `#0A1128` | `--color-primary-900` | 다크 섹션 bg, 본문 텍스트, 테이블 헤더 | +| Medium Navy | `#1A2B5E` | `--color-primary-800` | 보조 다크 | +| Near White | `#F4F6FB` | `--color-primary-50` | 밝은 배경 변형 | +| Accent Purple | `#6C5CE7` | `--color-accent` | 활성 탭 보더, 강조 아이콘, 로딩 스피너 | +| Gradient Start | `#4F1DA1` | — | 버튼, 배지 그라디언트 왼쪽 | +| Gradient End | `#021341` | — | 버튼, 배지 그라디언트 오른쪽 | + +### 2.2 Warm CTA Gradient (3-stop) + +```css +background: linear-gradient(to right, #fff3eb, #e4cfff, #f5f9ff); +``` + +| Stop | Hex | 이름 | +|------|-----|------| +| Start | `#fff3eb` | Warm Peach | +| Mid | `#e4cfff` | Soft Lilac | +| End | `#f5f9ff` | Cool White-Blue | + +**사용처:** CTA 카드 배경, URL 입력 필드, KPI CTA 카드 + +### 2.3 Semantic Status Palette + +**규칙:** 상태를 표현할 때 반드시 이 4가지 세트 중 하나를 사용. 원색(빨강/초록) 절대 금지. + +| Status | Background | Text | Border | Dot | 한국어 | +|--------|-----------|------|--------|-----|--------| +| Critical | `#FFF0F0` | `#7C3A4B` | `#F5D5DC` | `#D4889A` | 심각 | +| Warning | `#FFF6ED` | `#7C5C3A` | `#F5E0C5` | `#D4A872` | 주의 | +| Good | `#F3F0FF` | `#4A3A7C` | `#D5CDF5` | `#9B8AD4` | 양호 | +| Info | `#EFF0FF` | `#3A3F7C` | `#C5CBF5` | `#7A84D4` | 우수 | + +**CSS Variables:** +```css +--color-status-{level}-bg +--color-status-{level}-text +--color-status-{level}-border +--color-status-{level}-dot +``` + +### 2.4 ScoreRing Color Thresholds + +| Score Range | Color | Token | +|-------------|-------|-------| +| 0~40% | `#C084CF` | Soft Violet (Critical) | +| 41~60% | `#8B9CF7` | Periwinkle (Caution) | +| 61~80% | `#7C6DD8` | Medium Purple (Good) | +| 81~100% | `#6C5CE7` | INFINITH Purple (Excellent) | + +### 2.5 Platform Brand Colors (외부 브랜드 존중) + +| Platform | Hex | 용도 | +|----------|-----|------| +| YouTube | `#FF0000` | 아이콘 컨테이너, 진단 구분 | +| Instagram | `#E1306C` | 아이콘 그라디언트 | +| Facebook | `#1877F2` | 아이콘 컨테이너, 로고 | +| VIEW Purple | `#7B2D8E` | 고객 브랜드 (뷰성형외과) | +| VIEW Gold | `#E8B931` | 고객 브랜드 (뷰성형외과) | + +--- + +## 3. Typography + +### 3.1 Font Families + +| 폰트 | 역할 | 로딩 | +|------|------|------| +| **Playfair Display** | 영문 헤딩, 섹션 타이틀, INFINITH 로고 | Google Fonts | +| **Pretendard** | 한글 헤딩, 본문 | CDN | +| **Inter** | 영문 본문, UI 요소 | Google Fonts (sans-serif) | + +### 3.2 Size & Weight Scale + +| 용도 | 폰트 | Size | Weight | Tailwind Class | +|------|-------|------|--------|---------------| +| INFINITH 로고 | Playfair Display | `3xl` (30px) | Black 900 | `font-serif text-3xl font-black tracking-[0.05em]` | +| 페이지 H1 | Playfair Display | `4xl~5xl` | Bold 700 | `font-serif text-4xl md:text-5xl font-bold` | +| 섹션 타이틀 (영문) | Playfair Display | `3xl~4xl` | Bold 700 | `font-serif text-3xl md:text-4xl font-bold` | +| 섹션 서브타이틀 | Pretendard | `lg` (18px) | Regular 400 | `text-lg` | +| 카드 제목 | Pretendard | `lg` (18px) | Bold 700 | `text-lg font-bold` | +| 본문 | Pretendard / Inter | `sm~base` (14~16px) | Regular 400 | `text-sm` or `text-base` | +| 캡션/라벨 | Inter | `xs` (12px) | Medium 500 | `text-xs font-medium` | +| 배지/태그 | Inter | `xs` (12px) | Semibold 600 | `text-xs font-semibold` | + +### 3.3 Text Color Mapping + +| 컨텍스트 | 라이트 섹션 | 다크 섹션 | +|----------|-----------|----------| +| 제목 | `text-[#0A1128]` | gradient `from-purple-300 to-blue-300` | +| 서브타이틀 | `text-slate-600` | `text-purple-200` | +| 본문 | `text-slate-700` | `text-white/80` | +| 캡션 | `text-slate-500` | `text-purple-300` | +| 강조 | `text-[#6C5CE7]` | `text-purple-300` | + +--- + +## 4. Layout Patterns + +### 4.1 SectionWrapper + +모든 콘텐츠 섹션의 기본 래퍼. `src/components/report/ui/SectionWrapper.tsx` + +| Prop | Light Mode | Dark Mode | +|------|-----------|-----------| +| Background | `bg-white` | `bg-[#0A1128]` + `radial-gradient rgba(108,92,231,0.15)` | +| Padding | `py-16 md:py-20 px-6` | 동일 | +| Max Width | `max-w-7xl mx-auto` | 동일 | +| Title Style | `.text-gradient` (navy gradient) | purple-300→blue-300 gradient | +| Animation | `opacity: 0, y: 40` → `1, 0` on scroll | 동일 | + +### 4.2 Card Patterns + +| 패턴 | 클래스 | 사용처 | +|------|--------|--------| +| **Standard Card** | `bg-white rounded-2xl border border-slate-100 shadow-sm p-5` | 대부분의 라이트 섹션 카드 | +| **White Card on Dark** | `bg-white rounded-2xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6` | ChannelStrategy, ContentCalendar | +| **Glass Card** | `bg-white/70 backdrop-blur-xl border border-white/40 shadow-[0_8px_32px_0_rgba(31,38,135,0.07)] rounded-2xl` | 랜딩 페이지 (TargetAudience, UseCases) | +| **Dark Glass Card** | `bg-white/10 backdrop-blur-sm border border-white/10 rounded-2xl` | ProblemDiagnosis 내부 | +| **Gradient Card** | `bg-gradient-to-r from-[#4F1DA1] to-[#021341]` | ConsolidationCard, 강조 카드 | + +### 4.3 Shadow System + +```css +/* Standard (대부분의 카드) */ +shadow-sm /* Tailwind default */ + +/* Diagonal soft shadow (화이트 카드 on 다크) */ +shadow-[3px_4px_12px_rgba(0,0,0,0.06)] /* 기본 */ +shadow-[4px_6px_16px_rgba(0,0,0,0.09)] /* hover */ + +/* Status-tinted shadows (배지/태그) */ +shadow-[2px_3px_8px_rgba(212,136,154,0.15)] /* Critical tint */ +shadow-[2px_3px_8px_rgba(212,168,114,0.15)] /* Warning tint */ +shadow-[2px_3px_8px_rgba(155,138,212,0.15)] /* Good/Purple tint */ +shadow-[2px_3px_8px_rgba(122,132,212,0.15)] /* Info tint */ + +/* Elevated (헤더, 라이트박스) */ +shadow-lg /* 리포트/플랜 헤더 패널 */ +shadow-2xl /* 라이트박스 모달 */ +``` + +--- + +## 5. Component Inventory + +### 5.1 Shared UI Components (`src/components/report/ui/`) + +| Component | Props | Description | +|-----------|-------|-------------| +| `SectionWrapper` | `id, title, subtitle, dark?, className?` | 모든 섹션의 래퍼 | +| `MetricCard` | `value, label, trend?, subtext?` | 숫자 지표 카드 | +| `ScoreRing` | `score, maxScore, size?` | SVG 원형 점수 표시 | +| `SeverityBadge` | `severity` | 상태 배지 (심각/주의/양호/우수) | +| `DiagnosisRow` | `category, detail, severity, evidenceIds?` | 진단 항목 행 | +| `ComparisonRow` | `area, asIs, toBe` | As-Is → To-Be 비교 행 | +| `EvidenceGallery` | `evidenceIds[]` | 스크린샷 근거 갤러리 | +| `EvidenceScreenshot` | `evidence, compact?` | 개별 스크린샷 카드 | +| `EvidenceLightbox` | `evidence, onClose` | 스크린샷 확대 모달 | + +### 5.2 Custom Icons (`src/components/icons/FilledIcons.tsx`) + +모든 아이콘은 **fill 전용** (stroke 없음), `opacity: 0.25` 배경 rect + fill path. + +| Icon | 용도 | +|------|------| +| `YoutubeFilled` | YouTube 채널 | +| `InstagramFilled` | Instagram 채널 | +| `FacebookFilled` | Facebook 채널 | +| `TiktokFilled` | TikTok 채널 | +| `GlobeFilled` | 웹사이트/기타 채널 | +| `VideoFilled` | 영상 콘텐츠 | +| `FileTextFilled` | 블로그 콘텐츠 | +| `ShareFilled` | 소셜 콘텐츠 | +| `MegaphoneFilled` | 광고 콘텐츠 | +| `MessageFilled` | 메시지/커뮤니티 | +| `CalendarFilled` | 일정/빈도 | + +### 5.3 Button Patterns + +| 타입 | 클래스 | 사용처 | +|------|--------|--------| +| **Primary** | `bg-gradient-to-r from-[#4F1DA1] to-[#021341] text-white rounded-full` | CTA, 로그인 | +| **Secondary** | `bg-white border border-slate-200 text-[#021341] rounded-full` | PDF 다운로드 | +| **Tab Active** | `bg-gradient-to-r from-[#4F1DA1] to-[#021341] text-white rounded-full` | 탭 인터페이스 | +| **Tab Inactive** | `bg-slate-100 text-slate-600 rounded-full hover:bg-slate-200` | 탭 인터페이스 | +| **Nav Active** | `border-b-2 border-[#6C5CE7] text-[#0A1128]` | ReportNav 탭 | + +### 5.4 Badge/Tag Patterns + +| 타입 | 스타일 | 예시 | +|------|--------|------| +| **Priority P0** | `bg-[#FFF0F0] text-[#7C3A4B] border-[#F5D5DC]` | 긴급 채널 | +| **Priority P1** | `bg-[#FFF6ED] text-[#7C5C3A] border-[#F5E0C5]` | 주요 채널 | +| **Priority P2** | `bg-[#F3F0FF] text-[#4A3A7C] border-[#D5CDF5]` | 보조 채널 | +| **Content Type** | 각 semantic status 색상 | 카드 내 유형 태그 | +| **Platform** | `bg-slate-100 text-slate-700` | 일반 플랫폼 라벨 | + +--- + +## 6. Animation Patterns + +| 패턴 | 구현 | 사용처 | +|------|------|--------| +| **Scroll reveal** | `motion.div` `opacity: 0, y: 20→40` | 모든 SectionWrapper, 카드 | +| **Stagger** | `delay: index * 0.1` | 그리드 카드, 리스트 | +| **Blob float** | `animate-blob` (7s) / `animate-blob-large` (25s) | Hero, Modules 배경 | +| **Score ring** | `motion.circle` dashoffset animation | ScoreRing | +| **Page transition** | Instant (no transition) | React Router | + +--- + +## 7. PDF Export Rules + +| 규칙 | 구현 | +|------|------| +| 숨길 요소 | `data-report-nav`, `data-plan-nav`, `nav`, `data-cta-card`, `data-no-print` | +| 콘텐츠 래퍼 | `data-report-content` 또는 `data-plan-content` | +| 페이지 분할 | 섹션 단위 원자적 처리, 900px 이하 섹션은 통째로 | +| 마진 | 8mm 전방향 | +| 푸터 | "INFINITH Marketing Intelligence Report | Page X / Y" (7pt, #B4B4B4) | +| 해상도 | `scale: 2`, JPEG `quality: 0.9` | +| 애니메이션 | 내보내기 전 `opacity: 1, transform: none` 강제 적용 | + +--- + +## 8. Responsive Breakpoints + +| Breakpoint | Width | 주요 변화 | +|------------|-------|----------| +| Mobile | < 768px | 1열 그리드, 축소된 헤딩 | +| Tablet (md) | 768px+ | 2열 그리드, 중간 헤딩 | +| Desktop (lg) | 1024px+ | 3열 그리드, 풀 레이아웃 | +| Max content | 1280px | `max-w-7xl mx-auto` | + +--- + +*이 문서는 코드와 함께 버전 관리됩니다. 디자인 변경 시 반드시 이 문서도 업데이트하세요.* diff --git a/extracted_marketing_text.md b/extracted_marketing_text.md new file mode 100644 index 0000000..b9c240e --- /dev/null +++ b/extracted_marketing_text.md @@ -0,0 +1,71 @@ +# INFINITH Marketing Extract + +Here is the extracted text from the `INFINITH - Infinite Marketing` component files: + +## Navbar +- **Brand:** INFINITH +- **Links:** Solution, Modules, Use Cases +- **Button:** Login + +## Hero +- **Badge:** Infinite Marketing for Premium Medical Business & Marketing Agency +- **Headline:** AI Marketing Engine Infinite Marketing. +- **Description:** AI가 콘텐츠를 만들고 데이터가 마케팅을 개선합니다. 콘텐츠 기획, 생성, 영상 제작, 채널 배포, 데이터 분석을 하나의 자동화 엔진으로. +- **Call to Action:** Enter Your Website URL ➔ Analyze Marketing Performance +- **Subtext:** 네이버 블로그, 플레이스, 소셜미디어 등 Online Presence 종합 분석 리포트를 제공합니다. + +## Target Audience +- **Headline:** Who is Infinite Marketing for +- **Sub-headline:** 프리미엄 의료 서비스와 전문 마케팅 에이전시를 위한 최적의 솔루션 +- **Premium Medical Business:** 고객 LTV가 높고 브랜드 경쟁이 심해 콘텐츠 마케팅이 필수적인 프리미엄 의료 서비스 제공 병원 + - *Tags:* 피부과, 성형외과, 치과, 안과, 헬스케어 클리닉, 피트니스 +- **Medical Marketing Agency:** 콘텐츠 제작 비용 부담과 인력 의존도가 높고 영상 제작 생산성 개선이 필요한 병원 마케팅 대행사 + - *Tags:* 병원 마케팅 대행사, 콘텐츠 마케팅 Agency, 영상 마케팅 Agency, 광고 운영 대행사 + +## Problems +- **Headline:** Premium Medical Marketing is Hard +- **Sub-headline:** 병원 마케팅이 직면한 3가지 핵심 과제 +- **1. 콘텐츠 생산의 한계:** 블로그, SEO, 유튜브, 숏폼 등 지속적 생산이 필요하지만 인력과 비용, 시간 부족으로 제한됩니다. +- **2. 영상 콘텐츠 제작 비용:** 영상은 중요하지만 촬영, 편집, 기획 비용이 높습니다. 특히 숏폼 콘텐츠는 지속적인 제작이 어렵습니다. +- **3. 데이터 기반의 마케팅 부족:** 어떤 콘텐츠가 효과적인지, 어떤 키워드가 유입을 만드는지, 어떤 채널이 성과가 좋은지 알기 어렵고, 각 플랫폼들의 데이터들을 한눈에 파악할 수 없습니다. + +## Solution +- **Badge:** AI Marketing Engine +- **Headline:** Infinite Marketing Engine +- **Description:** Infinite Marketing for Premium Medical Business & Marketing Agency. Infinite Marketing은 Premium Medical Business와 Marketing Agency를 위한 AI Marketing Automation Platform입니다. INFINITH는 마케팅 분석, 콘텐츠 생성, 영상 콘텐츠 제작, 채널 배포, 성과 분석과 피드백 적용을 하나의 Self-Improving Marketing Engine으로 제공합니다. +- **AGDP Cycle:** + - A (Analysis), G (Generation), D (Distribution), P (Performance) + - 분석(Analysis) → 생성(Generation) → 배포(Distribution) → 성과(Performance)의 무한 루프를 통해 콘텐츠 품질(CTR)을 자율 최적화합니다. + +## Modules +- **Headline:** Core Modules +- **Sub-headline:** 성능 개선 반영 자율 순환 마케팅 시스템 +- **Self-Improving Growth Engine:** + 1. **Marketing Intelligence:** 브랜딩, 마케팅 현황 분석, 타겟 고객 분석, 키워드 분석, 경쟁 및 포지셔닝 분석, SEO 전략 & 채널별 콘텐츠 기획 (*AI 기반 시장 통찰력 도출*) + 2. **Content Creation:** 블로그 콘텐츠 생성, SEO 콘텐츠 생성, SNS 콘텐츠 생성, 마케팅 카피 생성, Human-in-the loop 프로세스 (*고품질 맞춤형 콘텐츠 자동화*) + 3. **Video Automation:** 블로그 → 영상 변환, 숏폼 콘텐츠 생성, 유튜브 콘텐츠 제작, SNS 영상 제작, 멀티모달 AI 엔진: 영상 + 음악 + 카피 (*원클릭 영상 제작 시스템*) + 4. **Distribution Engine:** 블로그 게시, SNS 자동 게시, 유튜브 업로드, 콘텐츠 일정 관리, SEO, AEO 자동 최적화 (*전 채널 통합 배포 및 최적화*) + 5. **Performance Intelligence:** SEO 성과 분석, 콘텐츠 성과 분석, 채널 성과 분석, AI 콘텐츠 개선 전략 추천, 데이터 기반 효과 검증 (*실시간 성과 추적 및 개선*) + +## Use Cases +- **Headline:** Use Cases +- **Sub-headline:** Infinite Marketing이 만드는 실질적인 변화를 확인해보세요! +- **Premium Medical Business:** + - SEO 콘텐츠 자동 생산으로 검색 상위 노출 달성 + - 비용 부담 없이 고품질 영상 콘텐츠 대량 확대 (NEW) + - 자연 검색 유입 증가로 인한 환자 전환율 상승 +- **Marketing Agency:** + - AI 기반 콘텐츠 제작 자동화로 생산성 극대화 + - 블로그 텍스트 기반 영상 제작 자동화로 리소스 절감 + - 다수 클라이언트 계정의 통합 운영 및 효율화 + +## CTA (Call to Action) +- **Headline:** Ready to Transform Your Marketing? +- **Description:** URL 하나로 시작하는 완벽한 마케팅 자동화. 지금 바로 무료 진단을 받아보세요. +- **Action:** Enter Your URL ➔ Analyze +- **Subtext:** 네이버 블로그, 플레이스, 소셜미디어 종합 분석 리포트 받아보기 + +## Footer +- **Brand:** INFINITH. All rights reserved. +- **Tagline:** Infinite Marketing for Premium Medical Business & Marketing Agency +- **Links:** Privacy Policy, Terms of Service diff --git a/index.html b/index.html new file mode 100644 index 0000000..5dae0bc --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + INFINITH - Infinite Marketing + + + + + + +
+ + + + diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..f283af8 --- /dev/null +++ b/metadata.json @@ -0,0 +1,5 @@ +{ + "name": "Remix: INFINITH - Infinite Marketing", + "description": "Infinite Marketing for Premium Medical Business \u0026 Marketing Agency", + "requestFramePermissions": [] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..28d5cb2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4980 @@ +{ + "name": "react-example", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "react-example", + "version": "0.0.0", + "dependencies": { + "@google/genai": "^1.29.0", + "@tailwindcss/vite": "^4.1.14", + "@vitejs/plugin-react": "^5.0.4", + "better-sqlite3": "^12.4.1", + "dotenv": "^17.2.3", + "express": "^4.21.2", + "html2canvas-pro": "^2.0.2", + "jspdf": "^4.2.1", + "lucide-react": "^0.546.0", + "motion": "^12.23.24", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router": "^7.13.1", + "vite": "^6.2.0" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^22.14.0", + "autoprefixer": "^10.4.21", + "tailwindcss": "^4.1.14", + "tsx": "^4.21.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@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.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@google/genai": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.46.0.tgz", + "integrity": "sha512-ewPMN5JkKfgU5/kdco9ZhXBHDPhVqZpMQqIFQhwsHLf8kyZfx1cNpw1pHo1eV6PGEW7EhIBFi3aYZraFndAXqg==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.1.tgz", + "integrity": "sha512-xB0b51TB7IfDEzAojXahmr+gfA00uYVInJGgNNkeQG6RPnCPGr7udsylFLTubuIUSRE6FkcI1NElyRt83PP5oQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.1.tgz", + "integrity": "sha512-XOjPId0qwSDKHaIsdzHJtKCxX0+nH8MhBwvrNsT7tVyKmdTx1jJ4XzN5RZXCdTzMpufLb+B8llTC0D8uCrLhcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.1.tgz", + "integrity": "sha512-vQuRd28p0gQpPrS6kppd8IrWmFo42U8Pz1XLRjSZXq5zCqyMDYFABT7/sywL11mO1EL10Qhh7MVPEwkG8GiBeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.1.tgz", + "integrity": "sha512-x6VG6U29+Ivlnajrg1IHdzXeAwSoEHBFVO+CtC9Brugx6de712CUJobRUxsIA0KYrQvCmzNrMPFTT1A4CCqNTg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.1.tgz", + "integrity": "sha512-Sgi0Uo6t1YCHJMNO3Y8+bm+SvOanUGkoZKn/VJPwYUe2kp31X5KnXmzKd/NjW8iA3gFcfNZ64zh14uOGrIllCQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.1.tgz", + "integrity": "sha512-AM4xnwEZwukdhk7laMWfzWu9JGSVnJd+Fowt6Fd7QW1nrf3h0Hp7Qx5881M4aqrUlKBCybOxz0jofvIIfl7C5g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.1.tgz", + "integrity": "sha512-KUizqxpwaR2AZdAUsMWfL/C94pUu7TKpoPd88c8yFVixJ+l9hejkrwoK5Zj3wiNh65UeyryKnJyxL1b7yNqFQA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.1.tgz", + "integrity": "sha512-MZoQ/am77ckJtZGFAtPucgUuJWiop3m2R3lw7tC0QCcbfl4DRhQUBUkHWCkcrT3pqy5Mzv5QQgY6Dmlba6iTWg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.1.tgz", + "integrity": "sha512-Sez95TP6xGjkWB1608EfhCX1gdGrO5wzyN99VqzRtC17x/1bhw5VU1V0GfKUwbW/Xr1J8mSasoFoJa6Y7aGGSA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.1.tgz", + "integrity": "sha512-9Cs2Seq98LWNOJzR89EGTZoiP8EkZ9UbQhBlDgfAkM6asVna1xJ04W2CLYWDN/RpUgOjtQvcv8wQVi1t5oQazA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.1.tgz", + "integrity": "sha512-n9yqttftgFy7IrNEnHy1bOp6B4OSe8mJDiPkT7EqlM9FnKOwUMnCK62ixW0Kd9Clw0/wgvh8+SqaDXMFvw3KqQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.1.tgz", + "integrity": "sha512-SfpNXDzVTqs/riak4xXcLpq5gIQWsqGWMhN1AGRQKB4qGSs4r0sEs3ervXPcE1O9RsQ5bm8Muz6zmQpQnPss1g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.1.tgz", + "integrity": "sha512-LjaChED0wQnjKZU+tsmGbN+9nN1XhaWUkAlSbTdhpEseCS4a15f/Q8xC2BN4GDKRzhhLZpYtJBZr2NZhR0jvNw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.1.tgz", + "integrity": "sha512-ojW7iTJSIs4pwB2xV6QXGwNyDctvXOivYllttuPbXguuKDX5vwpqYJsHc6D2LZzjDGHML414Tuj3LvVPe1CT1A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.1.tgz", + "integrity": "sha512-FP+Q6WTcxxvsr0wQczhSE+tOZvFPV8A/mUE6mhZYFW9/eea/y/XqAgRoLLMuE9Cz0hfX5bi7p116IWoB+P237A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.1.tgz", + "integrity": "sha512-L1uD9b/Ig8Z+rn1KttCJjwhN1FgjRMBKsPaBsDKkfUl7GfFq71pU4vWCnpOsGljycFEbkHWARZLf4lMYg3WOLw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.1.tgz", + "integrity": "sha512-EZc9NGTk/oSUzzOD4nYY4gIjteo2M3CiozX6t1IXGCOdgxJTlVu/7EdPeiqeHPSIrxkLhavqpBAUCfvC6vBOug==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.1.tgz", + "integrity": "sha512-NQ9KyU1Anuy59L8+HHOKM++CoUxrQWrZWXRik4BJFm+7i5NP6q/SW43xIBr80zzt+PDBJ7LeNmloQGfa0JGk0w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.1.tgz", + "integrity": "sha512-GZkLk2t6naywsveSFBsEb0PLU+JC9ggVjbndsbG20VPhar6D1gkMfCx4NfP9owpovBXTN+eRdqGSkDGIxPHhmQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.1.tgz", + "integrity": "sha512-1hjG9Jpl2KDOetr64iQd8AZAEjkDUUK5RbDkYWsViYLC1op1oNzdjMJeFiofcGhqbNTaY2kfgqowE7DILifsrA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.1.tgz", + "integrity": "sha512-ARoKfflk0SiiYm3r1fmF73K/yB+PThmOwfWCk1sr7x/k9dc3uGLWuEE9if+Pw21el8MSpp3TMnG5vLNsJ/MMGQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.1.tgz", + "integrity": "sha512-oOST61G6VM45Mz2vdzWMr1s2slI7y9LqxEV5fCoWi2MDONmMvgsJVHSXxce/I2xOSZPTZ47nDPOl1tkwKWSHcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.1.tgz", + "integrity": "sha512-x5WgLi5dWpRz7WclKBGEF15LcWTh0ewrHM6Cq4A+WUbkysUMZNeqt05bwPonOQ3ihPS/WMhAZV5zB1DfnI4Sxg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.1.tgz", + "integrity": "sha512-wS+zHAJRVP5zOL0e+a3V3E/NTEwM2HEvvNKoDy5Xcfs0o8lljxn+EAFPkUsxihBdmDq1JWzXmmB9cbssCPdxxw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.1.tgz", + "integrity": "sha512-rhHyrMeLpErT/C7BxcEsU4COHQUzHyrPYW5tOZUeUhziNtRuYxmDWvqQqzpuUt8xpOgmbKa1btGXfnA/ANVO+g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", + "license": "MIT", + "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" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "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" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "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" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz", + "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "tailwindcss": "4.2.2" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "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" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", + "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/better-sqlite3": { + "version": "12.8.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.8.0.tgz", + "integrity": "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.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" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dompurify": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", + "license": "ISC" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/html2canvas-pro": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html2canvas-pro/-/html2canvas-pro-2.0.2.tgz", + "integrity": "sha512-9G/t0XgCZWonLwL0JwI7su6NdbOPUY7Ur4Ihpp8+XMaW9ibA2nDXF181Jr6tm94k8lX6sthpaXB3XqEnsMd5Cw==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jspdf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.1.tgz", + "integrity": "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "fast-png": "^6.2.0", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.3.1", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "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" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.546.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.546.0.tgz", + "integrity": "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz", + "integrity": "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.38.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "license": "MIT" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "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" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", + "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, + "node_modules/rollup": { + "version": "4.59.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.1.tgz", + "integrity": "sha512-iZKH8BeoCwTCBTZBZWQQMreekd4mdomwdjIQ40GC1oZm6o+8PnNMIxFOiCsGMWeS8iDJ7KZcl7KwmKk/0HOQpA==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.1", + "@rollup/rollup-android-arm64": "4.59.1", + "@rollup/rollup-darwin-arm64": "4.59.1", + "@rollup/rollup-darwin-x64": "4.59.1", + "@rollup/rollup-freebsd-arm64": "4.59.1", + "@rollup/rollup-freebsd-x64": "4.59.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.1", + "@rollup/rollup-linux-arm-musleabihf": "4.59.1", + "@rollup/rollup-linux-arm64-gnu": "4.59.1", + "@rollup/rollup-linux-arm64-musl": "4.59.1", + "@rollup/rollup-linux-loong64-gnu": "4.59.1", + "@rollup/rollup-linux-loong64-musl": "4.59.1", + "@rollup/rollup-linux-ppc64-gnu": "4.59.1", + "@rollup/rollup-linux-ppc64-musl": "4.59.1", + "@rollup/rollup-linux-riscv64-gnu": "4.59.1", + "@rollup/rollup-linux-riscv64-musl": "4.59.1", + "@rollup/rollup-linux-s390x-gnu": "4.59.1", + "@rollup/rollup-linux-x64-gnu": "4.59.1", + "@rollup/rollup-linux-x64-musl": "4.59.1", + "@rollup/rollup-openbsd-x64": "4.59.1", + "@rollup/rollup-openharmony-arm64": "4.59.1", + "@rollup/rollup-win32-arm64-msvc": "4.59.1", + "@rollup/rollup-win32-ia32-msvc": "4.59.1", + "@rollup/rollup-win32-x64-gnu": "4.59.1", + "@rollup/rollup-win32-x64-msvc": "4.59.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "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 + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b2f0c01 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "react-example", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3000 --host=0.0.0.0", + "build": "vite build", + "preview": "vite preview", + "clean": "rm -rf dist", + "lint": "tsc --noEmit" + }, + "dependencies": { + "@google/genai": "^1.29.0", + "@tailwindcss/vite": "^4.1.14", + "@vitejs/plugin-react": "^5.0.4", + "better-sqlite3": "^12.4.1", + "dotenv": "^17.2.3", + "express": "^4.21.2", + "html2canvas-pro": "^2.0.2", + "jspdf": "^4.2.1", + "lucide-react": "^0.546.0", + "motion": "^12.23.24", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router": "^7.13.1", + "vite": "^6.2.0" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^22.14.0", + "autoprefixer": "^10.4.21", + "tailwindcss": "^4.1.14", + "tsx": "^4.21.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } +} diff --git a/public/assets/clients/view-clinic/screenshots/fb-en-page.svg b/public/assets/clients/view-clinic/screenshots/fb-en-page.svg new file mode 100644 index 0000000..0ef5be3 --- /dev/null +++ b/public/assets/clients/view-clinic/screenshots/fb-en-page.svg @@ -0,0 +1,7 @@ + + + + Facebook EN 페이지 + Screenshot Evidence Placeholder + Production: Captured via Firecrawl / Puppeteer + diff --git a/public/assets/clients/view-clinic/screenshots/ig-en-profile.svg b/public/assets/clients/view-clinic/screenshots/ig-en-profile.svg new file mode 100644 index 0000000..a5ef95e --- /dev/null +++ b/public/assets/clients/view-clinic/screenshots/ig-en-profile.svg @@ -0,0 +1,7 @@ + + + + Instagram EN (@view_plastic_surgery) + Screenshot Evidence Placeholder + Production: Captured via Firecrawl / Puppeteer + diff --git a/public/assets/clients/view-clinic/screenshots/ig-kr-profile.svg b/public/assets/clients/view-clinic/screenshots/ig-kr-profile.svg new file mode 100644 index 0000000..037bae2 --- /dev/null +++ b/public/assets/clients/view-clinic/screenshots/ig-kr-profile.svg @@ -0,0 +1,7 @@ + + + + Instagram KR (@viewplastic) + Screenshot Evidence Placeholder + Production: Captured via Firecrawl / Puppeteer + diff --git a/public/assets/clients/view-clinic/screenshots/website-homepage.svg b/public/assets/clients/view-clinic/screenshots/website-homepage.svg new file mode 100644 index 0000000..3edd192 --- /dev/null +++ b/public/assets/clients/view-clinic/screenshots/website-homepage.svg @@ -0,0 +1,7 @@ + + + + viewclinic.com 홈페이지 + Screenshot Evidence Placeholder + Production: Captured via Firecrawl / Puppeteer + diff --git a/public/assets/clients/view-clinic/screenshots/yt-about-links.svg b/public/assets/clients/view-clinic/screenshots/yt-about-links.svg new file mode 100644 index 0000000..b9542a2 --- /dev/null +++ b/public/assets/clients/view-clinic/screenshots/yt-about-links.svg @@ -0,0 +1,7 @@ + + + + YouTube 연결 링크 + Screenshot Evidence Placeholder + Production: Captured via Firecrawl / Puppeteer + diff --git a/public/assets/clients/view-clinic/screenshots/yt-channel.svg b/public/assets/clients/view-clinic/screenshots/yt-channel.svg new file mode 100644 index 0000000..aeaa68a --- /dev/null +++ b/public/assets/clients/view-clinic/screenshots/yt-channel.svg @@ -0,0 +1,7 @@ + + + + YouTube 채널 페이지 + Screenshot Evidence Placeholder + Production: Captured via Firecrawl / Puppeteer + diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..aa457c6 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,18 @@ +import { Outlet, useLocation } from 'react-router'; +import Navbar from './components/Navbar'; +import Footer from './components/Footer'; +import PageNavigator from './components/PageNavigator'; + +export default function App() { + const location = useLocation(); + const isLoadingPage = location.pathname === '/report/loading'; + + return ( +
+ {!isLoadingPage && } + + {!isLoadingPage &&
} + {!isLoadingPage && } +
+ ); +} diff --git a/src/components/CTA.tsx b/src/components/CTA.tsx new file mode 100644 index 0000000..dd75bec --- /dev/null +++ b/src/components/CTA.tsx @@ -0,0 +1,66 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router'; +import { motion } from 'motion/react'; +import { ArrowRight } from 'lucide-react'; + +export default function CTA() { + const [url, setUrl] = useState(''); + const navigate = useNavigate(); + + const handleAnalyze = () => { + if (url.trim()) navigate('/report/loading', { state: { url } }); + }; + + return ( +
+
+
+ +
+ + Ready to Transform Your Marketing? + + + + URL 하나로 시작하는 완벽한 마케팅 자동화. 지금 바로 무료 진단을 받아보세요. + + + + setUrl(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleAnalyze()} + className="w-full px-8 py-4 text-base font-medium bg-gradient-to-r from-[#fff3eb] via-[#e4cfff] to-[#f5f9ff] border border-white/20 rounded-full focus:outline-none focus:ring-2 focus:ring-white/50 shadow-sm text-center text-primary-900 placeholder:text-primary-900/60" + /> + +

+ 네이버 블로그, 플레이스, 소셜미디어 종합 분석 리포트 받아보기 +

+
+
+
+ ); +} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000..4215e36 --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +export default function Footer() { + return ( +
+
+
+ INFINITH +
+
+ © {new Date().getFullYear()} INFINITH. All rights reserved.
+ Infinite Marketing for Premium Medical Business & Marketing Agency +
+
+ Privacy Policy + Terms of Service +
+
+
+ ); +} diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx new file mode 100644 index 0000000..bf9cc35 --- /dev/null +++ b/src/components/Hero.tsx @@ -0,0 +1,83 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router'; +import { motion } from 'motion/react'; +import { ArrowRight, Sparkles } from 'lucide-react'; + +export default function Hero() { + const [url, setUrl] = useState(''); + const navigate = useNavigate(); + + const handleAnalyze = () => { + if (url.trim()) navigate('/report/loading', { state: { url } }); + }; + + return ( +
+ {/* Background Gradient */} +
+ +
+ + + Infinite Marketing for Premium Medical Business & Marketing Agency + + + + AI Marketing Engine
+ Infinite Marketing. +
+ + + Infinite Marketing for Premium Medical Business & Marketing Agency.
+ AI가 콘텐츠를 만들고 데이터가 마케팅을 개선합니다. + 콘텐츠 기획, 생성, 영상 제작, 채널 배포, 데이터 분석을 하나의 자동화 엔진으로. +
+ + +
+ setUrl(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleAnalyze()} + className="w-full px-8 py-5 text-base font-medium bg-white/80 backdrop-blur-sm border border-slate-200 rounded-2xl focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent/40 shadow-sm text-center text-primary-900 placeholder:text-slate-400 transition-all group-hover:border-slate-300" + /> +
+ +

+ 네이버 블로그, 플레이스, 소셜미디어 등 Online Presence 종합 분석 리포트를 제공합니다. +

+
+
+ + {/* Decorative elements */} +
+
+
+
+ ); +} diff --git a/src/components/Modules.tsx b/src/components/Modules.tsx new file mode 100644 index 0000000..86d1584 --- /dev/null +++ b/src/components/Modules.tsx @@ -0,0 +1,187 @@ +import React from 'react'; +import { motion } from 'motion/react'; + +const modules = [ + { + step: "1", + title: "Marketing Intelligence", + items: [ + "브랜딩, 마케팅 현황 분석", + "타겟 고객 분석", + "키워드 분석", + "경쟁 및 포지셔닝 분석", + "SEO 전략 & 채널별 콘텐츠 기획" + ], + highlight: "AI 기반 시장 통찰력 도출", + color: "bg-[#021341]", + textColor: "text-indigo-600" + }, + { + step: "2", + title: "Content Creation", + items: [ + "블로그 콘텐츠 생성", + "SEO 콘텐츠 생성", + "SNS 콘텐츠 생성", + "마케팅 카피 생성", + "Human-in-the loop 프로세스" + ], + highlight: "고품질 맞춤형 콘텐츠 자동화", + color: "bg-[#021341]", + textColor: "text-indigo-600" + }, + { + step: "3", + title: "Video Automation", + items: [ + "블로그 → 영상 변환", + "숏폼 콘텐츠 생성", + "유튜브 콘텐츠 제작", + "SNS 영상 제작", + "멀티모달 AI 엔진: 영상 + 음악 + 카피" + ], + highlight: "원클릭 영상 제작 시스템", + color: "bg-[#021341]", + textColor: "text-indigo-600" + }, + { + step: "4", + title: "Distribution Engine", + items: [ + "블로그 게시", + "SNS 자동 게시", + "유튜브 업로드", + "콘텐츠 일정 관리", + "SEO, AEO 자동 최적화" + ], + highlight: "전 채널 통합 배포 및 최적화", + color: "bg-[#021341]", + textColor: "text-indigo-600" + }, + { + step: "5", + title: "Performance Intelligence", + items: [ + "SEO 성과 분석", + "콘텐츠 성과 분석", + "채널 성과 분석", + "AI 콘텐츠 개선 전략 추천", + "데이터 기반 효과 검증" + ], + highlight: "실시간 성과 추적 및 개선", + color: "bg-[#021341]", + textColor: "text-indigo-600" + } +]; + +const Flywheel = () => ( +
+ {/* Center Content */} +
+

+ Self-Improving
Growth Engine +

+
+
+); + +const ModuleCard: React.FC<{ mod: any, className?: string }> = ({ mod, className }) => ( + +
+
+ {mod.step} +
+

{mod.title}

+
+ +
    + {mod.items.map((item: string, i: number) => ( +
  • + {item} +
  • + ))} +
+ +
+ {mod.highlight} +
+
+); + +export default function Modules() { + return ( +
+ {/* Animated Background Blobs */} +
+
+
+ +
+ +

+ Core Modules +

+

+ 성능 개선 반영 자율 순환 마케팅 시스템 +

+
+ + {/* Desktop Layout — Pentagon around Flywheel */} +
+ {/* Center flywheel */} +
+ +
+ + {/* Pentagon: 5 cards around center, all same width */} + {/* 1. Top center */} +
+ +
+ + {/* 2. Upper right */} +
+ +
+ + {/* 3. Lower right */} +
+ +
+ + {/* 4. Lower left */} +
+ +
+ + {/* 5. Upper left */} +
+ +
+
+ + {/* Mobile/Tablet Layout (Flex Column) */} +
+ +
+ {modules.map((mod, idx) => ( + + ))} +
+
+
+
+ ); +} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx new file mode 100644 index 0000000..906aef0 --- /dev/null +++ b/src/components/Navbar.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { motion } from 'motion/react'; + +export default function Navbar() { + return ( + + ); +} diff --git a/src/components/PageNavigator.tsx b/src/components/PageNavigator.tsx new file mode 100644 index 0000000..cac7cba --- /dev/null +++ b/src/components/PageNavigator.tsx @@ -0,0 +1,66 @@ +import { useLocation, useNavigate } from 'react-router'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; + +const PAGE_FLOW = [ + { path: '/', label: '랜딩' }, + { path: '/report/view-clinic', label: '마케팅 분석' }, + { path: '/plan/view-clinic', label: '콘텐츠 기획' }, + { path: '/studio/view-clinic', label: '콘텐츠 제작' }, + { path: '/channels', label: '채널 연결' }, + { path: '/distribute', label: '콘텐츠 배포' }, + { path: '/performance', label: '성과 관리' }, +]; + +export default function PageNavigator() { + const location = useLocation(); + const navigate = useNavigate(); + + const currentIndex = PAGE_FLOW.findIndex((p) => p.path === location.pathname); + if (currentIndex === -1) return null; + + const prev = currentIndex > 0 ? PAGE_FLOW[currentIndex - 1] : null; + const next = currentIndex < PAGE_FLOW.length - 1 ? PAGE_FLOW[currentIndex + 1] : null; + + return ( +
+ {/* Back */} + + + {/* Page Indicators */} +
+ {PAGE_FLOW.map((page, i) => ( +
+ + {/* Next */} + +
+ ); +} diff --git a/src/components/Problems.tsx b/src/components/Problems.tsx new file mode 100644 index 0000000..f403546 --- /dev/null +++ b/src/components/Problems.tsx @@ -0,0 +1,60 @@ +import { motion } from 'motion/react'; + +const problems = [ + { + title: "콘텐츠 생산의 한계", + desc: "블로그, SEO, 유튜브, 숏폼 등 지속적 생산이 필요하지만 인력과 비용, 시간 부족으로 제한됩니다." + }, + { + title: "영상 콘텐츠 제작 비용", + desc: "영상은 중요하지만 촬영, 편집, 기획 비용이 높습니다. 특히 숏폼 콘텐츠는 지속적인 제작이 어렵습니다.", + highlight: true + }, + { + title: "데이터 기반의 마케팅 부족", + desc: "어떤 콘텐츠가 효과적인지, 어떤 키워드가 유입을 만드는지, 어떤 채널이 성과가 좋은지 알기 어렵고, 각 플랫폼들의 데이터들을 한눈에 파악할 수 없습니다." + } +]; + +export default function Problems() { + return ( +
+
+ +

+ Premium Medical Marketing is Hard +

+

+ 병원 마케팅이 직면한 3가지 핵심 과제 +

+
+ +
+ {problems.map((problem, idx) => ( + +

+ {problem.title} +

+

+ {problem.desc} +

+
+ ))} +
+
+
+ ); +} diff --git a/src/components/Solution.tsx b/src/components/Solution.tsx new file mode 100644 index 0000000..fc35716 --- /dev/null +++ b/src/components/Solution.tsx @@ -0,0 +1,146 @@ +import { motion } from 'motion/react'; +import { Sparkles, ArrowRight } from 'lucide-react'; + +export default function Solution() { + return ( +
+ {/* Background Glow */} +
+ +
+ + + AI Marketing Engine + + + + Infinite Marketing Engine + + + + Infinite Marketing for Premium Medical Business & Marketing Agency +
+ Infinite Marketing은 Premium Medical Business와 Marketing Agency를 위한 AI Marketing Automation Platform입니다. + INFINITH는 마케팅 분석, 콘텐츠 생성, 영상 콘텐츠 제작, 채널 배포, 성과 분석과 피드백 적용을 하나의 Self-Improving Marketing Engine으로 제공합니다. +
+ + {/* Circular Loop Diagram */} + + {/* Static Inner Ring */} +
+ + {/* Animated Glowing Ring */} + + + + + + + + + + + + {/* Center Orb */} +
+
+ AGDP +

Infinite
Marketing

+
+
+ + {/* Node A: Analysis (Left) */} +
+
+ A +
+
+ Analysis +
+
+ + {/* Node G: Generation (Top) */} +
+
+ G +
+
+ Generation +
+
+ + {/* Node D: Distribution (Right) */} +
+
+ D +
+
+ Distribution +
+
+ + {/* Node P: Performance (Bottom) */} +
+
+ P +
+
+ Performance +
+
+ + {/* Reward Signal Curved Text (P to A) */} + + + + + + + ← Reward SIGNAL + + + +
+ + {/* AGDP Cycle Description */} + +
+

+ AGDP Cycle: 분석(Analysis) → 생성(Generation) → 배포(Distribution) → 성과(Performance)의 무한 루프를 통해 콘텐츠 품질(CTR)을 자율 최적화합니다. +

+
+
+
+
+ ); +} diff --git a/src/components/TargetAudience.tsx b/src/components/TargetAudience.tsx new file mode 100644 index 0000000..3d7f9dc --- /dev/null +++ b/src/components/TargetAudience.tsx @@ -0,0 +1,66 @@ +import { motion } from 'motion/react'; + +export default function TargetAudience() { + return ( +
+
+ +

+ Who is Infinite Marketing for +

+

+ 프리미엄 의료 서비스와 전문 마케팅 에이전시를 위한 최적의 솔루션 +

+
+ +
+ +

Premium Medical Business

+

+ 고객 LTV가 높고 브랜드 경쟁이 심해 콘텐츠 마케팅이 필수적인 프리미엄 의료 서비스 제공 병원 +

+
+ {['피부과', '성형외과', '치과', '안과', '헬스케어 클리닉', '피트니스'].map((item, i) => ( +
+ {item} +
+ ))} +
+
+ + +

Medical Marketing Agency

+

+ 콘텐츠 제작 비용 부담과 인력 의존도가 높고 영상 제작 생산성 개선이 필요한 병원 마케팅 대행사 +

+
+ {['병원 마케팅 대행사', '콘텐츠 마케팅 Agency', '영상 마케팅 Agency', '광고 운영 대행사'].map((item, i) => ( +
+ {item} +
+ ))} +
+
+
+
+
+ ); +} diff --git a/src/components/UseCases.tsx b/src/components/UseCases.tsx new file mode 100644 index 0000000..4d46382 --- /dev/null +++ b/src/components/UseCases.tsx @@ -0,0 +1,71 @@ +import { motion } from 'motion/react'; +import { CheckCircle2 } from 'lucide-react'; + +export default function UseCases() { + return ( +
+
+ +

+ Use Cases +

+

+ Infinite Marketing이 만드는 실질적인 변화를 확인해보세요! +

+
+ +
+ +

Premium Medical Business

+
    + {[ + 'SEO 콘텐츠 자동 생산으로 검색 상위 노출 달성', + '비용 부담 없이 고품질 영상 콘텐츠 대량 확대', + '자연 검색 유입 증가로 인한 환자 전환율 상승' + ].map((item, i) => ( +
  • + + {item} +
  • + ))} +
+
+ + +

Marketing Agency

+
    + {[ + 'AI 기반 콘텐츠 제작 자동화로 생산성 극대화', + '블로그 텍스트 기반 영상 제작 자동화로 리소스 절감', + '다수 클라이언트 계정의 통합 운영 및 효율화' + ].map((item, i) => ( +
  • + + {item} +
  • + ))} +
+
+
+
+
+ ); +} diff --git a/src/components/icons/FilledIcons.tsx b/src/components/icons/FilledIcons.tsx new file mode 100644 index 0000000..f47831b --- /dev/null +++ b/src/components/icons/FilledIcons.tsx @@ -0,0 +1,135 @@ +/** + * Filled/Shape-style icons for Channel Strategy & Content Calendar. + * Soft pastel colors, no outlines — all shapes use fill only. + */ + +interface IconProps { + size?: number; + className?: string; +} + +export function YoutubeFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + ); +} + +export function InstagramFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + + ); +} + +export function FacebookFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + ); +} + +export function GlobeFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + + + ); +} + +export function VideoFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + + ); +} + +export function MessageFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + ); +} + +export function CalendarFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + + + + + + ); +} + +export function FileTextFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + + + ); +} + +export function ShareFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + + + + ); +} + +export function MegaphoneFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + ); +} + +export function TiktokFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + ); +} + +export function MusicFilled({ size = 20, className = '' }: IconProps) { + return ( + + + + + + + + + ); +} diff --git a/src/components/plan/AssetCollection.tsx b/src/components/plan/AssetCollection.tsx new file mode 100644 index 0000000..040944e --- /dev/null +++ b/src/components/plan/AssetCollection.tsx @@ -0,0 +1,192 @@ +import { useState } from 'react'; +import { motion } from 'motion/react'; +import { Youtube } from 'lucide-react'; +import { SectionWrapper } from '../report/ui/SectionWrapper'; +import type { AssetCollectionData, AssetSource, AssetStatus, AssetType } from '../../types/plan'; + +interface AssetCollectionProps { + data: AssetCollectionData; +} + +const filterTabs = [ + { key: 'all', label: '전체' }, + { key: 'homepage', label: '홈페이지' }, + { key: 'naver_place', label: '네이버' }, + { key: 'blog', label: '블로그' }, + { key: 'social', label: '소셜미디어' }, + { key: 'youtube', label: 'YouTube' }, +] as const; + +type FilterKey = (typeof filterTabs)[number]['key']; + +const sourceBadgeColors: Record = { + homepage: 'bg-slate-100 text-slate-700', + naver_place: 'bg-[#F3F0FF] text-[#4A3A7C]', + blog: 'bg-[#EFF0FF] text-[#3A3F7C]', + social: 'bg-pink-100 text-pink-700', + youtube: 'bg-[#FFF0F0] text-[#7C3A4B]', +}; + +const typeBadgeColors: Record = { + photo: 'bg-indigo-50 text-indigo-700', + video: 'bg-purple-50 text-purple-700', + text: 'bg-[#FFF6ED] text-[#7C5C3A]', +}; + +const statusConfig: Record = { + collected: { className: 'bg-[#F3F0FF] text-[#4A3A7C]', label: '수집완료' }, + pending: { className: 'bg-[#FFF6ED] text-[#7C5C3A]', label: '수집 대기' }, + needs_creation: { className: 'bg-[#FFF0F0] text-[#7C3A4B]', label: '제작 필요' }, +}; + +function formatViews(views: number): string { + if (views >= 1_000_000) return `${(views / 1_000_000).toFixed(1)}M`; + if (views >= 1_000) return `${Math.round(views / 1_000)}K`; + return String(views); +} + +export default function AssetCollection({ data }: AssetCollectionProps) { + const [activeFilter, setActiveFilter] = useState('all'); + + const filteredAssets = + activeFilter === 'all' + ? data.assets + : data.assets.filter((a) => a.source === activeFilter); + + return ( + + {/* Source Filter Tabs */} +
+ {filterTabs.map((tab) => ( + + ))} +
+ + {/* Asset Cards Grid */} +
+ {filteredAssets.map((asset, i) => { + const statusInfo = statusConfig[asset.status]; + return ( + + {/* Top badges row */} +
+ + {asset.sourceLabel} + + + {asset.type} + + + {statusInfo.label} + +
+ + {/* Title & Description */} +

{asset.title}

+

{asset.description}

+ + {/* Repurposing suggestions */} + {asset.repurposingSuggestions.length > 0 && ( +
+

+ Repurposing → +

+
+ {asset.repurposingSuggestions.map((suggestion, j) => ( + + {suggestion} + + ))} +
+
+ )} +
+ ); + })} +
+ + {/* YouTube Repurpose Section */} + {data.youtubeRepurpose.length > 0 && ( +
+

+ YouTube Top Videos for Repurposing +

+
+ {data.youtubeRepurpose.map((video, i) => ( + +
+ +

{video.title}

+
+
+ + {formatViews(video.views)} views + + + {video.type} + +
+

+ Repurpose As: +

+
+ {video.repurposeAs.map((suggestion, j) => ( + + {suggestion} + + ))} +
+
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/components/plan/BrandingGuide.tsx b/src/components/plan/BrandingGuide.tsx new file mode 100644 index 0000000..8539901 --- /dev/null +++ b/src/components/plan/BrandingGuide.tsx @@ -0,0 +1,408 @@ +import { useState, type ComponentType } from 'react'; +import { motion } from 'motion/react'; +import { + Youtube, + Instagram, + Facebook, + Globe, + Video, + MessageSquare, + CheckCircle2, + XCircle, + AlertCircle, + ArrowRight, +} from 'lucide-react'; +import { SectionWrapper } from '../report/ui/SectionWrapper'; +import type { BrandGuide } from '../../types/plan'; +import type { BrandInconsistency } from '../../types/report'; + +interface BrandingGuideProps { + data: BrandGuide; +} + +const tabItems = [ + { key: 'visual', label: 'Visual Identity', labelKr: '비주얼 아이덴티티' }, + { key: 'tone', label: 'Tone & Voice', labelKr: '톤 & 보이스' }, + { key: 'channels', label: 'Channel Rules', labelKr: '채널별 규칙' }, + { key: 'consistency', label: 'Brand Consistency', labelKr: '브랜드 일관성' }, +] as const; + +type TabKey = (typeof tabItems)[number]['key']; + +const channelIconMap: Record> = { + youtube: Youtube, + instagram: Instagram, + facebook: Facebook, + globe: Globe, + video: Video, + messagesquare: MessageSquare, +}; + +function getChannelIcon(icon: string) { + return channelIconMap[icon.toLowerCase()] ?? Globe; +} + +const statusColor: Record = { + correct: 'bg-[#F3F0FF] text-[#4A3A7C] border-[#D5CDF5]', + incorrect: 'bg-[#FFF0F0] text-[#7C3A4B] border-[#F5D5DC]', + missing: 'bg-slate-100 text-slate-500 border-slate-200', +}; + +const statusLabel: Record = { + correct: 'Correct', + incorrect: 'Incorrect', + missing: 'Missing', +}; + +/* ─── Visual Identity Tab ─── */ +function VisualIdentityTab({ data }: { data: BrandGuide }) { + return ( + + {/* Color Palette */} +
+

Color Palette

+
+ {data.colors.map((swatch) => ( +
+
+
+

{swatch.hex}

+

{swatch.name}

+

{swatch.usage}

+
+
+ ))} +
+
+ + {/* Typography */} +
+

Typography

+
+ {data.fonts.map((spec) => ( +
+

+ {spec.family} +

+

+ {spec.sampleText} +

+

+ {spec.weight} ·{' '} + {spec.usage} +

+
+ ))} +
+
+ + {/* Logo Rules */} +
+

Logo Rules

+
+ {data.logoRules.map((rule) => ( +
+
+ {rule.correct ? ( + + ) : ( + + )} +
+

{rule.rule}

+

{rule.description}

+
+
+
+ ))} +
+
+ + ); +} + +/* ─── Tone & Voice Tab ─── */ +function ToneVoiceTab({ tone }: { tone: BrandGuide['toneOfVoice'] }) { + return ( + + {/* Personality */} +
+

Personality

+
+ {tone.personality.map((trait) => ( + + {trait} + + ))} +
+
+ + {/* Communication Style */} +
+

Communication Style

+
+

+ {tone.communicationStyle} +

+
+
+ + {/* DO / DON'T */} +
+
+

+ DO +

+
+ {tone.doExamples.map((example, i) => ( +
+

{example}

+
+ ))} +
+
+
+

+ DON'T +

+
+ {tone.dontExamples.map((example, i) => ( +
+

{example}

+
+ ))} +
+
+
+
+ ); +} + +/* ─── Channel Rules Tab ─── */ +function ChannelRulesTab({ channels }: { channels: BrandGuide['channelBranding'] }) { + return ( + +
+ {channels.map((ch) => { + const Icon = getChannelIcon(ch.icon); + return ( +
+ {/* Header */} +
+
+ +
+

{ch.channel}

+ + {statusLabel[ch.currentStatus]} + +
+ + {/* Specs */} +
+
+

Profile Photo

+

{ch.profilePhoto}

+
+
+

Banner Spec

+

{ch.bannerSpec}

+
+
+

Bio Template

+
+

+ {ch.bioTemplate} +

+
+
+
+
+ ); + })} +
+
+ ); +} + +/* ─── Brand Consistency Tab (accordion) ─── */ +function BrandConsistencyTab({ inconsistencies }: { inconsistencies: BrandInconsistency[] }) { + const [expanded, setExpanded] = useState(0); + + return ( + +
+ {inconsistencies.map((item, i) => ( +
+ {/* Header */} + + + {/* Expanded */} + {expanded === i && ( +
+
+ {item.values.map((v) => ( +
+ + {v.channel} + + + {v.value} + + + {v.isCorrect ? ( + + ) : ( + + )} + +
+ ))} +
+ + {/* Impact */} +
+

+ + Impact +

+

{item.impact}

+
+ + {/* Recommendation */} +
+

+ + Recommendation +

+

{item.recommendation}

+
+
+ )} +
+ ))} +
+
+ ); +} + +/* ─── Main Component ─── */ +export default function BrandingGuide({ data }: BrandingGuideProps) { + const [activeTab, setActiveTab] = useState('visual'); + + return ( + + {/* Tabs */} +
+ {tabItems.map((tab) => ( + + ))} +
+ + {activeTab === 'visual' && } + {activeTab === 'tone' && } + {activeTab === 'channels' && } + {activeTab === 'consistency' && ( + + )} +
+ ); +} diff --git a/src/components/plan/ChannelStrategy.tsx b/src/components/plan/ChannelStrategy.tsx new file mode 100644 index 0000000..b92dca7 --- /dev/null +++ b/src/components/plan/ChannelStrategy.tsx @@ -0,0 +1,123 @@ +import { type ComponentType } from 'react'; +import { motion } from 'motion/react'; +import { + YoutubeFilled, + InstagramFilled, + FacebookFilled, + GlobeFilled, + VideoFilled, + MessageFilled, + CalendarFilled, + TiktokFilled, +} from '../icons/FilledIcons'; +import { SectionWrapper } from '../report/ui/SectionWrapper'; +import type { ChannelStrategyCard } from '../../types/plan'; + +interface ChannelStrategyProps { + channels: ChannelStrategyCard[]; +} + +const channelIconMap: Record> = { + youtube: YoutubeFilled, + instagram: InstagramFilled, + facebook: FacebookFilled, + globe: GlobeFilled, + video: VideoFilled, + messagesquare: MessageFilled, + tiktok: TiktokFilled, +}; + +function getChannelIcon(icon: string) { + return channelIconMap[icon.toLowerCase()] ?? GlobeFilled; +} + +const priorityStyle: Record = { + P0: 'bg-[#FFF0F0] text-[#7C3A4B] border border-[#F5D5DC] shadow-[2px_3px_8px_rgba(212,136,154,0.15)]', + P1: 'bg-[#FFF6ED] text-[#7C5C3A] border border-[#F5E0C5] shadow-[2px_3px_8px_rgba(212,168,114,0.15)]', + P2: 'bg-[#F3F0FF] text-[#4A3A7C] border border-[#D5CDF5] shadow-[2px_3px_8px_rgba(155,138,212,0.15)]', +}; + +export default function ChannelStrategy({ channels }: ChannelStrategyProps) { + return ( + +
+ {channels.map((ch, index) => { + const Icon = getChannelIcon(ch.icon); + + return ( + + {/* Header */} +
+
+ +
+

{ch.channelName}

+ + {ch.priority} + +
+ + {/* Current → Target */} +
+ + {ch.currentStatus} + + + + {ch.targetGoal} + +
+ + {/* Content Types */} +
+ {ch.contentTypes.map((type) => ( + + {type} + + ))} +
+ + {/* Posting Frequency */} +
+ +

{ch.postingFrequency}

+
+ + {/* Tone */} +

{ch.tone}

+ + {/* Format Guidelines */} +
    + {ch.formatGuidelines.map((guideline, i) => ( +
  • + + {guideline} +
  • + ))} +
+
+ ); + })} +
+
+ ); +} diff --git a/src/components/plan/ContentCalendar.tsx b/src/components/plan/ContentCalendar.tsx new file mode 100644 index 0000000..60cf9cd --- /dev/null +++ b/src/components/plan/ContentCalendar.tsx @@ -0,0 +1,153 @@ +import { motion } from 'motion/react'; +import { + VideoFilled, + FileTextFilled, + ShareFilled, + MegaphoneFilled, +} from '../icons/FilledIcons'; +import { SectionWrapper } from '../report/ui/SectionWrapper'; +import type { CalendarData, ContentCategory, CalendarEntry } from '../../types/plan'; + +interface ContentCalendarProps { + data: CalendarData; +} + +const contentTypeColors: Record = { + video: { bg: 'bg-[#F3F0FF]', text: 'text-[#6C5CE7]', entry: 'bg-[#F3F0FF] border-[#D5CDF5]', border: 'border-[#D5CDF5]', shadow: 'shadow-[2px_3px_8px_rgba(155,138,212,0.15)]' }, + blog: { bg: 'bg-[#EFF0FF]', text: 'text-[#3A3F7C]', entry: 'bg-[#EFF0FF] border-[#C5CBF5]', border: 'border-[#C5CBF5]', shadow: 'shadow-[2px_3px_8px_rgba(122,132,212,0.15)]' }, + social: { bg: 'bg-[#FFF6ED]', text: 'text-[#7C5C3A]', entry: 'bg-[#FFF6ED] border-[#F5E0C5]', border: 'border-[#F5E0C5]', shadow: 'shadow-[2px_3px_8px_rgba(212,168,114,0.15)]' }, + ad: { bg: 'bg-[#FFF0F0]', text: 'text-[#7C3A4B]', entry: 'bg-[#FFF0F0] border-[#F5D5DC]', border: 'border-[#F5D5DC]', shadow: 'shadow-[2px_3px_8px_rgba(212,136,154,0.15)]' }, +}; + +const contentTypeLabels: Record = { + video: 'Video', + blog: 'Blog', + social: 'Social', + ad: 'Ad', +}; + +const contentTypeIcons: Record = { + video: VideoFilled, + blog: FileTextFilled, + social: ShareFilled, + ad: MegaphoneFilled, +}; + +const dayHeaders = ['월', '화', '수', '목', '금', '토', '일']; + +export default function ContentCalendar({ data }: ContentCalendarProps) { + return ( + + {/* Monthly Summary */} +
+ {data.monthlySummary.map((item) => { + const colors = contentTypeColors[item.type]; + return ( + +
+ + {item.label} +
+ {item.count} +
+ ); + })} +
+ + {/* Weekly Calendar Grid */} + {data.weeks.map((week, weekIdx) => { + const dayCells: CalendarEntry[][] = Array.from({ length: 7 }, () => []); + for (const entry of week.entries) { + const dayIndex = entry.dayOfWeek; + if (dayIndex >= 0 && dayIndex <= 6) { + dayCells[dayIndex].push(entry); + } + } + + return ( + +

{week.label}

+
+ {/* Day headers */} + {dayHeaders.map((day) => ( +
+ {day} +
+ ))} + + {/* Day cells */} + {dayCells.map((entries, dayIdx) => ( +
0 + ? 'bg-slate-50/50 border border-slate-100' + : 'border border-dashed border-slate-200/60' + }`} + > + {entries.map((entry, entryIdx) => { + const colors = contentTypeColors[entry.contentType]; + const Icon = contentTypeIcons[entry.contentType]; + return ( +
+
+ +
+

+ {entry.title} +

+
+ ); + })} +
+ ))} +
+
+ ); + })} + + {/* Color Legend */} +
+ {(Object.keys(contentTypeColors) as ContentCategory[]).map((type) => { + const colors = contentTypeColors[type]; + return ( + + {contentTypeLabels[type]} + + ); + })} +
+
+ ); +} diff --git a/src/components/plan/ContentStrategy.tsx b/src/components/plan/ContentStrategy.tsx new file mode 100644 index 0000000..bfb9e82 --- /dev/null +++ b/src/components/plan/ContentStrategy.tsx @@ -0,0 +1,230 @@ +import { useState } from 'react'; +import { motion } from 'motion/react'; +import { ArrowRight, Video, FileText, Share2, Megaphone } from 'lucide-react'; +import { SectionWrapper } from '../report/ui/SectionWrapper'; +import type { ContentStrategyData } from '../../types/plan'; + +interface ContentStrategyProps { + data: ContentStrategyData; +} + +const tabItems = [ + { key: 'pillars', label: 'Content Pillars', labelKr: '콘텐츠 필러' }, + { key: 'types', label: 'Content Types', labelKr: '콘텐츠 유형' }, + { key: 'workflow', label: 'Production Workflow', labelKr: '제작 워크플로우' }, + { key: 'repurposing', label: 'Repurposing', labelKr: '콘텐츠 재활용' }, +] as const; + +type TabKey = (typeof tabItems)[number]['key']; + +const channelColorMap: Record = { + YouTube: 'bg-[#FFF0F0] text-[#7C3A4B]', + Instagram: 'bg-pink-100 text-pink-700', + Blog: 'bg-[#EFF0FF] text-[#3A3F7C]', + '블로그': 'bg-[#EFF0FF] text-[#3A3F7C]', + '네이버블로그': 'bg-[#F3F0FF] text-[#4A3A7C]', + '네이버': 'bg-[#F3F0FF] text-[#4A3A7C]', + Facebook: 'bg-indigo-100 text-indigo-700', + '홈페이지': 'bg-slate-100 text-slate-700', + Website: 'bg-slate-100 text-slate-700', + TikTok: 'bg-purple-100 text-purple-700', + '카카오': 'bg-[#FFF6ED] text-[#7C5C3A]', +}; + +function getChannelBadgeClass(channel: string): string { + for (const [key, value] of Object.entries(channelColorMap)) { + if (channel.toLowerCase().includes(key.toLowerCase())) return value; + } + return 'bg-slate-100 text-slate-700'; +} + +export default function ContentStrategy({ data }: ContentStrategyProps) { + const [activeTab, setActiveTab] = useState('pillars'); + + return ( + + {/* Tabs */} +
+ {tabItems.map((tab) => ( + + ))} +
+ + {/* Tab 1: Content Pillars */} + {activeTab === 'pillars' && ( + + {data.pillars.map((pillar, i) => ( + +

+ {pillar.title} +

+

{pillar.description}

+ + {pillar.relatedUSP} + +
    + {pillar.exampleTopics.map((topic, j) => ( +
  • + + {topic} +
  • + ))} +
+
+ ))} +
+ )} + + {/* Tab 2: Content Types */} + {activeTab === 'types' && ( + +
+
Format
+
Channels
+
Frequency
+
Purpose
+
+ {data.typeMatrix.map((row, i) => ( + +
+ {row.format} +
+
+ {row.channels.map((ch) => ( + + {ch} + + ))} +
+
{row.frequency}
+
{row.purpose}
+
+ ))} +
+ )} + + {/* Tab 3: Production Workflow */} + {activeTab === 'workflow' && ( + + {data.workflow.map((step, i) => ( +
+ +
+ {step.step} +
+

{step.name}

+

{step.description}

+
+ + {step.owner} + + + {step.duration} + +
+
+ {i < data.workflow.length - 1 && ( + + )} +
+ ))} +
+ )} + + {/* Tab 4: Repurposing */} + {activeTab === 'repurposing' && ( + + {/* Source Card */} +
+
+
+
+ + {/* Connector */} +
+
+
+ + {/* Outputs Grid */} +
+ {data.repurposingOutputs.map((output, i) => ( + +

{output.format}

+

{output.channel}

+

{output.description}

+
+ ))} +
+ + )} + + ); +} diff --git a/src/components/plan/MyAssetUpload.tsx b/src/components/plan/MyAssetUpload.tsx new file mode 100644 index 0000000..27de85e --- /dev/null +++ b/src/components/plan/MyAssetUpload.tsx @@ -0,0 +1,302 @@ +import { useState, useRef, useCallback, type DragEvent, type ChangeEvent } from 'react'; +import { motion, AnimatePresence } from 'motion/react'; +import { SectionWrapper } from '../report/ui/SectionWrapper'; +import { VideoFilled, FileTextFilled } from '../icons/FilledIcons'; + +// ─── Types ─── + +type UploadCategory = 'all' | 'image' | 'video' | 'text'; + +interface UploadedAsset { + id: string; + file: File; + category: 'image' | 'video' | 'text'; + previewUrl: string | null; + name: string; + size: string; + uploadedAt: Date; +} + +// ─── Helpers ─── + +function categorize(file: File): 'image' | 'video' | 'text' { + if (file.type.startsWith('image/')) return 'image'; + if (file.type.startsWith('video/')) return 'video'; + return 'text'; +} + +function formatSize(bytes: number): string { + if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`; + if (bytes >= 1024) return `${Math.round(bytes / 1024)} KB`; + return `${bytes} B`; +} + +function uid() { + return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; +} + +const categoryConfig: Record = { + all: { label: '전체' }, + image: { label: 'Image' }, + video: { label: 'Video' }, + text: { label: 'Text' }, +}; + +const categoryBadge: Record<'image' | 'video' | 'text', string> = { + image: 'bg-[#F3F0FF] text-[#4A3A7C] shadow-[2px_3px_6px_rgba(155,138,212,0.12)]', + video: 'bg-[#FFF0F0] text-[#7C3A4B] shadow-[2px_3px_6px_rgba(212,136,154,0.12)]', + text: 'bg-[#FFF6ED] text-[#7C5C3A] shadow-[2px_3px_6px_rgba(212,168,114,0.12)]', +}; + +const ACCEPT_MAP: Record = { + 'image/*': '.jpg,.jpeg,.png,.gif,.webp,.svg', + 'video/*': '.mp4,.mov,.webm,.avi', + 'text/*': '.txt,.md,.doc,.docx,.pdf,.csv,.json', +}; +const ALL_ACCEPT = Object.values(ACCEPT_MAP).join(','); + +// ─── Component ─── + +export default function MyAssetUpload() { + const [assets, setAssets] = useState([]); + const [activeFilter, setActiveFilter] = useState('all'); + const [isDragOver, setIsDragOver] = useState(false); + const inputRef = useRef(null); + + const processFiles = useCallback((files: FileList | File[]) => { + const newAssets: UploadedAsset[] = Array.from(files).map((file) => { + const cat = categorize(file); + const previewUrl = + cat === 'image' || cat === 'video' + ? URL.createObjectURL(file) + : null; + return { + id: uid(), + file, + category: cat, + previewUrl, + name: file.name, + size: formatSize(file.size), + uploadedAt: new Date(), + }; + }); + setAssets((prev) => [...newAssets, ...prev]); + }, []); + + const handleDrop = useCallback( + (e: DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + if (e.dataTransfer.files.length) processFiles(e.dataTransfer.files); + }, + [processFiles], + ); + + const handleInputChange = useCallback( + (e: ChangeEvent) => { + if (e.target.files?.length) { + processFiles(e.target.files); + e.target.value = ''; + } + }, + [processFiles], + ); + + const removeAsset = useCallback((id: string) => { + setAssets((prev) => { + const found = prev.find((a) => a.id === id); + if (found?.previewUrl) URL.revokeObjectURL(found.previewUrl); + return prev.filter((a) => a.id !== id); + }); + }, []); + + const filtered = + activeFilter === 'all' ? assets : assets.filter((a) => a.category === activeFilter); + + const counts = { + all: assets.length, + image: assets.filter((a) => a.category === 'image').length, + video: assets.filter((a) => a.category === 'video').length, + text: assets.filter((a) => a.category === 'text').length, + }; + + return ( + + {/* Drop Zone */} +
{ e.preventDefault(); setIsDragOver(true); }} + onDragLeave={() => setIsDragOver(false)} + onDrop={handleDrop} + onClick={() => inputRef.current?.click()} + className={`relative rounded-2xl border-2 border-dashed p-10 md:p-14 text-center cursor-pointer transition-all mb-8 ${ + isDragOver + ? 'border-[#9B8AD4] bg-[#F3F0FF]/60 scale-[1.01]' + : 'border-slate-200 bg-slate-50/50 hover:border-[#D5CDF5] hover:bg-[#F3F0FF]/20' + }`} + > + + + {/* Upload Icon */} +
+
+ + + + +
+
+ +

+ 파일을 드래그하거나 클릭하여 업로드 +

+

+ Image, Video, Text 파일 지원 (JPG, PNG, MP4, MOV, TXT, PDF, DOC 등) +

+ + {/* File Type Badges */} +
+ {(['image', 'video', 'text'] as const).map((cat) => ( + + {cat === 'image' ? 'Image' : cat === 'video' ? 'Video' : 'Text'} + + ))} +
+
+ + {/* Filter Tabs + Count */} + {assets.length > 0 && ( + <> +
+ {(Object.keys(categoryConfig) as UploadCategory[]).map((key) => ( + + ))} +
+ + {/* Uploaded Assets Grid */} +
+ + {filtered.map((asset) => ( + + {/* Preview Area */} +
+ {asset.category === 'image' && asset.previewUrl && ( + {asset.name} + )} + {asset.category === 'video' && asset.previewUrl && ( +
+ + {/* Info */} +
+

+ {asset.name} +

+

{asset.size}

+
+
+ ))} +
+
+ + )} +
+ ); +} diff --git a/src/components/plan/PlanCTA.tsx b/src/components/plan/PlanCTA.tsx new file mode 100644 index 0000000..fecac72 --- /dev/null +++ b/src/components/plan/PlanCTA.tsx @@ -0,0 +1,64 @@ +import { motion } from 'motion/react'; +import { useNavigate } from 'react-router'; +import { Rocket, Download, Loader2 } from 'lucide-react'; +import { useExportPDF } from '../../hooks/useExportPDF'; + +export default function PlanCTA() { + const { exportPDF, isExporting } = useExportPDF(); + const navigate = useNavigate(); + + return ( + +
+
+
+
+ +
+
+ +

+ 콘텐츠 제작을 시작하세요 +

+ +

+ INFINITH가 브랜딩부터 콘텐츠 제작, 채널 배포까지 자동화합니다. +

+ +
+ + + +
+
+
+
+ ); +} diff --git a/src/components/plan/PlanHeader.tsx b/src/components/plan/PlanHeader.tsx new file mode 100644 index 0000000..40a61f7 --- /dev/null +++ b/src/components/plan/PlanHeader.tsx @@ -0,0 +1,113 @@ +import { motion } from 'motion/react'; +import { Calendar, Globe } from 'lucide-react'; + +interface PlanHeaderProps { + clinicName: string; + clinicNameEn: string; + date: string; + targetUrl: string; +} + +export default function PlanHeader({ + clinicName, + clinicNameEn, + date, + targetUrl, +}: PlanHeaderProps) { + return ( +
+ {/* Animated blobs */} + + + + +
+
+ {/* Left: Text content */} + + + Marketing Execution Plan + + + + {clinicName} + + + + {clinicNameEn} + + + + + + {date} + + + + {targetUrl} + + + + + {/* Right: 90 Days badge */} + +
+ + 90 + + Days +
+
+
+
+
+ ); +} diff --git a/src/components/report/ChannelOverview.tsx b/src/components/report/ChannelOverview.tsx new file mode 100644 index 0000000..5499f93 --- /dev/null +++ b/src/components/report/ChannelOverview.tsx @@ -0,0 +1,64 @@ +import type { ComponentType } from 'react'; +import { motion } from 'motion/react'; +import { Youtube, Instagram, Globe, Star, Facebook, Search } from 'lucide-react'; +import { SectionWrapper } from './ui/SectionWrapper'; +import { ScoreRing } from './ui/ScoreRing'; +import { SeverityBadge } from './ui/SeverityBadge'; +import type { ChannelScore } from '../../types/report'; + +interface ChannelOverviewProps { + channels: ChannelScore[]; +} + +const iconMap: Record> = { + youtube: Youtube, + instagram: Instagram, + facebook: Facebook, + star: Star, + globe: Globe, + search: Search, +}; + +const brandColor: Record = { + facebook: '#1877F2', + instagram: '#E1306C', + youtube: '#FF0000', + globe: '#6B2D8B', +}; + +function getChannelColor(icon: string | undefined): string | undefined { + return brandColor[icon?.toLowerCase() ?? '']; +} + +export default function ChannelOverview({ channels }: ChannelOverviewProps) { + return ( + +
+ {channels.map((ch, i) => { + const Icon = iconMap[ch.icon?.toLowerCase()] ?? Globe; + const color = getChannelColor(ch.icon); + return ( + +
+ +
+

{ch.channel}

+ +

+ {ch.headline} +

+ +
+ ); + })} +
+
+ ); +} diff --git a/src/components/report/ClinicSnapshot.tsx b/src/components/report/ClinicSnapshot.tsx new file mode 100644 index 0000000..4ff30c0 --- /dev/null +++ b/src/components/report/ClinicSnapshot.tsx @@ -0,0 +1,114 @@ +import { motion } from 'motion/react'; +import { Calendar, Users, MapPin, Phone, Award, Star, Globe } from 'lucide-react'; +import { SectionWrapper } from './ui/SectionWrapper'; +import type { ClinicSnapshot as ClinicSnapshotType } from '../../types/report'; + +interface ClinicSnapshotProps { + data: ClinicSnapshotType; +} + +function formatNumber(n: number): string { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(0)}K`; + return n.toLocaleString(); +} + +const infoFields = (data: ClinicSnapshotType) => [ + { label: '개원', value: `${data.established} (${data.yearsInBusiness}년)`, icon: Calendar }, + { label: '의료진', value: `${data.staffCount}명`, icon: Users }, + { label: '강남언니 평점', value: `${data.overallRating} / 5.0`, icon: Star }, + { label: '리뷰 수', value: formatNumber(data.totalReviews), icon: Star }, + { label: '시술 가격대', value: `${data.priceRange.min} ~ ${data.priceRange.max}`, icon: Globe }, + { label: '위치', value: `${data.location} (${data.nearestStation})`, icon: MapPin }, + { label: '전화', value: data.phone, icon: Phone }, + { label: '도메인', value: data.domain, icon: Globe }, +]; + +export default function ClinicSnapshot({ data }: ClinicSnapshotProps) { + const fields = infoFields(data); + + return ( + +
+ {fields.map((field, i) => { + const Icon = field.icon; + return ( + +
+
+ +
+
+

{field.label}

+

{field.value}

+
+
+
+ ); + })} +
+ + {/* Lead Doctor Highlight */} + +
+ +

대표 원장

+
+

{data.leadDoctor.name}

+

{data.leadDoctor.credentials}

+
+
+ {Array.from({ length: 5 }).map((_, i) => ( + + ))} + + {data.leadDoctor.rating} + +
+ + 리뷰 {formatNumber(data.leadDoctor.reviewCount)}건 + +
+
+ + {/* Certifications */} + {data.certifications.length > 0 && ( + +

인증 및 자격

+
+ {data.certifications.map((cert) => ( + + {cert} + + ))} +
+
+ )} +
+ ); +} diff --git a/src/components/report/FacebookAudit.tsx b/src/components/report/FacebookAudit.tsx new file mode 100644 index 0000000..b562617 --- /dev/null +++ b/src/components/report/FacebookAudit.tsx @@ -0,0 +1,353 @@ +import { useState } from 'react'; +import { motion } from 'motion/react'; +import { + Facebook, AlertCircle, AlertTriangle, ExternalLink, CheckCircle2, XCircle, + ArrowRight, Link2, MessageCircle, TrendingUp, Eye, ImageIcon, Globe, +} from 'lucide-react'; +import { SectionWrapper } from './ui/SectionWrapper'; +import { SeverityBadge } from './ui/SeverityBadge'; +import { EvidenceGallery } from './ui/EvidenceGallery'; +import type { + FacebookAudit as FacebookAuditType, + FacebookPage, + BrandInconsistency, + DiagnosisItem, +} from '../../types/report'; + +function formatNumber(n: number): string { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; + return n.toLocaleString(); +} + +/* ─── Page Card ─── */ +function PageCard({ page, index }: { key?: string | number; page: FacebookPage; index: number }) { + const isKR = page.language === 'KR'; + const langColor = isKR ? 'bg-slate-100 text-slate-700' : 'bg-[#EFF0FF] text-[#3A3F7C]'; + const isLogoMismatch = page.logo?.includes('불일치'); + const isLowFollowers = page.followers < 500; + + return ( + + {/* Header */} +
+
+ + {page.label} + +
+ +
+
+ {isKR && isLowFollowers && ( + + 방치 상태 + + )} + {page.hasWhatsApp && ( + + WhatsApp 연결 + + )} +
+ +

{page.pageName}

+

{page.category}

+ + {/* Metrics grid */} +
+
+

팔로워

+

+ {formatNumber(page.followers)} +

+
+
+

리뷰

+

+ {page.reviews} +

+
+
+

팔로잉

+

{formatNumber(page.following)}

+
+
+ + {/* Detail rows */} +
+
+ 최근 게시물 + {page.recentPostAge} +
+ {page.postFrequency && ( +
+ 게시 빈도 + {page.postFrequency} +
+ )} + {page.topContentType && ( +
+ 콘텐츠 유형 + {page.topContentType} +
+ )} + {page.engagement && ( +
+ 참여율 + {page.engagement} +
+ )} +
+ + {/* Logo analysis - the enhanced version */} +
+
+ {isLogoMismatch + ? + : + } +

+ 로고 {page.logo} +

+
+

+ {page.logoDescription} +

+
+ + {/* Domain link */} +
+
+ +

연결 도메인

+
+

+ {page.linkedDomain || page.link} +

+
+ + {/* Bio */} +
+

Bio

+

"{page.bio}"

+
+ + {/* Link */} + + + {page.url} + +
+ ); +} + +/* ─── Brand Inconsistency Map ─── */ +function BrandConsistencyMap({ inconsistencies }: { inconsistencies: BrandInconsistency[] }) { + const [expanded, setExpanded] = useState(0); + + return ( + +

Brand Consistency Map

+

전 채널 브랜드 일관성 분석

+ +
+ {inconsistencies.map((item, i) => ( +
+ {/* Header - clickable */} + + + {/* Expanded content */} + {expanded === i && ( +
+ {/* Channel values */} +
+ {item.values.map((v) => ( +
+ {v.channel} + + {v.value} + + + {v.isCorrect + ? + : + } + +
+ ))} +
+ + {/* Impact */} +
+

+ + Impact +

+

{item.impact}

+
+ + {/* Recommendation */} +
+

+ + Recommendation +

+

{item.recommendation}

+
+
+ )} +
+ ))} +
+
+ ); +} + +/* ─── Diagnosis Section ─── */ +function DiagnosisSection({ items }: { items: DiagnosisItem[] }) { + return ( + +

진단 결과

+

Facebook 채널 문제점

+ +
+ {items.map((item, i) => ( +
+
+
+

{item.category}

+
+

{item.detail}

+
+ +
+
+ {item.evidenceIds && item.evidenceIds.length > 0 && ( + + )} +
+ ))} +
+
+ ); +} + +/* ─── Consolidation Recommendation ─── */ +function ConsolidationCard({ text }: { text: string }) { + return ( + +
+
+ +
+
+

통합 권장 사항

+

{text}

+
+
+
+ ); +} + +/* ─── Main Component ─── */ +export default function FacebookAudit({ data }: { data: FacebookAuditType }) { + return ( + + {/* Page cards side by side */} +
+ {data.pages.map((page, i) => ( + + ))} +
+ + {/* Brand Consistency Map - the new enhanced section */} + {data.brandInconsistencies && data.brandInconsistencies.length > 0 && ( + + )} + + {/* Diagnosis table */} + {data.diagnosis && data.diagnosis.length > 0 && ( + + )} + + {/* Consolidation recommendation */} + {data.consolidationRecommendation && ( + + )} +
+ ); +} diff --git a/src/components/report/InstagramAudit.tsx b/src/components/report/InstagramAudit.tsx new file mode 100644 index 0000000..7da82d2 --- /dev/null +++ b/src/components/report/InstagramAudit.tsx @@ -0,0 +1,136 @@ +import { motion } from 'motion/react'; +import { Instagram, AlertCircle, FileText, Users, Eye } from 'lucide-react'; +import { SectionWrapper } from './ui/SectionWrapper'; +import { MetricCard } from './ui/MetricCard'; +import { DiagnosisRow } from './ui/DiagnosisRow'; +import type { InstagramAudit as InstagramAuditType, InstagramAccount } from '../../types/report'; + +interface InstagramAuditProps { + data: InstagramAuditType; +} + +function formatNumber(n: number): string { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(0)}K`; + return n.toLocaleString(); +} + +function AccountCard({ account, index }: { key?: string | number; account: InstagramAccount; index: number }) { + const langColor = account.language === 'KR' + ? 'bg-slate-100 text-slate-700' + : 'bg-[#EFF0FF] text-[#3A3F7C]'; + + return ( + + {/* Language badge + handle */} +
+ + {account.label} + +
+ +
+
+ +

{account.handle}

+

{account.category}

+ + {/* Compact metrics */} +
+
+

게시물

+

{formatNumber(account.posts)}

+
+
+

팔로워

+

{formatNumber(account.followers)}

+
+
+

팔로잉

+

{formatNumber(account.following)}

+
+
+ + {/* Format & reels */} +
+
+ 콘텐츠 포맷 + {account.contentFormat} +
+
+ 릴스 수 + + {account.reelsCount === 0 ? ( + + + 0 (미운영) + + ) : ( + account.reelsCount + )} + +
+
+ + {/* Highlights */} + {account.highlights.length > 0 && ( +
+

하이라이트

+
+ {account.highlights.map((h) => ( + + {h} + + ))} +
+
+ )} + + {/* Bio */} + {account.bio && ( +
+

Bio

+

{account.bio}

+
+ )} +
+ ); +} + +export default function InstagramAudit({ data }: InstagramAuditProps) { + return ( + + {/* Account cards */} +
+ {data.accounts.map((account, i) => ( + + ))} +
+ + {/* Diagnosis */} + {data.diagnosis.length > 0 && ( + +

진단 결과

+ {data.diagnosis.map((item, i) => ( + + ))} +
+ )} +
+ ); +} diff --git a/src/components/report/KPIDashboard.tsx b/src/components/report/KPIDashboard.tsx new file mode 100644 index 0000000..d85ddec --- /dev/null +++ b/src/components/report/KPIDashboard.tsx @@ -0,0 +1,106 @@ +import { motion } from 'motion/react'; +import { TrendingUp, ArrowUpRight, Download, Loader2 } from 'lucide-react'; +import { SectionWrapper } from './ui/SectionWrapper'; +import { useExportPDF } from '../../hooks/useExportPDF'; +import type { KPIMetric } from '../../types/report'; + +interface KPIDashboardProps { + metrics: KPIMetric[]; +} + +function isNegativeValue(value: string): boolean { + const lower = value.toLowerCase(); + return lower === '0' || lower.includes('없음') || lower.includes('불가') || lower === 'n/a'; +} + +export default function KPIDashboard({ metrics }: KPIDashboardProps) { + const { exportPDF, isExporting } = useExportPDF(); + + return ( + + {/* KPI Table */} + + {/* Header */} +
+
Metric
+
Current
+
3-Month Target
+
12-Month Target
+
+ + {/* Data rows */} + {metrics.map((metric, i) => ( + +
{metric.metric}
+
+ {metric.current} +
+
{metric.target3Month}
+
{metric.target12Month}
+
+ ))} +
+ + {/* CTA Card */} + +
+
+ +
+

+ Start Your Transformation +

+

+ INFINITH와 함께 데이터 기반 마케팅 전환을 시작하세요. 90일 안에 측정 가능한 성과를 만들어 드립니다. +

+
+ + 마케팅 기획 + + + +
+
+
+
+ ); +} diff --git a/src/components/report/OtherChannels.tsx b/src/components/report/OtherChannels.tsx new file mode 100644 index 0000000..dc53c59 --- /dev/null +++ b/src/components/report/OtherChannels.tsx @@ -0,0 +1,155 @@ +import { motion } from 'motion/react'; +import { CheckCircle2, XCircle, HelpCircle, ExternalLink, AlertCircle, Globe, Shield } from 'lucide-react'; +import { SectionWrapper } from './ui/SectionWrapper'; +import type { OtherChannel, WebsiteAudit } from '../../types/report'; + +interface OtherChannelsProps { + channels: OtherChannel[]; + website: WebsiteAudit; +} + +const statusConfig = { + active: { icon: CheckCircle2, color: 'text-[#9B8AD4]', label: '활성' }, + inactive: { icon: XCircle, color: 'text-[#D4889A]', label: '비활성' }, + unknown: { icon: HelpCircle, color: 'text-slate-400', label: '미확인' }, + not_found: { icon: XCircle, color: 'text-[#D4889A]', label: '미발견' }, +}; + +export default function OtherChannels({ channels, website }: OtherChannelsProps) { + return ( + + {/* Other Channels */} + +
+

기타 채널 현황

+
+
+ {channels.map((ch, i) => { + const cfg = statusConfig[ch.status]; + const StatusIcon = cfg.icon; + return ( + + +
+

{ch.name}

+

{ch.details}

+
+ {cfg.label} + {ch.url && ( + + + + )} +
+ ); + })} +
+
+ + {/* Website Tech Audit */} + +
+ +

웹사이트 기술 진단

+
+ + {/* Domain info */} +
+

기본 도메인: {website.primaryDomain}

+ {website.additionalDomains.length > 0 && ( +
+ {website.additionalDomains.map((d) => ( +

+ {d.domain} — {d.purpose} +

+ ))} +
+ )} +

+ 주요 CTA: {website.mainCTA} +

+
+ + {/* Tracking Pixels */} +
+

트래킹 픽셀 설치 현황

+
+ {website.trackingPixels.map((pixel) => ( +
+ {pixel.installed ? ( + + ) : ( + + )} +
+

+ {pixel.name} +

+ {pixel.details && ( +

{pixel.details}

+ )} +
+
+ ))} +
+
+ + {/* SNS Links Status */} + {!website.snsLinksOnSite && ( + + +
+

홈페이지에 SNS 링크 없음

+

+ 웹사이트에서 소셜 미디어 채널로의 연결이 없습니다. 방문자가 SNS를 통해 브랜드와 연결할 수 없습니다. +

+
+
+ )} + {website.snsLinksOnSite && ( +
+ +

홈페이지에 SNS 링크 연결됨

+
+ )} +
+
+ ); +} diff --git a/src/components/report/ProblemDiagnosis.tsx b/src/components/report/ProblemDiagnosis.tsx new file mode 100644 index 0000000..2bec680 --- /dev/null +++ b/src/components/report/ProblemDiagnosis.tsx @@ -0,0 +1,50 @@ +import { motion } from 'motion/react'; +import { AlertCircle } from 'lucide-react'; +import { SectionWrapper } from './ui/SectionWrapper'; +import type { DiagnosisItem } from '../../types/report'; + +interface ProblemDiagnosisProps { + diagnosis: DiagnosisItem[]; +} + +const severityDot: Record = { + critical: 'bg-[#C084CF]', + warning: 'bg-[#8B9CF7]', + good: 'bg-[#7C6DD8]', + excellent: 'bg-[#6C5CE7]', + unknown: 'bg-slate-400', +}; + +export default function ProblemDiagnosis({ diagnosis }: ProblemDiagnosisProps) { + return ( + +
+ {diagnosis.map((item, i) => ( + + {/* Severity dot */} +
+ +
+ +
+
+ +
+
+

{item.category}

+

{item.detail}

+
+
+
+ ))} +
+
+ ); +} diff --git a/src/components/report/ReportHeader.tsx b/src/components/report/ReportHeader.tsx new file mode 100644 index 0000000..3b535a6 --- /dev/null +++ b/src/components/report/ReportHeader.tsx @@ -0,0 +1,143 @@ +import { motion } from 'motion/react'; +import { Calendar, Globe, MapPin } from 'lucide-react'; +import { ScoreRing } from './ui/ScoreRing'; + +interface ReportHeaderProps { + clinicName: string; + clinicNameEn: string; + overallScore: number; + date: string; + targetUrl: string; + location: string; + logoImage?: string; + brandColors?: { primary: string; accent: string; text: string }; +} + +export default function ReportHeader({ + clinicName, + logoImage, + brandColors, + clinicNameEn, + overallScore, + date, + targetUrl, + location, +}: ReportHeaderProps) { + return ( +
+ {/* Animated blobs */} + + + + +
+
+ {/* Left: Text content */} + + + Marketing Intelligence Report + + + {logoImage && ( + + {clinicName} { (e.target as HTMLImageElement).style.display = 'none'; }} + /> + + )} + + + {clinicName} + + + + {clinicNameEn} + + + + + + {date} + + + + {targetUrl} + + + + {location} + + + + + {/* Right: Score ring */} + +
+

+ Overall Score +

+ +
+
+
+
+
+ ); +} diff --git a/src/components/report/ReportNav.tsx b/src/components/report/ReportNav.tsx new file mode 100644 index 0000000..b6ec841 --- /dev/null +++ b/src/components/report/ReportNav.tsx @@ -0,0 +1,81 @@ +import { useEffect, useRef, useState } from 'react'; + +interface ReportNavProps { + sections: { id: string; label: string }[]; +} + +export function ReportNav({ sections }: ReportNavProps) { + const [activeId, setActiveId] = useState(sections[0]?.id ?? ''); + const navRef = useRef(null); + const tabRefs = useRef>(new Map()); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + const visible = entries + .filter((e) => e.isIntersecting) + .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top); + + if (visible.length > 0) { + setActiveId(visible[0].target.id); + } + }, + { rootMargin: '-100px 0px -60% 0px', threshold: 0 } + ); + + sections.forEach(({ id }) => { + const el = document.getElementById(id); + if (el) observer.observe(el); + }); + + return () => observer.disconnect(); + }, [sections]); + + useEffect(() => { + const activeTab = tabRefs.current.get(activeId); + if (activeTab && navRef.current) { + activeTab.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + inline: 'center', + }); + } + }, [activeId]); + + const handleClick = (id: string) => { + const el = document.getElementById(id); + if (el) { + el.scrollIntoView({ behavior: 'smooth' }); + } + }; + + return ( + + ); +} diff --git a/src/components/report/RoadmapTimeline.tsx b/src/components/report/RoadmapTimeline.tsx new file mode 100644 index 0000000..1105635 --- /dev/null +++ b/src/components/report/RoadmapTimeline.tsx @@ -0,0 +1,61 @@ +import { motion } from 'motion/react'; +import { CheckCircle2 } from 'lucide-react'; +import { SectionWrapper } from './ui/SectionWrapper'; +import type { RoadmapMonth } from '../../types/report'; + +interface RoadmapTimelineProps { + months: RoadmapMonth[]; +} + +export default function RoadmapTimeline({ months }: RoadmapTimelineProps) { + return ( + +
+ {months.map((month, i) => ( + + {/* Month badge */} +
+
+ {month.month} +
+
+

{month.title}

+

{month.subtitle}

+
+
+ + {/* Task checklist */} +
    + {month.tasks.map((task, j) => ( + + {task.completed ? ( + + ) : ( +
    + )} + + {task.task} + + + ))} +
+
+ ))} +
+
+ ); +} diff --git a/src/components/report/TransformationProposal.tsx b/src/components/report/TransformationProposal.tsx new file mode 100644 index 0000000..128bede --- /dev/null +++ b/src/components/report/TransformationProposal.tsx @@ -0,0 +1,205 @@ +import { useState, type ComponentType } from 'react'; +import { motion } from 'motion/react'; +import { Youtube, Instagram, Facebook, Globe, Search, Star, ArrowUpRight } from 'lucide-react'; +import { SectionWrapper } from './ui/SectionWrapper'; +import { ComparisonRow } from './ui/ComparisonRow'; +import type { TransformationProposal as TransformationProposalType, PlatformStrategy } from '../../types/report'; + +interface TransformationProposalProps { + data: TransformationProposalType; +} + +const tabItems = [ + { key: 'brand', label: 'Brand Identity', labelKr: '브랜드 아이덴티티' }, + { key: 'content', label: 'Content Strategy', labelKr: '콘텐츠 전략' }, + { key: 'platform', label: 'Platform Strategies', labelKr: '플랫폼 전략' }, + { key: 'website', label: 'Website', labelKr: '웹사이트 개선' }, + { key: 'newChannel', label: 'New Channels', labelKr: '신규 채널' }, +] as const; + +type TabKey = (typeof tabItems)[number]['key']; + +const platformIconMap: Record> = { + youtube: Youtube, + instagram: Instagram, + facebook: Facebook, + website: Globe, + blog: Globe, + naver: Search, + tiktok: Star, +}; + +function PlatformStrategyCard({ strategy, index }: { key?: string | number; strategy: PlatformStrategy; index: number }) { + const Icon = platformIconMap[strategy.icon?.toLowerCase()] ?? Globe; + + return ( + +
+
+ +
+

{strategy.platform}

+
+ + {/* Current to Target */} +
+ + {strategy.currentMetric} + + + + {strategy.targetMetric} + +
+ + {/* Strategy bullets */} +
    + {strategy.strategies.map((s, i) => ( +
  • + +
    +

    {s.strategy}

    +

    {s.detail}

    +
    +
  • + ))} +
+
+ ); +} + +const priorityColor: Record = { + high: 'bg-[#FFF0F0] text-[#7C3A4B] border-[#F5D5DC]', + medium: 'bg-[#FFF6ED] text-[#7C5C3A] border-[#F5E0C5]', + low: 'bg-[#F3F0FF] text-[#4A3A7C] border-[#D5CDF5]', + 높음: 'bg-[#FFF0F0] text-[#7C3A4B] border-[#F5D5DC]', + 중간: 'bg-[#FFF6ED] text-[#7C5C3A] border-[#F5E0C5]', + 낮음: 'bg-[#F3F0FF] text-[#4A3A7C] border-[#D5CDF5]', +}; + +export default function TransformationProposal({ data }: TransformationProposalProps) { + const [activeTab, setActiveTab] = useState('brand'); + + return ( + + {/* Tabs */} +
+ {tabItems.map((tab) => ( + + ))} +
+ + {/* Brand Identity */} + {activeTab === 'brand' && ( + +

브랜드 아이덴티티

+ {data.brandIdentity.map((item, i) => ( + + ))} +
+ )} + + {/* Content Strategy */} + {activeTab === 'content' && ( + +

콘텐츠 전략

+ {data.contentStrategy.map((item, i) => ( + + ))} +
+ )} + + {/* Platform Strategies */} + {activeTab === 'platform' && ( +
+ {data.platformStrategies.map((strategy, i) => ( + + ))} +
+ )} + + {/* Website Improvements */} + {activeTab === 'website' && ( + +

웹사이트 개선

+ {data.websiteImprovements.map((item, i) => ( + + ))} +
+ )} + + {/* New Channel Proposals */} + {activeTab === 'newChannel' && ( + + + + + + + + + + + {data.newChannelProposals.map((ch, i) => ( + + + + + + ))} + +
채널우선순위근거
{ch.channel} + + {ch.priority} + + {ch.rationale}
+
+ )} +
+ ); +} diff --git a/src/components/report/YouTubeAudit.tsx b/src/components/report/YouTubeAudit.tsx new file mode 100644 index 0000000..426ad61 --- /dev/null +++ b/src/components/report/YouTubeAudit.tsx @@ -0,0 +1,177 @@ +import { motion } from 'motion/react'; +import { Youtube, Users, Video, Eye, TrendingUp, ExternalLink } from 'lucide-react'; +import { SectionWrapper } from './ui/SectionWrapper'; +import { MetricCard } from './ui/MetricCard'; +import { DiagnosisRow } from './ui/DiagnosisRow'; +import type { YouTubeAudit as YouTubeAuditType } from '../../types/report'; + +interface YouTubeAuditProps { + data: YouTubeAuditType; +} + +function formatNumber(n: number): string { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(0)}K`; + return n.toLocaleString(); +} + +export default function YouTubeAudit({ data }: YouTubeAuditProps) { + return ( + + {/* Metrics row */} +
+ } + subtext={data.subscriberRank} + /> + } + /> + } + /> + } + subtext={`${data.weeklyViewGrowth.percentage > 0 ? '+' : ''}${data.weeklyViewGrowth.percentage}%`} + trend={data.weeklyViewGrowth.percentage > 0 ? 'up' : data.weeklyViewGrowth.percentage < 0 ? 'down' : 'neutral'} + /> +
+ + {/* Channel info card */} + +
+
+ +
+
+

{data.channelName}

+

{data.handle}

+
+
+

{data.channelDescription}

+
+ 개설일: {data.channelCreatedDate} + 평균 영상 길이: {data.avgVideoLength} + 업로드 빈도: {data.uploadFrequency} +
+ + {/* Linked URLs */} + {data.linkedUrls.length > 0 && ( +
+ {data.linkedUrls.map((link) => ( + + + {link.label} + + ))} +
+ )} +
+ + {/* Playlists */} + {data.playlists.length > 0 && ( + +

재생목록

+
+ {data.playlists.map((pl) => ( + + {pl} + + ))} +
+
+ )} + + {/* Top Videos */} + {data.topVideos.length > 0 && ( + +

인기 영상 TOP {data.topVideos.length}

+
+ {data.topVideos.map((video, i) => ( + +
+ + {video.type} + + {video.uploadedAgo} +
+

+ {video.title} +

+
+ + {formatNumber(video.views)} + views +
+
+ ))} +
+
+ )} + + {/* Diagnosis */} + {data.diagnosis.length > 0 && ( + +

진단 결과

+ {data.diagnosis.map((item, i) => ( + + ))} +
+ )} +
+ ); +} diff --git a/src/components/report/ui/ComparisonRow.tsx b/src/components/report/ui/ComparisonRow.tsx new file mode 100644 index 0000000..de82594 --- /dev/null +++ b/src/components/report/ui/ComparisonRow.tsx @@ -0,0 +1,20 @@ +interface ComparisonRowProps { + key?: string | number; + area: string; + asIs: string; + toBe: string; +} + +export function ComparisonRow({ area, asIs, toBe }: ComparisonRowProps) { + return ( +
+ {area} +
+ {asIs} +
+
+ {toBe} +
+
+ ); +} diff --git a/src/components/report/ui/DiagnosisRow.tsx b/src/components/report/ui/DiagnosisRow.tsx new file mode 100644 index 0000000..05c7fcc --- /dev/null +++ b/src/components/report/ui/DiagnosisRow.tsx @@ -0,0 +1,28 @@ +import type { Severity } from '../../../types/report'; +import { SeverityBadge } from './SeverityBadge'; +import { EvidenceGallery } from './EvidenceGallery'; + +interface DiagnosisRowProps { + key?: string | number; + category: string; + detail: string; + severity: Severity; + evidenceIds?: string[]; +} + +export function DiagnosisRow({ category, detail, severity, evidenceIds }: DiagnosisRowProps) { + return ( +
+
+ + {category} + +

{detail}

+
+ +
+
+ +
+ ); +} diff --git a/src/components/report/ui/EvidenceGallery.tsx b/src/components/report/ui/EvidenceGallery.tsx new file mode 100644 index 0000000..d260bb6 --- /dev/null +++ b/src/components/report/ui/EvidenceGallery.tsx @@ -0,0 +1,34 @@ +import { useScreenshots } from '../../../contexts/ScreenshotContext'; +import { EvidenceScreenshot } from './EvidenceScreenshot'; + +interface EvidenceGalleryProps { + evidenceIds?: string[]; + compact?: boolean; +} + +export function EvidenceGallery({ evidenceIds, compact }: EvidenceGalleryProps) { + const { getByIds } = useScreenshots(); + + if (!evidenceIds?.length) return null; + + const items = getByIds(evidenceIds); + if (!items.length) return null; + + if (items.length === 1) { + return ( +
+ +
+ ); + } + + return ( +
+ {items.map((item) => ( +
+ +
+ ))} +
+ ); +} diff --git a/src/components/report/ui/EvidenceLightbox.tsx b/src/components/report/ui/EvidenceLightbox.tsx new file mode 100644 index 0000000..5e56592 --- /dev/null +++ b/src/components/report/ui/EvidenceLightbox.tsx @@ -0,0 +1,105 @@ +import { useEffect, useCallback } from 'react'; +import { motion, AnimatePresence } from 'motion/react'; +import type { ScreenshotEvidence } from '../../../types/report'; + +interface EvidenceLightboxProps { + evidence: ScreenshotEvidence | null; + onClose: () => void; +} + +export function EvidenceLightbox({ evidence, onClose }: EvidenceLightboxProps) { + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }, + [onClose], + ); + + useEffect(() => { + if (evidence) { + document.addEventListener('keydown', handleKeyDown); + document.body.style.overflow = 'hidden'; + } + return () => { + document.removeEventListener('keydown', handleKeyDown); + document.body.style.overflow = ''; + }; + }, [evidence, handleKeyDown]); + + return ( + + {evidence && ( + + {/* Backdrop */} +
+ + {/* Content */} + + {/* Close button */} + + + {/* Image container with annotations */} +
+ {evidence.caption} + + {/* Annotation overlays */} + {evidence.annotations?.map((ann, i) => ( +
+ {ann.label && ( + + {ann.label} + + )} +
+ ))} +
+ + {/* Caption */} +
+

{evidence.caption}

+ {evidence.sourceUrl && ( +

{evidence.sourceUrl}

+ )} +
+
+ + )} + + ); +} diff --git a/src/components/report/ui/EvidenceScreenshot.tsx b/src/components/report/ui/EvidenceScreenshot.tsx new file mode 100644 index 0000000..ad6f3c2 --- /dev/null +++ b/src/components/report/ui/EvidenceScreenshot.tsx @@ -0,0 +1,78 @@ +import { useState } from 'react'; +import type { ScreenshotEvidence } from '../../../types/report'; +import { EvidenceLightbox } from './EvidenceLightbox'; + +interface EvidenceScreenshotProps { + evidence: ScreenshotEvidence; + compact?: boolean; +} + +export function EvidenceScreenshot({ evidence, compact }: EvidenceScreenshotProps) { + const [loaded, setLoaded] = useState(true); + const [lightboxOpen, setLightboxOpen] = useState(false); + + if (!loaded) return null; + + return ( + <> + + + setLightboxOpen(false)} + /> + + ); +} diff --git a/src/components/report/ui/MetricCard.tsx b/src/components/report/ui/MetricCard.tsx new file mode 100644 index 0000000..ad2f36c --- /dev/null +++ b/src/components/report/ui/MetricCard.tsx @@ -0,0 +1,44 @@ +import type { ReactNode } from 'react'; +import { ArrowUp, ArrowDown, Minus } from 'lucide-react'; + +interface MetricCardProps { + key?: string | number; + label: string; + value: string | number; + subtext?: string; + icon?: ReactNode; + trend?: 'up' | 'down' | 'neutral'; +} + +const trendConfig = { + up: { icon: ArrowUp, color: 'text-emerald-500' }, + down: { icon: ArrowDown, color: 'text-red-500' }, + neutral: { icon: Minus, color: 'text-slate-400' }, +}; + +export function MetricCard({ label, value, subtext, icon, trend }: MetricCardProps) { + return ( +
+ {icon && ( +
+ {icon} +
+ )} +

{label}

+
+ {value} + {trend && ( + + {(() => { + const TrendIcon = trendConfig[trend].icon; + return ; + })()} + + )} +
+ {subtext && ( +

{subtext}

+ )} +
+ ); +} diff --git a/src/components/report/ui/ScoreRing.tsx b/src/components/report/ui/ScoreRing.tsx new file mode 100644 index 0000000..bc392e9 --- /dev/null +++ b/src/components/report/ui/ScoreRing.tsx @@ -0,0 +1,73 @@ +import { motion } from 'motion/react'; + +interface ScoreRingProps { + score: number; + maxScore?: number; + size?: number; + label?: string; + color?: string; +} + +function getScoreColor(score: number, maxScore: number): string { + const pct = (score / maxScore) * 100; + if (pct <= 40) return '#C084CF'; // soft violet — critical + if (pct <= 60) return '#8B9CF7'; // periwinkle blue — caution + if (pct <= 80) return '#7C6DD8'; // medium purple — good + return '#6C5CE7'; // Infinith primary purple — excellent +} + +export function ScoreRing({ + score, + maxScore = 100, + size = 120, + label, + color, +}: ScoreRingProps) { + const strokeWidth = 8; + const radius = (size - strokeWidth) / 2; + const circumference = 2 * Math.PI * radius; + const progress = Math.min(score / maxScore, 1); + const strokeDashoffset = circumference * (1 - progress); + const resolvedColor = color ?? getScoreColor(score, maxScore); + + return ( +
+
+ + + + +
+ {score} +
+
+ {label && ( + {label} + )} +
+ ); +} diff --git a/src/components/report/ui/SectionWrapper.tsx b/src/components/report/ui/SectionWrapper.tsx new file mode 100644 index 0000000..f8f57b4 --- /dev/null +++ b/src/components/report/ui/SectionWrapper.tsx @@ -0,0 +1,65 @@ +import type { ReactNode } from 'react'; +import { motion } from 'motion/react'; + +interface SectionWrapperProps { + id: string; + title: string; + subtitle?: string; + children: ReactNode; + dark?: boolean; + className?: string; +} + +export function SectionWrapper({ + id, + title, + subtitle, + children, + dark = false, + className = '', +}: SectionWrapperProps) { + return ( + + {dark && ( +
+ )} +
+
+

+ {title} +

+ {subtitle && ( +

+ {subtitle} +

+ )} +
+ {children} +
+ + ); +} diff --git a/src/components/report/ui/SeverityBadge.tsx b/src/components/report/ui/SeverityBadge.tsx new file mode 100644 index 0000000..bdf4e43 --- /dev/null +++ b/src/components/report/ui/SeverityBadge.tsx @@ -0,0 +1,41 @@ +type SeverityLevel = 'critical' | 'warning' | 'good' | 'excellent' | 'unknown'; + +interface SeverityBadgeProps { + severity: SeverityLevel; + label?: string; +} + +const config: Record = { + critical: { + className: 'bg-[#FFF0F0] text-[#7C3A4B] border-[#F5D5DC]', + defaultLabel: '심각', + }, + warning: { + className: 'bg-[#FFF6ED] text-[#7C5C3A] border-[#F5E0C5]', + defaultLabel: '주의', + }, + good: { + className: 'bg-[#F3F0FF] text-[#4A3A7C] border-[#D5CDF5]', + defaultLabel: '양호', + }, + excellent: { + className: 'bg-[#EFF0FF] text-[#3A3F7C] border-[#C5CBF5]', + defaultLabel: '우수', + }, + unknown: { + className: 'bg-slate-50 text-slate-700 border-slate-200', + defaultLabel: '미확인', + }, +}; + +export function SeverityBadge({ severity, label }: SeverityBadgeProps) { + const { className, defaultLabel } = config[severity]; + + return ( + + {label ?? defaultLabel} + + ); +} diff --git a/src/components/studio/BlogEditorStep.tsx b/src/components/studio/BlogEditorStep.tsx new file mode 100644 index 0000000..beb2053 --- /dev/null +++ b/src/components/studio/BlogEditorStep.tsx @@ -0,0 +1,202 @@ +import { useState, useCallback } from 'react'; +import { motion } from 'motion/react'; +import { FileTextFilled } from '../icons/FilledIcons'; +import type { StudioState } from '../../types/studio'; +import { CHANNEL_OPTIONS } from '../../types/studio'; +import { generateImage, type GenerateResult } from '../../services/geminiImageGen'; + +interface Props { + studioState: StudioState; +} + +type GenStatus = 'idle' | 'generating' | 'done' | 'error'; + +const PLACEHOLDER_BLOG = `# 한번에 성공하는 코성형, VIEW의 비결 + +코성형은 얼굴의 중심을 결정하는 중요한 수술입니다. VIEW 성형외과는 21년간 축적된 노하우를 바탕으로, 환자 개개인의 얼굴 비율과 피부 특성을 정밀 분석하여 자연스러운 결과를 만들어냅니다. + +## 왜 VIEW인가? + +- **21년 무사고** 안전 시스템 +- **분야별 전문의** 협진 시스템 +- **수술실 CCTV** 투명한 수술 환경 +- **전담 마취과 전문의** 상주 + +## 코성형 종류별 특징 + +### 1. 매부리코 교정 +매부리코는 코뼈와 연골의 돌출로 인해 발생합니다. VIEW에서는 절골술과 연골 성형을 병행하여 자연스러운 일자 라인을 만들어냅니다. + +### 2. 콧대 높이기 +실리콘이나 고어텍스를 이용한 융비술로 이상적인 콧대 높이를 만듭니다. 환자의 피부 두께와 코 구조에 맞는 보형물을 선택합니다. + +### 3. 코끝 성형 +코끝의 모양과 각도를 조절하여 세련된 인상을 만드는 수술입니다. 자가연골(귀연골, 비중격연골)을 활용하여 자연스러운 결과를 추구합니다. + +## 상담 예약 + +지금 바로 무료 상담을 예약하세요. VIEW 성형외과 전문의가 1:1 맞춤 상담을 제공합니다. + +☎ 02-XXX-XXXX | 서울 강남구 논현동`; + +export default function BlogEditorStep({ studioState }: Props) { + const [imageStatus, setImageStatus] = useState('idle'); + const [imageResult, setImageResult] = useState(null); + const [imageError, setImageError] = useState(''); + const [blogText, setBlogText] = useState(PLACEHOLDER_BLOG); + const [activeTab, setActiveTab] = useState<'image' | 'text'>('image'); + + const activeChannel = CHANNEL_OPTIONS.find(c => c.channel === studioState.channel); + const activeFormat = activeChannel?.formats.find(f => f.key === studioState.format); + + const handleGenerateImage = useCallback(async () => { + setImageStatus('generating'); + setImageError(''); + try { + const res = await generateImage(studioState); + setImageResult(res); + setImageStatus('done'); + } catch (err) { + setImageError(err instanceof Error ? err.message : '이미지 생성 실패'); + setImageStatus('error'); + } + }, [studioState]); + + return ( +
+ {/* Tabs */} +
+ {([ + { key: 'image' as const, label: '대표 이미지' }, + { key: 'text' as const, label: '블로그 텍스트' }, + ]).map(tab => ( + + ))} +
+ + {activeTab === 'image' && ( +
+ {/* Image generation */} +
+

대표 이미지 생성

+

블로그 상단에 들어갈 대표 이미지를 AI로 생성합니다

+ +
+ + + +
+ + {(imageStatus === 'idle' || imageStatus === 'error') && ( + + )} + {imageStatus === 'error' && ( +

{imageError}

+ )} + {imageStatus === 'done' && ( + + )} +
+ + {/* Image preview */} +
+

프리뷰

+
+ {imageStatus === 'idle' && ( +
+
+ +
+

생성 버튼을 눌러주세요

+
+ )} + {imageStatus === 'generating' && ( +
+
+

이미지 생성 중...

+
+ )} + {imageStatus === 'error' && ( +

{imageError}

+ )} + {imageStatus === 'done' && imageResult?.imageDataUrl && ( + + )} +
+
+
+ )} + + {activeTab === 'text' && ( +
+
+
+

블로그 편집기

+

AI가 생성한 초안을 수정하세요 (Markdown 지원)

+
+
+ + {blogText.length.toLocaleString()}자 + + + SEO 최적화 + +
+
+ +