commit 4deefff06167b5aeeba887b4f9083fb22b8374d6 Author: o2odev Date: Fri May 29 12:03:48 2026 +0900 Initial commit: ADO2 Hookit — 8초 후킹 숏폼 생성 파이프라인 3-엔진 오케스트레이션(Claude → Higgsfield → Remotion) PoC + 셀프 웹앱. 백엔드 개발자 핸드오프 문서(HANDOFF.md) 포함. Co-Authored-By: Claude Opus 4.7 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8550002 --- /dev/null +++ b/.env.example @@ -0,0 +1,22 @@ +# Higgsfield Shorts Engine — Environment Variables +# 이 파일을 .env 로 복사한 뒤 실제 값으로 채우세요. (.env 는 .gitignore 됨) + +# --- Higgsfield API --- +HIGGSFIELD_API_KEY= +HIGGSFIELD_BASE_URL=https://api.higgsfield.ai/v1 + +# --- ADO2 Tenant Context (선택) --- +# 멀티테넌트 운영 시 기본 tenant 지정 +ADO2_DEFAULT_TENANT_ID= +ADO2_DEFAULT_SHOOT_ID= + +# --- Cost & Quota Guardrails --- +HF_MAX_COST_PER_VIDEO_USD=2.0 +HF_MONTHLY_BUDGET_USD=80 + +# --- Storage (Phase 2 — S3 결과 업로드) --- +S3_BUCKET_OUTPUTS= +S3_REGION=ap-northeast-2 + +# --- Observability --- +LOG_LEVEL=INFO diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ebd5a02 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# Node +node_modules/ +npm-debug.log* +yarn-debug.log* +pnpm-debug.log* +.npm/ + +# Environment +.env +.env.local +.env.*.local + +# Python (Phase 2 — when src/ Python client is added) +.venv/ +__pycache__/ +*.egg-info/ +*.pyc +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ + +# Media (대용량 — S3에만 보관) +inputs/*/* +outputs/*/* +!inputs/.gitkeep +!outputs/.gitkeep +!inputs/.gitignore +!outputs/.gitignore + +# Remotion render outputs (재생성 가능) +remotion/out/ + +# Server runtime (업로드/렌더 결과) +server/.uploads/ +server/outputs/ + +# Vercel (배포 메타 — 로컬 링크 정보) +.vercel/ + +# OS +.DS_Store + +# IDE +.vscode/ +.idea/ +*.swp diff --git a/HANDOFF.md b/HANDOFF.md new file mode 100644 index 0000000..8bbcd0d --- /dev/null +++ b/HANDOFF.md @@ -0,0 +1,222 @@ +# ADO2 Hookit — 백엔드 개발자 핸드오프 + +> **무엇인가**: 사진 4장 + 5개 인터뷰 답변 → 8초 세로형(9:16) 후킹 숏폼을 자동 생성하는 파이프라인. +> "Intentional Unreal" 전략 — 실제 사진 기반에 AI 카메라 무브를 입혀 스크롤을 멈추게 하고, 디스클로저로 신뢰를 지킨다. +> ADO2 플랫폼의 **어텐션 트랙** 엔진 (`engine/higgsfield_shorts`). + +이 문서는 PoC를 **운영 가능한 서비스**로 넘기기 위한 핸드오프입니다. 현 상태, 실행법, 데이터 계약, 그리고 운영화에 반드시 필요한 작업(❗)을 정리했습니다. + +--- + +## 1. 아키텍처 — 3 엔진 오케스트레이션 + +``` +인터뷰 답변(5개) + 사진(4장+) + │ + ▼ + ① spec_builder.py Claude(claude-opus-4-7) → VideoSpec(JSON) / ScriptResult(JSON) + │ · 구조화 출력(json_schema) + 프롬프트 캐싱 + 가드레일 + ▼ + ② higgsfield_client.py Higgsfield CLI shell-out → 완성형 8초 base.mp4 (오디오 포함) + │ · model=marketing_studio_video, mode=tv_spot, 9:16, 40cr + ▼ + ③ remotion_render.py VideoSpec + base.mp4 → props.json → `npx remotion render` → final.mp4 + │ · 자막/타이틀/셀링배지/엔드카드 오버레이 합성 + ▼ + /outputs/.mp4 + caption(업로드용 해시태그) +``` + +핵심 설계 원칙: +- **각 단계는 순수 함수**(입력 → path/객체 반환). 비동기 잡 큐로 바꾸기 쉬움. +- **데이터 주도 Remotion**: 컷/자막/타이밍을 props JSON으로 주입 → 콘텐츠가 바뀌어도 컴포넌트 불변. +- **dry_run 모드**: 크레딧 0으로 데모 클립 반환 (Higgsfield 호출 스킵). 개발·테스트용. + +--- + +## 2. 디렉터리 구조 + +``` +engine/higgsfield_shorts/ +├── server/ # FastAPI 백엔드 (운영 대상 ★) +│ ├── app/ +│ │ ├── main.py # 라우트: /api/health, /api/captions, /api/generate +│ │ ├── schemas.py # Pydantic 데이터 계약 (LLM·Remotion 공유) +│ │ └── pipeline/ +│ │ ├── spec_builder.py # ① Claude → VideoSpec / ScriptResult +│ │ ├── higgsfield_client.py # ② Higgsfield CLI 래퍼 +│ │ └── remotion_render.py # ③ Remotion 렌더 래퍼 +│ ├── pyproject.toml # Python deps (fastapi, anthropic, pydantic…) +│ └── .env.example # ANTHROPIC_API_KEY +├── remotion/ # Remotion 영상 합성 프로젝트 (Node) +│ ├── src/ +│ │ ├── Root.tsx # Composition 등록 (MumumShort) +│ │ ├── MumumShort.tsx # 메인 컴포지션 (props 기반) +│ │ ├── data/types.ts # ShortProps 타입 (= schemas.py VideoSpec와 1:1) +│ │ ├── data/mumum.ts # defaultProps (Studio 미리보기용 샘플) +│ │ └── components/ # HookTitle/Subtitle/SellingBadge/BrandLines/EndCard +│ ├── public/ # base 영상이 여기로 복사됨 (staticFile 해석) +│ └── package.json # remotion 4.0.468, react 19 +├── webapp/ # 프론트엔드 (단일 HTML, Vercel 정적 배포) +│ ├── index.html # React(CDN) 단일 파일 — 4스텝 UI +│ ├── vercel.json +│ └── demo/mumum.mp4 # dry_run·데모용 완성 영상 +├── _brief/03_energy-matching.md # 에너지 자동 매칭 결정표 (3 프로파일) +├── prompts/camera-vocabulary.md # 카메라 어휘 + 키프레임 기법 +├── concepts/ # 5개 영상 컨셉 정의 +└── docs/pipeline-integration.md # ADO2 본체 통합 노트 +``` + +--- + +## 3. 사전 요구사항 (개발 환경) + +| 항목 | 버전/조건 | 비고 | +|---|---|---| +| Python | ≥ 3.12 | `uv` 권장 | +| Node.js | ≥ 22 | Remotion 4.x 요구 | +| ffmpeg / ffprobe | 설치 필요 | `remotion_render._probe_frames`가 ffprobe 사용 | +| Higgsfield CLI | `@higgsfield/cli@^0.1.40` | `higgsfield auth login` 으로 **디바이스 로그인 인증 선행** | +| ANTHROPIC_API_KEY | 필수 | `server/.env` 에 설정 (Claude 호출) | + +> ⚠️ Higgsfield는 **API 키가 아니라 CLI 디바이스 로그인**으로 인증합니다(`~/.higgsfield/`). +> 서버는 `subprocess`로 CLI를 호출하므로, **서버 실행 계정이 로그인되어 있어야** 실제 생성이 동작합니다. +> 컨테이너/서버리스 환경에서는 이 인증 상태 주입이 별도 과제입니다(§7 참고). + +--- + +## 4. 로컬 실행 + +```bash +# 0) 인증 (1회) +higgsfield auth login + +# 1) Remotion 의존성 (1회) +cd remotion && npm install && cd .. + +# 2) 서버 의존성 + 환경변수 +cd server +cp .env.example .env # ANTHROPIC_API_KEY 채우기 +uv sync # 또는: pip install -e . + +# 3) 서버 기동 +uv run uvicorn app.main:app --reload --port 8000 +# → http://localhost:8000/ (웹앱) +# → http://localhost:8000/api/health + +# (선택) Remotion 미리보기 스튜디오 +cd ../remotion && npm run studio +``` + +프론트엔드만 따로 확인하려면 `webapp/`을 정적 서빙 (`python3 -m http.server`). `index.html`의 `API_BASE`를 백엔드 주소로 설정하면 실 호출, 비우면 데모 모드. + +--- + +## 5. API 계약 + +### `GET /api/health` +```json +{ "ok": true } +``` + +### `POST /api/captions` — 자막 스크립트 4블록 생성 (JSON) +요청 (`GenerateRequest`): +```json +{ + "kind": "place", // "place" | "product" | "message" + "biz_name": "Stay, 머뭄", + "addr": "전북 군산 …", // optional + "price": "1박 18만원", // optional + "selling": "프라이빗 독채, 넓은 정원, 깊은 욕조" +} +``` +응답 (`ScriptResult`): +```json +{ "intro": "...", "selling": "...", "story": "...", "cta": "..." } +``` + +### `POST /api/generate` — 최종 영상 생성 (multipart/form-data) +필드: `photos[]`(파일, **4장 이상**), `kind`, `biz_name`, `selling`, `addr?`, `price?`, `dry_run?(bool)` +응답 (`GenerateResult`): +```json +{ + "video_url": "/outputs/.mp4", + "caption": "업로드용 캡션 + 해시태그", + "profile": "Still Cinema", + "cost_credits": 40.0, + "job_id": "ab12cd34" +} +``` + +데이터 계약 정의: `server/app/schemas.py` (VideoSpec / ScriptResult / GenerateRequest / GenerateResult). +Remotion 측 동일 계약: `remotion/src/data/types.ts` — **두 파일은 1:1로 동기화 유지 필수**. + +--- + +## 6. 가드레일 (반드시 유지 — 비즈니스 규칙) + +`spec_builder.py`의 시스템 프롬프트에 인코딩됨. 프롬프트 수정 시 절대 훼손 금지: + +1. **식민지 유산·역사정치 용어 금지** — "적산가옥/일제/식민지/근대사" 등 → 미학·경험 어휘로 대체. + ("적산가옥"은 어원상 부정적 함의 → 숙박 카피에서 영구 차단) +2. **거짓·과장 금지** — 사용자가 주지 않은 거리/효능/수치를 지어내지 않음. +3. **자막은 흰색 모노크롬** — 카피에 색 지시어 금지. 강조는 굵기·크기·그림자로. +4. **AI 디스클로저 고정** — 엔드카드에 "실제 사진 기반, AI 카메라 효과를 적용한 영상입니다." + +--- + +## 7. 운영화(Production) 작업 — ❗ 백엔드 핵심 과제 + +현재는 **동작하는 PoC**입니다. 실 트래픽 전 다음을 처리해야 합니다: + +1. **❗ 동기 → 비동기 잡 전환** + `/api/generate`가 Higgsfield 대기(최대 15분) + Remotion 렌더(수십 초)를 **한 요청 안에서 블로킹**함. + → `POST /api/generate`는 `job_id` 즉시 반환, `GET /api/jobs/{id}` 폴링 or 웹소켓. + → Celery/RQ/ARQ + Redis(이미 ADO2 스택에 있음) 권장. + +2. **❗ Higgsfield 인증의 서버 이식** + CLI 디바이스 로그인(`~/.higgsfield/`)이 로컬 사용자에 묶여 있음. 컨테이너에서 동작하려면 + 인증 토큰/세션을 시크릿으로 주입하거나, Higgsfield **REST API 직접 호출**로 전환 검토 + (현재는 CLI를 `subprocess`로 감싼 것 — `.env.example`의 `HIGGSFIELD_API_KEY`는 미사용 placeholder). + +3. **❗ 스토리지** — `server/outputs/`, `remotion/public/`(잡별 base 복사)는 로컬 디스크. + → S3(`S3_BUCKET_OUTPUTS`, `ap-northeast-2`) 업로드 + presigned URL 반환. 잡 종료 후 로컬 정리. + +4. **멀티테넌트 / RLS** — ADO2 본체 규칙(모든 쿼리 `tenant_id` 기반). 잡·결과물에 tenant context 주입. + +5. **비용 가드레일** — 생성 전 `higgsfield_client.cost()`로 크레딧 선확인, 월 예산(`HF_MONTHLY_BUDGET_USD`) 차단 로직. + +6. **에러 처리** — 현재 각 단계 실패를 502로 surface. 재시도/부분 실패(예: Higgsfield 성공·Remotion 실패 시 base 보존) 정책 필요. + +7. **동시성** — Remotion 렌더는 CPU/메모리 집약적. 렌더 워커 풀 분리 권장. + +8. **CORS** — 현재 `allow_origins=["*"]`. 운영 도메인으로 제한. + +9. **에너지 자동 매칭 자동화** — 현 PoC는 프로파일을 LLM이 텍스트로 선택. `_brief/03_energy-matching.md`의 + 결정표를 코드(`energy_matcher`)로 옮겨 결정론적 프로파일링 → Remotion 트랜지션/리듬 기본값 주입(Phase 2). + +--- + +## 8. 비용 모델 (참고) + +- 1 크레딧 ≈ $0.051(세포함) ≈ ₩76 (환율 1500 기준) +- 영상 1편 = **40cr = 약 ₩3,060** (marketing_studio_video tv_spot) +- 풀로디드(LLM 호출 포함) ≈ **₩3,160 / 편** + +--- + +## 9. 배포 현황 + +- **프론트엔드(webapp)**: Vercel 정적 배포 — `https://ado2short.o2osolution.ai` (커스텀 도메인 DNS 연결 진행 중). + Vercel은 정적 호스팅이므로 **백엔드 미포함** → 데모 모드만 동작. 실 생성은 별도 백엔드 호스트 필요. +- **백엔드(server)**: 아직 배포처 없음. → §7 운영화 후 컨테이너 배포 대상. + +--- + +## 10. 다음 액션 (백엔드 개발자 시작점) + +1. 로컬 실행(§4) → `dry_run=true`로 `/api/generate` 한 번 통과시켜 파이프라인 감 잡기. +2. §7-1(비동기 잡) + §7-2(Higgsfield 인증 이식)이 **블로커**. 이 둘부터 설계. +3. `schemas.py` ↔ `remotion/src/data/types.ts` 동기화 규칙 확인 (계약 변경 시 양쪽). +4. 가드레일(§6)은 제품 신뢰의 핵심 — 프롬프트 리팩터 시 회귀 테스트 추가 권장. + +문의: 크리에이티브/프롬프트 로직은 `_brief/`·`prompts/`·`concepts/` 참조. diff --git a/README.md b/README.md new file mode 100644 index 0000000..02caaa6 --- /dev/null +++ b/README.md @@ -0,0 +1,189 @@ +# Higgsfield Shorts Engine + +> ADO2 마케팅 자동화 파이프라인의 **어텐션 트랙(Attention Track)** 영상 생성 엔진. +> 펜션 사진 3~5장 → 8초 임팩트 숏츠/릴스 (Higgsfield API 기반). + +--- + +## 1. Project Goal + +**한 줄 목표** +> 펜션 마케팅의 신뢰 기반 콘텐츠를 깨지 않고, **의도적 비현실(Intentional Unreal)** 영상으로 SNS 피드의 Pattern Interrupt를 만들어 평균 시청유지율을 2배 이상 끌어올린다. + +**왜 지금인가 (Why Now)** +- 기존 Creatomate 슬라이드쇼는 "실사 보존" 원칙 덕분에 신뢰는 높지만, Instagram/TikTok 피드에서 평균 1.5초 안에 스킵됨 +- 펜션 SNS 피드의 90%는 실사 사진/영상 → 차별화 여지 없음 +- Higgsfield의 카메라 무브·드림시퀀스·시네마틱 모션은 "AI가 만든 티"가 나도 오히려 **궁금증 → 스와이프 정지 → 프로필 진입** 의 깔때기를 형성 + +**전략적 포지셔닝** +| 트랙 | 엔진 | 역할 | 사용자 인식 | +|---|---|---|---| +| Trust Track | `engine/semantic_video/` + Creatomate | 실사 슬라이드쇼·정보 전달 | "이 펜션은 진짜 이렇게 생겼다" | +| **Attention Track** | **`engine/higgsfield_shorts/` (본 모듈)** | **8초 시네마틱·드림시퀀스** | **"뭐지? 가보고 싶다"** | + +두 트랙은 **상호 보완** 관계. 어텐션 트랙으로 유입 → 트러스트 트랙으로 전환(예약). + +--- + +## 2. Success Criteria (KPI) + +### Primary (어텐션 성과) +- **평균 시청 유지율** ≥ 65% (8초 기준, 5.2초 이상 시청) +- **Hook Rate** (3초 시청률) ≥ 35% (업계 평균 15~20%) +- **저장률** ≥ 2% (Instagram Reels 기준) + +### Secondary (전환 깔때기) +- 영상 → 프로필 클릭률 ≥ 4% +- 프로필 → 예약 페이지 이동 ≥ 1% + +### Production (운영 효율) +- 사진 5장 입력 → 8초 영상 출력까지 **자동 파이프라인 ≤ 5분** +- 1편당 Higgsfield API 비용 ≤ $2 (목표 단가) +- 클라이언트 1곳당 월 12편 생산 가능 (주 3편 ×4주) + +### Phase 1 KPI (6주, 2026-05-28 ~ 2026-07-09) +- **3개 펜션 클라이언트로 파일럿 운영** +- **편당 평균 도달 ≥ 10,000회 / 평균 시청유지율 ≥ 65% 검증** +- **컨셉 라이브러리 5종 검증 → 2종 표준화** + +--- + +## 3. Scope + +### In Scope (Phase 1) +- 펜션 정사진 3~5장 → 8초 9:16 (Reels/Shorts) 영상 생성 +- Higgsfield API 연동 (Image-to-Video, Camera Control) +- 컨셉 라이브러리 (시네마틱·드림시퀀스·하이퍼리얼·트레일러·글리치 등) +- 한국어 자막/타이포그래피 모션 (Remotion 후처리) +- Creatomate Trust Track 자산과의 메타데이터 연결 (같은 펜션 = 같은 자산 ID) + +### Out of Scope (Phase 1) +- 16:9 가로 영상 (Phase 2: 유튜브 시청자대상) +- 30초/60초 장형 (Phase 2: 광고 캠페인) +- 사람(인물) 영상 생성 — 초상권/딥페이크 리스크 +- 음악 자동 매칭 — Phase 1은 큐레이션된 라이브러리에서 수동 선택 + +### Hard Constraints (절대 위반 금지) +- ❌ **펜션의 위치·구조·시설을 왜곡하지 않는다** (예: 산속 펜션을 바닷가로 합성 X) +- ❌ **존재하지 않는 어메니티를 합성하지 않는다** (수영장 없는데 수영장 X) +- ✅ **분위기·시간대·날씨·카메라 무브는 자유롭게 과장 가능** (이게 어텐션 핵심) +- ✅ **추상화·드림시퀀스·픽션화는 OK** — 단, 영상 후반/캡션에 펜션명 명시 + +> Why: 펜션은 신뢰 기반 마케팅이 필수. "분위기 과장"은 광고 관행 안에 있지만, "사실 왜곡"은 부정경쟁방지법·표시광고법 위반. + +--- + +## 4. Architecture (How it plugs in) + +``` +[Tenant Photo Library] + │ + ▼ +┌────────────────────────────────────┐ +│ ADO2 Agent Orchestrator │ +│ ├─ Content Agent: 컨셉 선택 │ +│ └─ Media Agent: 사진 큐레이션 │ +└──────────────┬─────────────────────┘ + │ + ┌──────┴──────┐ + ▼ ▼ +┌──────────────┐ ┌──────────────────────┐ +│ Trust Track │ │ Attention Track │ +│ semantic_ │ │ higgsfield_shorts │ +│ video + │ │ (this engine) │ +│ Creatomate │ │ │ +└──────┬───────┘ └──────────┬───────────┘ + │ │ + │ ┌─────────────────┘ + ▼ ▼ +┌────────────────────┐ +│ Distribution Agent │ → Instagram / TikTok / Naver +└────────────────────┘ +``` + +**모듈 내부 흐름** +``` +inputs/{tenant}/{shoot_id}/*.jpg + │ + ▼ +src/curation/ # 3~5장 선별 (구도·색감·서사 점수) + │ + ▼ +src/concepts/ # 컨셉 라이브러리에서 매칭 + │ + ▼ +src/prompts/ # Higgsfield 프롬프트 빌더 + │ + ▼ +src/rendering/ # Higgsfield API 호출 + 비동기 큐 + │ + ▼ +src/post/ # Remotion 자막·로고·CTA 합성 + │ + ▼ +outputs/{tenant}/{shoot_id}/{concept}.mp4 +``` + +--- + +## 5. Folder Structure + +``` +engine/higgsfield_shorts/ +├── README.md # 본 문서 (Project Charter) +├── _brief/ +│ ├── 01_creative-direction.md # 크리에이티브 디렉션 (가짜OK 전략) +│ ├── 02_goals-kpis.md # 상세 KPI 및 측정 방법 +│ └── 03_scope-constraints.md # 법적·브랜드 제약 +├── concepts/ # 컨셉 라이브러리 +│ ├── README.md +│ ├── 01_cinematic-trailer.md +│ ├── 02_dream-sequence.md +│ ├── 03_hyperreal-luxury.md +│ ├── 04_time-warp.md +│ └── 05_anime-painterly.md +├── prompts/ # Higgsfield 프롬프트 템플릿 +│ └── README.md +├── inputs/ # 테스트용 펜션 사진 셋 +│ └── .gitkeep +├── outputs/ # 렌더링 결과 +│ └── .gitkeep +├── configs/ # Higgsfield API·렌더 파라미터 +│ └── higgsfield.yaml +├── docs/ +│ └── pipeline-integration.md # ADO2 파이프라인 연동 스펙 +└── tests/ + └── README.md +``` + +--- + +## 6. Phase 1 Roadmap (6 weeks) + +| Week | 마일스톤 | +|---|---| +| W1 (5/28-6/3) | 컨셉 라이브러리 5종 작성 + Higgsfield API 계정 셋업 + 첫 사진셋 수집 | +| W2 (6/4-6/10) | 프롬프트 템플릿 v0.1 + 수동 렌더 3편 (컨셉별 PoC) | +| W3 (6/11-6/17) | 파일럿 펜션 3곳 선정 + 사진 큐레이션 SOP | +| W4 (6/18-6/24) | 9편 발행 (펜션 3 × 컨셉 3) + 측정 | +| W5 (6/25-7/1) | 결과 분석 + 컨셉 2종 표준화 | +| W6 (7/2-7/9) | 자동화 파이프라인 v0.1 (Content Agent 통합) | + +--- + +## 7. Stakeholders + +- **Creative Director / Head of Marketing Ops**: 컨셉 디렉션, KPI 책임 (= 사용자) +- **Engineering**: 엔진 구현, API 연동 +- **Pilot Clients**: 펜션 3곳 (5월 KPI Success Case와 연계) + +--- + +## 8. Open Questions + +이 프로젝트가 출발하려면 다음 결정이 필요합니다 (`_brief/01_creative-direction.md` 참고): + +1. **Phase 1에서 우선 검증할 컨셉 2~3종은?** (라이브러리 5종 중) +2. **사진 큐레이션 기준** — AI 자동 큐레이션 vs 사람 큐레이션 vs 하이브리드? +3. **펜션명/CTA 노출 시점** — 영상 마지막 1초 vs 캡션에만 vs 둘 다? +4. **음악 라이브러리 선택** — Epidemic Sound vs Artlist vs SUNO 자동 생성? diff --git a/_brief/01_creative-direction.md b/_brief/01_creative-direction.md new file mode 100644 index 0000000..8a56f93 --- /dev/null +++ b/_brief/01_creative-direction.md @@ -0,0 +1,98 @@ +--- +title: Creative Direction — Intentional Unreal +owner: Creative Director / Head of Marketing Ops +status: v0.1 draft (2026-05-28) +--- + +# Creative Direction: Intentional Unreal + +## 1. The Core Insight + +펜션 SNS 피드의 평균 콘텐츠는 다음과 같다: +- 실사 사진 슬라이드쇼 +- 인테리어 클로즈업 +- 노을·바베큐·반려동물 같은 클리셰 +- 캡션은 "○○펜션입니다 / 예약문의 DM" + +**→ 결과: 사용자 엄지손가락이 0.8초 안에 다음 영상으로 이동.** + +**우리의 전략: 의도적 비현실 (Intentional Unreal)** + +"진짜처럼 보이려 노력하는 AI 영상"이 아니라, "AI가 만들었다는 게 명백히 드러나지만 펜션의 본질은 살아 있는" 영상. + +> 비유: 영화 *Wes Anderson*의 펜션 광고. 누가 봐도 픽션인데, 그 픽션이 공간의 매력을 더 강화한다. + +## 2. Three Creative Principles + +### Principle 1 — "분위기는 과장, 사실은 보존" +- ✅ 노을을 보라색으로 과장, 안개를 깔고, 비현실적 카메라 무브 OK +- ❌ 펜션 위치·구조·시설 왜곡 NO (산속 펜션을 바닷가로 X) +- ❌ 없는 어메니티 합성 NO (수영장 합성 X) + +### Principle 2 — "8초 안에 3개의 시각적 펀치" +8초는 짧다. 다음 3비트가 반드시 있어야 한다: +1. **Hook (0-2초)** — 가장 비현실적/놀라운 프레임. 엄지를 멈추게 한다. +2. **Reveal (3-6초)** — 펜션의 진짜 매력 (공간감·디테일). 호기심을 만족시킨다. +3. **Brand (7-8초)** — 펜션명 + 위치 (예: "OO펜션 · 강원 양양"). 기억에 박는다. + +### Principle 3 — "AI 티가 나도 좋다, 단 부끄럽지 않게" +- 캡션에 "*AI로 재해석한 ○○펜션의 시그니처 무드*" 같은 메타 카피를 적극 노출 +- 사용자가 "어, 이거 AI지?"라고 알아채는 순간이 **참여(댓글·저장)** 로 전환되는 트리거 +- 단, 저급한 AI 아티팩트(왜곡된 손가락·뭉개진 얼굴)는 자동 컷 + +## 3. Why This Works for Pension Marketing + +| 통념 | 우리의 가설 | +|---|---| +| "펜션은 신뢰가 생명이라 실사여야" | 신뢰는 **트러스트 트랙**(Creatomate 실사)이 담당. 어텐션은 별도 트랙. | +| "AI 티 나면 안 좋아할 것" | AI 티 자체가 차별화. 평균 피드 90%가 실사라 오히려 **시각적 희소성** 발생. | +| "정확한 정보 전달이 우선" | 8초 숏츠는 정보가 아니라 **감정**을 전달. 정보는 프로필·예약페이지가 담당. | + +## 4. Trust Track ↔ Attention Track 결합 방식 + +같은 펜션 = 같은 메타데이터 ID로 묶어, 다음 사용자 여정을 만든다: + +``` +[Reels에서 Higgsfield 어텐션 영상 시청] + ↓ (호기심) +[프로필 진입] + ↓ +[프로필에 Creatomate 트러스트 영상 핀 고정] + ↓ (확인·안심) +[Bio 링크 → 예약 페이지] +``` + +→ 신뢰 손상 없이 어텐션만 흡수. + +## 5. Phase 1 Concept Selection (Decision) + +라이브러리 5종 중 **W1-W2에 우선 검증할 3종**을 다음과 같이 선정 (가설 기반): + +| 컨셉 | 심리 트리거 | 펜션 핏 | 우선순위 | +|---|---|---|---| +| **Cinematic Trailer** | 호기심 (영화같다) | 全 펜션 범용 | ★★★ | +| **Dream Sequence** | 향수·로망 | 자연·뷰형 펜션 | ★★★ | +| **Hyperreal Luxury** | 욕망 (와 멋지다) | 풀빌라·프리미엄 | ★★ | +| Time Warp | 놀라움 | 한옥·레트로 컨셉 | ★ | +| Anime Painterly | 웃음·공유욕 | 특이/테마 펜션 | ★ | + +W4 결과에 따라 W5에 2종 표준화. + +## 6. Guardrails (해서는 안 되는 것) + +- 인물(고객) 생성 — 초상권/딥페이크 +- 경쟁사 비교 — 의료광고법 유사 리스크 (숙박은 표시광고법) +- 가격 표기 — 영상은 분위기, 가격은 캡션/링크 +- 별점·후기 합성 — 거짓 표시 +- 실재 인물(연예인) 닮은꼴 — 퍼블리시티권 + +## 7. Open Decisions + +다음은 운영 시작 전 결정 필요: + +1. **사진 큐레이션** — 자동(CLIP 기반 점수) vs 수동 vs 하이브리드 + - 가설: W1-W2는 수동, W3부터 하이브리드, W6에 자동화 도입 +2. **자막/타이포그래피 톤** — 미니멀 vs 시네마틱 vs 한글 캘리그래피 + - 가설: 시네마틱(고딕 + 영문 보조) 기본, 펜션 결에 따라 조정 +3. **음악 매칭** — 컨셉별 BGM 라이브러리 사전 큐레이션 + - 가설: 컨셉별 5트랙씩 25트랙 큐레이션 (Epidemic Sound) diff --git a/_brief/03_energy-matching.md b/_brief/03_energy-matching.md new file mode 100644 index 0000000..e917057 --- /dev/null +++ b/_brief/03_energy-matching.md @@ -0,0 +1,105 @@ +--- +title: Energy Auto-Matching Logic +owner: Creative Director / Head of Marketing Ops +status: v0.1 (2026-05-28) +phase: PoC #2 — 문서 결정표 (Phase 2에서 src/energy_matcher 로 코드화) +--- + +# Energy Auto-Matching Logic (제품 IP) + +브랜드 인텔리전스 → `energy_score` (0~100) → 3개 Energy Profile 중 1개 자동 선택. +각 프로파일은 **고유한 카메라 어휘·컷 리듬·트랜지션·음악**을 강제하여 영상 문법을 결정한다. + +> 핵심: 영상 톤을 사람이 매번 고르지 않는다. 인텔리전스가 산출한다. 펜션이 바뀌면 점수가 바뀌고, 점수가 프로파일을, 프로파일이 영상 문법을 결정한다. + +--- + +## 1. energy_score 산출 + +4개 신호의 가중 합 (각 -25 ~ +25, 합산 후 0~100 정규화). + +``` +raw = amenity_signal + persona_signal + selling_point_signal + photo_signal +energy_score = clamp( 50 + raw, 0, 100 ) +``` + +### 1.1 amenity_signal (-25 ~ +25) +| 어메니티 신호 | 점수 | +|---|---| +| 수영장(특히 대형)·워터파크 | +25 | +| 파티룸·스파·자쿠지·루프탑 | +15 | +| 액티비티(글램핑·카라반·뷰포인트) | +10 | +| 일반 데크·바베큐 | 0 | +| 미니멀·독채·프라이빗 | -15 | +| 정숙·디지털디톡스·웰니스 | -25 | + +### 1.2 persona_signal (-25 ~ +25) +| 1차 페르소나 | 점수 | +|---|---| +| 20대 그룹·파티·액티비티 | +25 | +| 감성사진 크리에이터(20-34) | +12 | +| 커플여행(20-30대) | 0 | +| 힐링커플(30대) | -12 | +| 번아웃 회복 1인 | -25 | + +### 1.3 selling_point_signal (-25 ~ +25) +인텔리전스 `selling_points` Top3의 카테고리로 판정. +| 셀링포인트 우세 | 점수 | +|---|---| +| 스펙터클·뷰·포토스팟·액티비티 | +20 | +| 입지·접근성·숏브레이크 | +5 | +| 브랜드컨셉·야간감성 | -5 | +| 힐링·프라이버시·정숙 | -20 | + +### 1.4 photo_signal (-25 ~ +25) +입력 사진셋의 시각 특성 (사람 판단 또는 CLIP 분류). +| 사진 특성 | 점수 | +|---|---| +| 주간·다채로운 색·물/풍경 와이드 | +20 | +| 액티비티·사람·움직임 | +12 | +| 중립 | 0 | +| 야간·저조도·정적 | -12 | +| 미니멀·실내·단색 톤 | -20 | + +--- + +## 2. 3 Energy Profiles + +| 항목 | **A. Still Cinema** | **B. Rhythm Reveal** | **C. Maximum Viral** | +|---|---|---|---| +| energy_score | **0–35** | **36–70** | **71–100** | +| 정체성 | 정적·다이내믹 하이브리드 | 레퍼런스 매칭 | 공격적 바이럴 | +| 카메라 어휘 | 느린 push_in·dolly 위주 **+ 액센트 1~2개**(push_crash, speed_ramp) | orbit·crane·dolly_through·fpv + push 혼합 | push_crash·dolly_zoom·fpv_drone·공격적 orbit | +| 컷 길이 | 1.5~2.5s | 0.8~1.5s | 0.4~0.9s | +| 컷 수(8s) | 4~5 | 6~9 | 8~14 | +| 트랜지션 | 매치컷·소프트 디졸브·젠틀 휩 | 휩팬·스피드램프컷·라이트플래시·매치컷 | 줌펀치·글리치·플래시·J컷 | +| 비트싱크 | 느슨(액센트만) | 중간(주요 컷) | 하드(전 컷) | +| 음악 | 앰비언트 슬로빌드 | 트렌드 업비트 | 바이럴 오디오 | +| 자막 모션 | 페이드+미세 상승 | 슬라이드+팝 | 글리치+스냅 | +| 대표 케이스 | 머뭄, 한옥스테이, 료칸형, 웰니스 | 70m풀 글램핑, 뷰맛집, 풀빌라 | 워터파크·파티펜션·대형글램핑 | + +**공통 원칙**: Profile A라도 액센트 무브를 최소 1개 의무화 → 절대 단조롭지 않게. (PoC #1 실패 교훈) + +--- + +## 3. 적용 절차 (PoC = 수작업 결정표) + +1. 인텔리전스 JSON에서 amenities/personas/selling_points 추출 +2. photo_signal은 사진셋 육안 판정 +3. 4신호 점수 → energy_score 계산 +4. 점수 구간 → Profile 확정 +5. Profile의 카메라어휘·컷리듬·트랜지션을 샷리스트·Remotion data에 주입 + +### Phase 2 자동화 (후속) +`engine/higgsfield_shorts/src/energy_matcher` 모듈: +- input: 인텔리전스 JSON + 사진 CLIP 분류 결과 +- output: `{ energy_score, profile, camera_vocabulary[], cut_rhythm, transitions[], music_brief }` +- Content Agent가 호출 → 샷리스트 자동 생성 → Higgsfield 생성 → Remotion data 주입 + +--- + +## 4. 가드레일 + +- 모든 프로파일 공통: 펜션 구조·위치·어메니티 사실 왜곡 금지 (분위기·카메라무브만 과장) +- 숙박 카피 민감성: 식민지 유산 어휘(적산가옥/일제 등) 금지 → 미학 어휘 (feedback_lodging_copy_sensitivity 참조) +- AI 디스클로저: 엔드카드에 "AI로 재해석…" 의무 노출 diff --git a/concepts/01_cinematic-trailer.md b/concepts/01_cinematic-trailer.md new file mode 100644 index 0000000..63e8e70 --- /dev/null +++ b/concepts/01_cinematic-trailer.md @@ -0,0 +1,58 @@ +--- +concept_id: cinematic-trailer +psychology: 호기심 +priority: 1 +status: v0.1 +--- + +# 01. Cinematic Trailer + +## Pitch +> "이게 펜션 영상이라고? 영화 예고편인줄." + +펜션 사진 3~5장을 헐리우드 트레일러 문법으로 재구성. 슬로우 푸시인 → 미스터리한 디테일 → 와이드 리빌 → 펜션명 타이포그래피. + +## Psychology +- **트리거**: 호기심 (이게 뭐지?) +- **타겟 감정 곡선**: 긴장 → 호기심 → 만족 → 기억 + +## Visual Language +- 색감: 시네마틱 LUT (Teal & Orange, 또는 cold blue) +- 구도: 16:9 시네마스코프 마스크를 9:16 안에 박스로 (위아래 검정 바) +- 모션: 슬로우 푸시인·크레인 다운·미세한 핸드헬드 +- 타이포: 영문 serif (예: Cormorant) + 한글 명조 + +## 8-Second Beat Structure +| 비트 | 시간 | 사진 | 모션 | 자막 | +|---|---|---|---|---| +| Hook | 0-2s | 디테일 클로즈업 (창문·문손잡이·조명) | 슬로우 푸시인 | (무자막) | +| Build | 2-4s | 인테리어 와이드 | 크레인 다운 | "어떤 밤은 / 평생 기억에 남는다" | +| Reveal | 4-6.5s | 외관 풀샷 또는 야경 | 슬로우 풀백 | (펜션 분위기 강조 키워드 1개) | +| Brand | 6.5-8s | 시그니처 컷 + 정적 프레임 | 미세 줌인 | "○○펜션 · 강원 양양" | + +## Higgsfield Parameters +```yaml +camera_move: dolly_in_slow # 1.5초당 0.3m +motion_strength: 4 # 1-10 scale, 중간 강도 +style_keywords: + - "cinematic teal and orange" + - "anamorphic lens flare" + - "shallow depth of field" + - "film grain subtle" +duration_per_clip: 2.0 # 4클립 × 2초 = 8초 +aspect_ratio: "9:16" +fps: 30 +``` + +## Pension Fit +- ✅ 모든 펜션 범용 (특히 야간·인테리어 강한 곳) +- ✅ 풀빌라·독채형 펜션 최적 +- ⚠️ 캠핑형·글램핑은 다른 컨셉 권장 + +## Hard Constraints +- 색감 과장 OK, 구조 변형 NO +- 야간 LUT 적용 시 실제 야간 사진 1장 이상 포함 필수 + +## A/B Test Hypothesis +- vs Dream Sequence: 호기심 트리거가 더 강함 (Hook Rate +15%p 예상) +- vs Hyperreal Luxury: 도달은 비슷, 저장률 -2%p (욕망 트리거가 저장 유발에 강함) diff --git a/concepts/02_dream-sequence.md b/concepts/02_dream-sequence.md new file mode 100644 index 0000000..1923b8f --- /dev/null +++ b/concepts/02_dream-sequence.md @@ -0,0 +1,63 @@ +--- +concept_id: dream-sequence +psychology: 향수·로망 +priority: 1 +status: v0.1 +--- + +# 02. Dream Sequence + +## Pitch +> "꿈에서 본 것 같은 풍경. 가본 적 없는데 그리워지는 곳." + +펜션을 백일몽의 한 장면으로 변환. 안개·파스텔 톤·느린 호흡·플로팅 카메라. "기억 속 풍경"의 감각을 만들어 **저장률**을 노린다. + +## Psychology +- **트리거**: 향수 + 로망 + "언젠가 가야지" +- **타겟 감정 곡선**: 몽환 → 그리움 → 결심 → 저장 + +## Visual Language +- 색감: 파스텔 핑크·라벤더·소프트 옐로우, 헤이즈 필터 +- 구도: 비대칭·여백 많음·인물 없음 +- 모션: 떠다니는(floating) 카메라, 호버링, 매우 느린 패럴랙스 +- 타이포: 한글 명조 + 영문 italic, 화면 가장자리 배치 + +## 8-Second Beat Structure +| 비트 | 시간 | 사진 | 모션 | 자막 | +|---|---|---|---|---| +| Drift | 0-2.5s | 자연 디테일 (잎·물·구름) | 떠다니는 패럴랙스 | (없음) | +| Memory | 2.5-5s | 인테리어 한 구석 | 미세한 호흡 모션 | "기억나? / 가본 적 없는데" | +| Yearning | 5-7s | 외관 또는 풍경 와이드 | 느린 페이드 | "그리운 곳" | +| Brand | 7-8s | 정적 프레임 | (정지) | "○○펜션" | + +## Higgsfield Parameters +```yaml +camera_move: floating_hover +motion_strength: 2 # 매우 부드럽게 +style_keywords: + - "soft pastel haze" + - "dreamcore aesthetic" + - "lavender and peach tones" + - "ethereal lighting" + - "slight motion blur" +duration_per_clip: 2.0 +aspect_ratio: "9:16" +fps: 30 +post_fx: + - bloom_low + - chromatic_aberration_subtle +``` + +## Pension Fit +- ✅ 자연·뷰형 펜션 (산·바다·호수) +- ✅ 한옥·옛집 컨셉 +- ⚠️ 도심형·모던 펜션은 Hyperreal Luxury 권장 + +## Hard Constraints +- 색감 변환 자유, 단 풍경 실루엣은 보존 +- 안개 합성 OK, 단 실제 자연 요소(나무·산)는 변형 X + +## A/B Test Hypothesis +- 저장률 1위 후보 (욕망보다 향수가 저장 행동을 더 유발) +- 시청 유지율은 Cinematic Trailer보다 -5%p 예상 (느림 → 일부 스킵) +- 댓글 "여기 어디예요?" 비율 ★★★ diff --git a/concepts/03_hyperreal-luxury.md b/concepts/03_hyperreal-luxury.md new file mode 100644 index 0000000..c382299 --- /dev/null +++ b/concepts/03_hyperreal-luxury.md @@ -0,0 +1,64 @@ +--- +concept_id: hyperreal-luxury +psychology: 욕망 +priority: 2 +status: v0.1 +--- + +# 03. Hyperreal Luxury + +## Pitch +> "잡지에서나 보던 화보. 근데 이거 진짜 있다고?" + +색감·반사·질감을 과포화시켜 럭셔리 매거진 화보 톤으로 변환. 물 위 반사, 와인잔의 결, 침구 텍스처를 클로즈업으로 강조. + +## Psychology +- **트리거**: 욕망 ("나도 저기서 시간 보내고 싶다") +- **타겟 감정 곡선**: 시각적 압도 → 욕망 → 자기보상 욕구 → 예약 충동 + +## Visual Language +- 색감: 고채도·하이콘트라스트, 골드/딥블루/에메랄드 +- 구도: 클로즈업·디테일 중심·구성적 +- 모션: 정밀한 dolly, 마크로 푸시인, 빛 반사 강조 +- 타이포: 영문 sans-serif (DIDOT 계열) + 한글 고딕 + +## 8-Second Beat Structure +| 비트 | 시간 | 사진 | 모션 | 자막 | +|---|---|---|---|---| +| Desire | 0-2s | 디테일 매크로 (텍스처·반사) | 정밀 푸시인 | "Stay where it matters" | +| Indulge | 2-4.5s | 욕조·침구·다이닝 | 슬라이드 | (무자막) | +| Escape | 4.5-7s | 인테리어 와이드 | 와이드 풀백 | "당신만을 위한" | +| Brand | 7-8s | 시그니처 컷 | 정지 | "○○풀빌라" | + +## Higgsfield Parameters +```yaml +camera_move: precision_push_dolly +motion_strength: 5 +style_keywords: + - "luxury magazine editorial" + - "high contrast saturated" + - "gold and emerald palette" + - "macro detail" + - "polished reflective surfaces" +duration_per_clip: 2.0 +aspect_ratio: "9:16" +fps: 30 +post_fx: + - clarity_high + - vibrance_high +``` + +## Pension Fit +- ✅ 풀빌라·프리미엄 독채 +- ✅ 스파·자쿠지 보유 펜션 +- ❌ 글램핑·캠핑·게스트하우스 + +## Hard Constraints +- 디테일 색감 과장 OK +- 없는 어메니티(수영장·자쿠지) 합성 절대 금지 +- 가격대 오해를 유발하는 럭셔리 표현 시, 캡션에 실제 가격대 명시 필수 + +## A/B Test Hypothesis +- 객단가 높은 펜션에서 ROAS 최고 +- 도달은 Cinematic Trailer 대비 -10% (정적 톤 → 알고리즘 약점) +- 그러나 **전환율(예약)** 은 ★★★ — 욕망 트리거가 행동으로 가장 빠르게 변환 diff --git a/concepts/04_time-warp.md b/concepts/04_time-warp.md new file mode 100644 index 0000000..c79c161 --- /dev/null +++ b/concepts/04_time-warp.md @@ -0,0 +1,60 @@ +--- +concept_id: time-warp +psychology: 놀라움 +priority: 3 +status: v0.1 +--- + +# 04. Time Warp + +## Pitch +> "낮 → 노을 → 밤 → 새벽. 8초 안에 하루를 전부." + +같은 펜션의 같은 앵글을 시간대별로 변환해 빠르게 전환. 시간 변화의 시각적 충격으로 어텐션 확보. + +## Psychology +- **트리거**: 놀라움 + "와 시간이 흐른다" +- **타겟 감정 곡선**: 인지부조화 → 이해 → 감탄 → 공유 욕구 + +## Visual Language +- 색감: 시간대별 점진 변화 (낮 백색광 → 노을 오렌지 → 야간 블루 → 새벽 보라) +- 구도: 동일 앵글 유지 (변하는 건 빛만) +- 모션: 미세한 호흡, 시간 경과 효과 +- 타이포: 시계/시간 표시 모션그래픽 + +## 8-Second Beat Structure +| 비트 | 시간 | 사진 변환 | 자막 | +|---|---|---|---| +| Day | 0-2s | 정오 톤 | "12:00" | +| Sunset | 2-4s | 골든아워 | "18:30" | +| Night | 4-6s | 야간 블루 | "23:00" | +| Dawn | 6-8s | 새벽 미스트 | "05:45 / ○○펜션의 하루" | + +## Higgsfield Parameters +```yaml +camera_move: locked_off_breathing # 카메라는 거의 정지, 빛만 변화 +motion_strength: 1 # 카메라 모션 최소 +style_keywords: + - "time of day variation" + - "golden hour to blue hour" + - "circadian lighting" + - "long exposure sky" +duration_per_clip: 2.0 +aspect_ratio: "9:16" +fps: 30 +mode: "relighting" # 같은 사진의 라이팅만 변환 +``` + +## Pension Fit +- ✅ 풍경 좋은 자연형 펜션 +- ✅ 한옥·레트로 컨셉 (시간성과 잘 어울림) +- ⚠️ 풍경 없이 인테리어만으로는 효과 약함 + +## Hard Constraints +- 같은 펜션 같은 앵글 — 다른 펜션 합성 절대 금지 +- 시간대별 자연광 시뮬레이션은 OK, 인공조명 추가는 신중 + +## A/B Test Hypothesis +- 공유율 1위 후보 ("이거 봐봐" 트리거 강함) +- Hook Rate는 Cinematic Trailer 대비 -5%p (정적 톤 단점) +- 외부링크 클릭률 ★★ (호기심 → 검색 유발) diff --git a/concepts/05_anime-painterly.md b/concepts/05_anime-painterly.md new file mode 100644 index 0000000..7b33f69 --- /dev/null +++ b/concepts/05_anime-painterly.md @@ -0,0 +1,62 @@ +--- +concept_id: anime-painterly +psychology: 웃음·공유욕 +priority: 3 +status: v0.1 +--- + +# 05. Anime Painterly + +## Pitch +> "지브리 영화 속 펜션. 토토로가 살 것 같은." + +펜션 사진을 일러스트레이션/지브리 페인터리 스타일로 변환. 가장 명백한 "AI 티"를 의도적으로 노출, 친근감·공유욕을 노린다. + +## Psychology +- **트리거**: 웃음 + 친근감 + 공유 욕구 ("이거 너무 귀여워") +- **타겟 감정 곡선**: 놀라움 → 미소 → 공유 + +## Visual Language +- 색감: 채도 높은 페인터리 톤, 손그림 텍스처 +- 구도: 동화책 일러스트 구도 +- 모션: 2D 페럴랙스, 종이 결 노이즈 +- 타이포: 손글씨체 한글 + 영문 brushscript + +## 8-Second Beat Structure +| 비트 | 시간 | 변환 | 자막 | +|---|---|---|---| +| Intro | 0-2s | 외관 페인터리 | "그림 같은 곳" | +| Detail | 2-4.5s | 인테리어 일러스트 | (작은 디테일 강조) | +| Wonder | 4.5-7s | 풍경 와이드 페인팅 | "동화 같은 하룻밤" | +| Brand | 7-8s | 펜션명 손글씨 | "○○펜션" | + +## Higgsfield Parameters +```yaml +camera_move: 2d_parallax +motion_strength: 3 +style_keywords: + - "studio ghibli painterly" + - "watercolor illustration" + - "hand-drawn aesthetic" + - "children's book art" + - "warm earthy palette" +duration_per_clip: 2.0 +aspect_ratio: "9:16" +fps: 24 # 애니메이션 톤을 위해 24fps +mode: "style_transfer_strong" +``` + +## Pension Fit +- ✅ 특이/테마 펜션 (한옥·트리하우스·돔) +- ✅ 반려동물·가족 타겟 펜션 +- ⚠️ 럭셔리·미니멀 펜션과는 톤 충돌 + +## Hard Constraints +- 스타일 변환 가장 강함 → 캡션에 "AI 일러스트 재해석" 명시 필수 +- 실재 인물(투숙객) 일러스트화 금지 + +## A/B Test Hypothesis +- 공유율 ★★★ ("DM으로 친구한테 보내고 싶음" 트리거) +- 직접 전환율 ★ (욕망 트리거 약함) +- 그러나 브랜드 인지·바이럴 성과 ★★★ +- 적합한 펜션은 30% 미만 → 선별 운영 필요 diff --git a/concepts/README.md b/concepts/README.md new file mode 100644 index 0000000..4e7ef64 --- /dev/null +++ b/concepts/README.md @@ -0,0 +1,30 @@ +# Concept Library + +8초 임팩트 영상의 **컨셉 = 심리 트리거 + 시각 언어 + Higgsfield 파라미터 프리셋**. + +## 컨셉 3축 구조 + +모든 컨셉 파일은 다음 3축으로 정의된다: + +| 축 | 정의 | +|---|---| +| **Psychology** | 시청자에게 일으킬 감정 (호기심·욕망·놀라움·향수·웃음) | +| **Visual Language** | 색감·구도·모션 스타일 | +| **Higgsfield Params** | 카메라 무브(orbit/dolly/push-in/crane), 모션 강도(1-10), 스타일 키워드 | + +## Phase 1 라이브러리 (5종) + +| # | 컨셉 | 심리 트리거 | 우선순위 | +|---|---|---|---| +| 01 | [Cinematic Trailer](01_cinematic-trailer.md) | 호기심 | ★★★ | +| 02 | [Dream Sequence](02_dream-sequence.md) | 향수·로망 | ★★★ | +| 03 | [Hyperreal Luxury](03_hyperreal-luxury.md) | 욕망 | ★★ | +| 04 | [Time Warp](04_time-warp.md) | 놀라움 | ★ | +| 05 | [Anime Painterly](05_anime-painterly.md) | 웃음·공유욕 | ★ | + +## 사용 방법 (Content Agent 통합 시) + +1. Content Agent가 펜션 사진셋 5장을 분석 +2. 사진 특성(자연/도시·낮/밤·미니멀/디테일) → 컨셉 매칭 점수 계산 +3. 상위 2개 컨셉으로 각 1편씩 생성 → A/B 발행 +4. 7일 후 성과 데이터로 컨셉별 가중치 업데이트 diff --git a/configs/higgsfield.yaml b/configs/higgsfield.yaml new file mode 100644 index 0000000..f8e290b --- /dev/null +++ b/configs/higgsfield.yaml @@ -0,0 +1,41 @@ +# Higgsfield API configuration for ADO2 marketing pipeline +# Env vars: HIGGSFIELD_API_KEY, HIGGSFIELD_BASE_URL + +api: + base_url: "${HIGGSFIELD_BASE_URL:-https://api.higgsfield.ai/v1}" + api_key_env: "HIGGSFIELD_API_KEY" + timeout_seconds: 180 + max_retries: 3 + retry_backoff_seconds: [5, 15, 45] + +defaults: + aspect_ratio: "9:16" # Phase 1: Reels/Shorts only + duration_seconds: 8 + fps: 30 + resolution: "1080x1920" + output_format: "mp4" + output_codec: "h264" + +cost_guardrails: + max_cost_per_video_usd: 2.0 + monthly_budget_per_tenant_usd: 80 # 월 40편 기준 + alert_threshold_pct: 80 + +quality_gates: + # 자동 컷오프 기준 (생성 후 검수) + reject_if: + - face_artifact_score_gt: 0.6 # 얼굴 왜곡 자동 컷 + - hand_artifact_score_gt: 0.7 + - text_artifact_present: true # 자동 생성된 가짜 텍스트 + - structural_drift_gt: 0.4 # 펜션 구조 변형 의심 + +concurrency: + max_parallel_renders: 3 + queue_priority: + - tenant_priority_high: 1 + - tenant_priority_normal: 2 + +observability: + log_level: "INFO" + metrics_endpoint: "${PROMETHEUS_PUSHGATEWAY_URL:-}" + trace_sample_rate: 0.1 diff --git a/docs/pipeline-integration.md b/docs/pipeline-integration.md new file mode 100644 index 0000000..00d8366 --- /dev/null +++ b/docs/pipeline-integration.md @@ -0,0 +1,79 @@ +# Pipeline Integration + +ADO2 Agent 파이프라인에 본 엔진을 통합하는 인터페이스 스펙. + +## 입력 인터페이스 + +```python +# agents/src/integrations/higgsfield_shorts.py 에서 호출 +from engine.higgsfield_shorts import generate_short + +result = await generate_short( + tenant_id="acme-pension", + shoot_id="2026-06-15_summer-shoot", + photos=[ + "s3://ado2/media/acme/p1.jpg", # 3~5장 + "s3://ado2/media/acme/p2.jpg", + "s3://ado2/media/acme/p3.jpg", + ], + concept_id="cinematic-trailer", # concepts/ 라이브러리에서 선택 + brand={ + "name": "○○펜션", + "location": "강원 양양", + "cta_url": "https://booking.example.com/acme", + }, + options={ + "aspect_ratio": "9:16", + "duration": 8, + "caption_lang": "ko", + }, +) +# result.video_url, result.thumbnail_url, result.cost_usd, result.metrics +``` + +## 출력 메타데이터 + +```json +{ + "video_url": "s3://ado2/output/acme/2026-06-15/cinematic-trailer.mp4", + "thumbnail_url": "s3://ado2/output/acme/2026-06-15/cinematic-trailer.jpg", + "trust_track_link": "s3://ado2/output/acme/2026-06-15/trust-slideshow.mp4", + "concept_id": "cinematic-trailer", + "duration_seconds": 8, + "cost_usd": 1.42, + "quality_score": 0.87, + "render_time_seconds": 240, + "metadata_for_distribution": { + "suggested_caption": "...", + "suggested_hashtags": [...], + "suggested_post_time": "...", + "ai_disclosure_required": true + } +} +``` + +## MarketingState 통합 + +`agents/src/common/state.py`의 `MarketingState`에 다음 필드 추가: + +```python +class MarketingState(TypedDict): + # ... existing fields + higgsfield_renders: list[HiggsfieldRender] # 어텐션 트랙 결과들 + trust_track_renders: list[CreatomateRender] # 트러스트 트랙 결과들 + paired_assets: dict[str, str] # shoot_id -> trust_video_id 매핑 +``` + +## Distribution Agent 핸드오프 + +Distribution Agent는 두 트랙을 **페어 배포** 한다: + +1. **Reels/Shorts 피드** ← Higgsfield (어텐션) +2. **프로필 핀 게시물** ← Creatomate (트러스트) +3. **캡션 자동 생성** 시 AI 디스클로저 라인 포함 ("AI로 재해석한 ○○펜션의 무드") + +## Phase 2 (자동화 도입 후) + +- Content Agent가 사진셋 자동 분석 → 컨셉 자동 선택 +- 1개 사진셋 → 2개 컨셉으로 A/B 자동 생성 +- 7일 후 Analytics Agent가 컨셉별 성과 학습 → 컨셉 가중치 업데이트 diff --git a/inputs/.gitkeep b/inputs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/outputs/.gitkeep b/outputs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a9ba1b1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,42 @@ +{ + "name": "@ado2/higgsfield-shorts", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@ado2/higgsfield-shorts", + "version": "0.1.0", + "devDependencies": { + "@higgsfield/cli": "^0.1.40" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@higgsfield/cli": { + "version": "0.1.40", + "resolved": "https://registry.npmjs.org/@higgsfield/cli/-/cli-0.1.40.tgz", + "integrity": "sha512-SgpShjkFZfMomvXAtLUDAp4n6oI/v7smAMJFmc4pCNfwucXYVbBQiLrYHrsXcaC2rJDhiBxhcrkBu0bStmcc7w==", + "cpu": [ + "x64", + "arm64" + ], + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32" + ], + "bin": { + "higgs": "bin/higgs.js", + "higgsfield": "bin/higgsfield.js" + }, + "engines": { + "node": ">=14" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1278120 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "@ado2/higgsfield-shorts", + "version": "0.1.0", + "private": true, + "description": "ADO2 어텐션 트랙 영상 엔진 — Higgsfield 기반 8초 펜션 숏츠 (Intentional Unreal 전략)", + "type": "module", + "engines": { + "node": ">=22" + }, + "scripts": { + "hf": "higgsfield", + "hf:version": "higgsfield --version", + "hf:help": "higgsfield --help", + "gen:trailer": "higgsfield generate --concept cinematic-trailer", + "gen:dream": "higgsfield generate --concept dream-sequence", + "gen:luxury": "higgsfield generate --concept hyperreal-luxury", + "gen:timewarp": "higgsfield generate --concept time-warp", + "gen:anime": "higgsfield generate --concept anime-painterly" + }, + "devDependencies": { + "@higgsfield/cli": "^0.1.40" + }, + "ado2": { + "engine": "higgsfield_shorts", + "track": "attention", + "pairedTrack": "engine/semantic_video", + "phase": 1, + "phaseDeadline": "2026-07-09" + } +} diff --git a/prompts/README.md b/prompts/README.md new file mode 100644 index 0000000..28da333 --- /dev/null +++ b/prompts/README.md @@ -0,0 +1,58 @@ +# Prompt Templates + +Higgsfield Image-to-Video API에 전달할 프롬프트 빌더. + +## 구조 + +Higgsfield 프롬프트는 다음 4부로 구성: + +1. **Subject Description** — 사진 내용 (자동 생성, Claude Vision 사용) +2. **Camera Motion** — 컨셉 프리셋에서 가져옴 +3. **Style Modifiers** — 컨셉의 `style_keywords` 결합 +4. **Negative Prompt** — 펜션 마케팅 가드레일 + +## 템플릿 예시 + +```jinja +# Cinematic Trailer 프롬프트 + +[SUBJECT]: {{ photo_caption }} +[MOTION]: {{ concept.camera_move }}, motion strength {{ concept.motion_strength }} +[STYLE]: {{ concept.style_keywords | join(", ") }} +[NEGATIVE]: distorted faces, extra fingers, fake text, brand logos, + structural deformation, watermarks, low quality +``` + +## 가드레일 (모든 컨셉 공통 Negative Prompt) + +``` +distorted human faces, +extra fingers, malformed hands, +fake text or signs, +competitor brand logos, +structural deformation of building, +fake amenities (pool, jacuzzi if not in source), +celebrities or recognizable people, +star ratings, fake reviews, +watermarks, low quality, blurry +``` + +## 펜션 사진 캡션 생성 (Claude Vision) + +각 입력 사진에 대해 Claude Vision으로 다음 메타데이터를 추출: + +```yaml +scene_type: "interior" | "exterior" | "nature" | "detail" +time_of_day: "day" | "golden_hour" | "night" | "dawn" +focal_element: "bed | window | view | texture | ..." +mood_keywords: ["cozy", "spacious", "wooden", ...] +structural_features: ["wooden_beams", "floor_to_ceiling_window", ...] # 보존 필수 +``` + +이 메타데이터를 기반으로 프롬프트 빌더가 컨셉 매칭 점수를 계산 + 프롬프트 구성. + +## TBD (W2 작성 예정) + +- 컨셉별 jinja 템플릿 파일 5개 (`01_cinematic-trailer.j2` 등) +- 한글 자막 자동 생성 프롬프트 (Claude API) +- 음악 매칭 메타데이터 스키마 diff --git a/prompts/camera-vocabulary.md b/prompts/camera-vocabulary.md new file mode 100644 index 0000000..01e8a83 --- /dev/null +++ b/prompts/camera-vocabulary.md @@ -0,0 +1,90 @@ +--- +title: Camera Vocabulary & Keyframe Templates +status: v0.1 (2026-05-28) +note: Higgsfield 시그니처 카메라 프리셋은 웹 UI 전용. CLI에선 프롬프트 시네마토그래피 동사로 유도. +--- + +# Camera Vocabulary & Keyframe Templates + +## 1. 카메라 무브 어휘 (CLI 프롬프트 동사) + +각 무브의 영문 프롬프트 조각 + 모델 안정성 + 적합 Profile. + +| 무브 | 프롬프트 조각 | 안정성 | Profile | +|---|---|---|---| +| `push_in_slow` | "very slow cinematic dolly push-in, smooth and steady" | ★★★★★ | A,B,C | +| `push_crash` (크래시줌) | "sudden fast crash zoom-in toward [subject], punchy and energetic" | ★★★ | A(액센트),B,C | +| `pull_back` | "slow dolly pull-back revealing the wider space" | ★★★★ | A,B | +| `orbit_L` / `orbit_R` | "smooth camera orbit moving [left/right] around [subject], parallax depth" | ★★★★ | B,C (A는 짧게) | +| `arc` | "gentle arc movement sweeping past [subject]" | ★★★ | B | +| `crane_up` / `crane_down` | "slow cinematic crane [up/down], revealing [from/to]" | ★★★ | A(미세),B | +| `dolly_through` | "camera dollying through [doorway/window/frame], entering the space" | ★★★ | A(Hook),B | +| `rack_focus` | "shallow depth of field rack focus shifting from foreground to background" | ★★★★ | A,B | +| `tilt_reveal` | "slow vertical tilt revealing [ceiling beams / full height]" | ★★★ | A,B | +| `dolly_zoom` (버티고) | "dolly zoom vertigo effect, background warping while subject stays" | ★★ | C | +| `fpv_drone` | "fast FPV drone fly-through, dynamic immersive motion" | ★★ | B,C | +| `handheld_drift` | "subtle handheld drift, organic micro-movement" | ★★★★ | A,B | +| `speed_ramp` | "speed ramp from slow motion snapping to real time" | ★★★ | A(액센트),B,C | + +**안정성 ★★★ 이하는 키프레임(start/end)과 병행** 권장 → 모델이 궤적을 잡도록. + +--- + +## 2. 키프레임 보간 기법 (`--start-image` / `--end-image`) + +`generate create`는 두 키프레임을 받아 그 사이를 보간 → 방향성 있는 카메라무브·씬 모핑. + +### 패턴 A — 방향성 무브 (한 씬 내 줌/앵글 변화) +```bash +higgsfield generate create \ + --prompt "smooth dolly push-in from wide shot to tight close-up of the shell pendant lamp" \ + --start-image "wide_kitchen.jpg" \ + --end-image "closeup_shelllamp.png" # 이미지모델로 생성한 클로즈업 스틸 + --aspect_ratio "9:16" --duration 5 +``` +→ 와이드에서 클로즈로 빨려드는 의도된 무브. 단순 push보다 훨씬 정교. + +### 패턴 B — 씬 모핑 트랜지션 (컷 N → 컷 N+1) +```bash +# 컷2의 마지막 프레임 = 컷3의 첫 프레임으로 연결 +--start-image "cut2_lastframe.png" --end-image "cut3_firstframe.png" +``` +→ 두 공간이 매끄럽게 모핑. Remotion 컷 트랜지션과 별개로 "생성형 트랜지션". + +### end-frame 생성 (이미지 모델) +목표 프레임이 원본에 없을 때 이미지 모델로 제작: +```bash +higgsfield generate create nano_banana_2 \ + --prompt "extreme close-up of the glowing shell-shaped pendant lamp, same room, same light" \ + --image "스테이 머뭄 02.jpeg" --wait --json +# 또는 seedream_v4_5 +``` +→ 반환된 이미지 id를 video 생성의 `--end-image`로 사용. + +--- + +## 3. 프롬프트 조립 공식 + +``` +[CAMERA MOVE] + [SUBJECT/SCENE] + [LIGHT/MOOD] + [STYLE/LUT] + [GRAIN/DOF] +``` +예 (Profile A, orbit+crash 액센트): +``` +"smooth camera orbit-left around a glowing sculptural shell pendant lamp under +wooden vaulted ceiling beams, then a subtle crash zoom toward the lamp, +warm amber interior light, modern Korean stay kitchen, cinematic teal and +orange grade, shallow depth of field, subtle film grain, slow cinema" +``` + +### Negative(프롬프트 내 자연어로 — CS 계열은 negative 파라미터 없음) +구조 왜곡·가짜 텍스트·인물·없는 어메니티 회피를 프롬프트 끝에 명시하거나, negative 지원 모델(일부)만 별도 사용. + +--- + +## 4. Profile별 컷 카메라 배정 가이드 + +- **A. Still Cinema**: [push_in_slow] ×2~3 + [push_crash 또는 speed_ramp] ×1~2 + [crane_down/rack_focus] 마감 +- **B. Rhythm Reveal**: [orbit/arc/dolly_through] 교차 + [fpv/crane] 히어로 + [push] 연결 +- **C. Maximum Viral**: [push_crash/dolly_zoom] 연타 + [fpv_drone] 1~2 + 하드컷 + +> 원칙: **연속 두 컷이 같은 무브를 반복하지 않는다.** (단조로움 방지 1순위 규칙) diff --git a/remotion/package-lock.json b/remotion/package-lock.json new file mode 100644 index 0000000..aaa7a82 --- /dev/null +++ b/remotion/package-lock.json @@ -0,0 +1,2907 @@ +{ + "name": "@ado2/higgsfield-remotion", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@ado2/higgsfield-remotion", + "version": "0.1.0", + "dependencies": { + "@remotion/cli": "4.0.468", + "@remotion/google-fonts": "4.0.468", + "react": "19.2.0", + "react-dom": "19.2.0", + "remotion": "4.0.468" + }, + "devDependencies": { + "@types/react": "19.2.0", + "typescript": "5.7.3" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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/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/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "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/@mediabunny/aac-encoder": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/@mediabunny/aac-encoder/-/aac-encoder-1.45.0.tgz", + "integrity": "sha512-vLQw8cY7Me6pvTTMkMhOiH9UCuINzfTOETCeDxbGNeNfDqc/7QlxloUH1Ylp/Zz2ek0O8kc6YdygV2vWAPakrA==", + "license": "MPL-2.0", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Vanilagy" + }, + "peerDependencies": { + "mediabunny": "^1.0.0" + } + }, + "node_modules/@mediabunny/flac-encoder": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/@mediabunny/flac-encoder/-/flac-encoder-1.45.0.tgz", + "integrity": "sha512-LfKbAMZVkxRS7PpEIVnWOY/l0KcHv+rjO7pYY3O0TPCZvbHWfrnQjn8JPacPIfuq6Yv7r4f8lhcl7yHSynoRkQ==", + "license": "MPL-2.0", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Vanilagy" + }, + "peerDependencies": { + "mediabunny": "^1.0.0" + } + }, + "node_modules/@mediabunny/mp3-encoder": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/@mediabunny/mp3-encoder/-/mp3-encoder-1.45.0.tgz", + "integrity": "sha512-Bobi6AaQYEc7TWmPJ8Q0/hcUtBN7pLUC2qjoC7oZR4FcGqGztby6k7A1SWlmswoMOEIhYsOrgDaemrSDAC0QVQ==", + "license": "MPL-2.0", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Vanilagy" + }, + "peerDependencies": { + "mediabunny": "^1.0.0" + } + }, + "node_modules/@module-federation/error-codes": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.22.0.tgz", + "integrity": "sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==", + "license": "MIT" + }, + "node_modules/@module-federation/runtime": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.22.0.tgz", + "integrity": "sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==", + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.22.0", + "@module-federation/runtime-core": "0.22.0", + "@module-federation/sdk": "0.22.0" + } + }, + "node_modules/@module-federation/runtime-core": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.22.0.tgz", + "integrity": "sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==", + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.22.0", + "@module-federation/sdk": "0.22.0" + } + }, + "node_modules/@module-federation/runtime-tools": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.22.0.tgz", + "integrity": "sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==", + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.22.0", + "@module-federation/webpack-bundler-runtime": "0.22.0" + } + }, + "node_modules/@module-federation/sdk": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.22.0.tgz", + "integrity": "sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==", + "license": "MIT" + }, + "node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.22.0.tgz", + "integrity": "sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==", + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.22.0", + "@module-federation/sdk": "0.22.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", + "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@remotion/bundler": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/bundler/-/bundler-4.0.468.tgz", + "integrity": "sha512-jsCzE4KmDLnV+hz4UOWjOJnhzH90LNrnqIrSpwg+Wu5fZip9tPA414qZNKuF+M3Z4riBC4UOJVt5/BQ41Uk0wg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@remotion/media-parser": "4.0.468", + "@remotion/studio": "4.0.468", + "@remotion/studio-shared": "4.0.468", + "@remotion/timeline-utils": "4.0.468", + "@rspack/core": "1.7.6", + "@rspack/plugin-react-refresh": "1.6.1", + "css-loader": "7.1.4", + "esbuild": "0.28.0", + "react-refresh": "0.18.0", + "remotion": "4.0.468", + "style-loader": "4.0.0", + "webpack": "5.105.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/cli": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/cli/-/cli-4.0.468.tgz", + "integrity": "sha512-wHc9cjDYJDiP5hfFHal1xuDnkb5Wku6162tCQ9V5bNf5BKC1zEN6DA+S5sN/yWMhYgUxyaO12nLmz9h50PrkDA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@remotion/bundler": "4.0.468", + "@remotion/media-utils": "4.0.468", + "@remotion/player": "4.0.468", + "@remotion/renderer": "4.0.468", + "@remotion/studio": "4.0.468", + "@remotion/studio-server": "4.0.468", + "@remotion/studio-shared": "4.0.468", + "dotenv": "17.3.1", + "minimist": "1.2.6", + "prompts": "2.4.2", + "remotion": "4.0.468" + }, + "bin": { + "remotion": "remotion-cli.js", + "remotionb": "remotionb-cli.js", + "remotiond": "remotiond-cli.js" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/compositor-darwin-arm64": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-arm64/-/compositor-darwin-arm64-4.0.468.tgz", + "integrity": "sha512-wB1m1bSvqU+CGg4qxZt8XGZf+4eDFot58IMy6Z7b2+qHpvE/qdtm2CENvlSuDu/zVO6b10wWbaWGKz0jf2YM0Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@remotion/compositor-darwin-x64": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/compositor-darwin-x64/-/compositor-darwin-x64-4.0.468.tgz", + "integrity": "sha512-WdJS1T7zbxHRraK9F6SIpWbfU3UprSeLnmoHtkMhW35o0zfaq3scfprmm1eTHSynlttz2nRwEvNPrNvhbJrBtg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@remotion/compositor-linux-arm64-gnu": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-gnu/-/compositor-linux-arm64-gnu-4.0.468.tgz", + "integrity": "sha512-HEXjGlcL1b/7EfZkJOSs+XQt5mJ88f6SJB/uIHzde3hpYwZvqmtls9zC8wzM6VncIYNQOu8gDxfbVzAZ+Hfv3Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@remotion/compositor-linux-arm64-musl": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-arm64-musl/-/compositor-linux-arm64-musl-4.0.468.tgz", + "integrity": "sha512-8Qf14zSsvbAKfWdcrdxhPP4pm0A6qsc72vzWjDIH1XmYAKHg49eOHolQTr6bzNyekMLgJ3eKC66W4UFYny0gfg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@remotion/compositor-linux-x64-gnu": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-gnu/-/compositor-linux-x64-gnu-4.0.468.tgz", + "integrity": "sha512-PqbgcutSTR3Od3WTkOlMBIgW4sFeylnWYK8Pdcfs81TePD2xFDXEKY45HBYpi58EzSt6zp2qdQAW8b3NrIhEiw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@remotion/compositor-linux-x64-musl": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/compositor-linux-x64-musl/-/compositor-linux-x64-musl-4.0.468.tgz", + "integrity": "sha512-EtMjnQvaYOvNDGvYqEQdcfznN0sKCf7fT9lA1admcTYJ7h/jxFydj8aTTpvgmA3VG4TN1IL70RSLVu3Esx1Ylg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@remotion/compositor-win32-x64-msvc": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/compositor-win32-x64-msvc/-/compositor-win32-x64-msvc-4.0.468.tgz", + "integrity": "sha512-h6fpDu9e1Wfpg5jpYABYwbh1y3uqhkOS3FYfnUCvKNBp+ZpNkla8TfsQsQVoMECtxxOwLHLYesY5BzvUtOZ/5Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@remotion/google-fonts": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/google-fonts/-/google-fonts-4.0.468.tgz", + "integrity": "sha512-Dl59gKL+GiVaozSfNt+b4b6iYyR5T1GtlMOF5Yxn/JLyRSw1hUYFreon+m0qZ8n5mZ+iipqlKeNjfYKGWtbtww==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "remotion": "4.0.468" + } + }, + "node_modules/@remotion/licensing": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/licensing/-/licensing-4.0.468.tgz", + "integrity": "sha512-l0iIn8YVdKiOBYDN1bnbtguMReq/Q7QozxtHr+mKIHr66zeCjhpwFvHklmN/LXMFGWh2OxfNJ6fgXKWS1Iuy3A==", + "license": "MIT" + }, + "node_modules/@remotion/media-parser": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/media-parser/-/media-parser-4.0.468.tgz", + "integrity": "sha512-lh6IGOmVnCsV6oytuGbgr9PB/AkWnWRwBr8tsq7gpjBqhR3oLe3fFd+p7jJreMrF+l4y55bcrf52qT3yxBD9fw==", + "license": "Remotion License https://remotion.dev/license" + }, + "node_modules/@remotion/media-utils": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/media-utils/-/media-utils-4.0.468.tgz", + "integrity": "sha512-at4/faHpezfYuv3WikG1ePQY3pBnjWTgFv2++IRJ4z5+ZneoSM1mtFS9lIJO9maCPRLxJRqHWOI0+rnNrNHADA==", + "license": "MIT", + "dependencies": { + "mediabunny": "1.45.0", + "remotion": "4.0.468" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/player": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/player/-/player-4.0.468.tgz", + "integrity": "sha512-M0kdFnAZvstMctK2ypoTkFOahI61kUE9HnQAh5/9dPb+FlOwCDLRDnM2LBkltZsJxgqgH1nDe1iNPsry0QCjmw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "remotion": "4.0.468" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/renderer": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/renderer/-/renderer-4.0.468.tgz", + "integrity": "sha512-n8fLD9o5W0N4FEz9V7OBmHjiG/Qo36cwH/MaUsEdATCfKIUGlMjrBvyFuKGobl5GHdt3xCyzYQoar3vFiwll8Q==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@remotion/licensing": "4.0.468", + "@remotion/streaming": "4.0.468", + "execa": "5.1.1", + "remotion": "4.0.468", + "source-map": "0.8.0-beta.0", + "ws": "8.20.1" + }, + "optionalDependencies": { + "@remotion/compositor-darwin-arm64": "4.0.468", + "@remotion/compositor-darwin-x64": "4.0.468", + "@remotion/compositor-linux-arm64-gnu": "4.0.468", + "@remotion/compositor-linux-arm64-musl": "4.0.468", + "@remotion/compositor-linux-x64-gnu": "4.0.468", + "@remotion/compositor-linux-x64-musl": "4.0.468", + "@remotion/compositor-win32-x64-msvc": "4.0.468" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/streaming": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/streaming/-/streaming-4.0.468.tgz", + "integrity": "sha512-Ts0pq7Lh+ztSvJp5oOTHOc2oKuQ3LtXGfWhQKiJXm81Xz5Qjz3KbPhQ/Z94/GzbZDRrs+zdzwRyqS6pTFnoLdg==", + "license": "MIT" + }, + "node_modules/@remotion/studio": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/studio/-/studio-4.0.468.tgz", + "integrity": "sha512-Wzy/EtI4GLbkExet253KTrwOunJb3ciGFf+/bd0FBOt3plmaPxuJe4iJ4IvMwwA0/xR1U9HkgXg/gPZxzVHsSQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@remotion/media-utils": "4.0.468", + "@remotion/player": "4.0.468", + "@remotion/renderer": "4.0.468", + "@remotion/studio-shared": "4.0.468", + "@remotion/timeline-utils": "4.0.468", + "@remotion/web-renderer": "4.0.468", + "@remotion/zod-types": "4.0.468", + "mediabunny": "1.45.0", + "memfs": "3.4.3", + "open": "8.4.2", + "remotion": "4.0.468", + "semver": "7.5.3", + "zod": "4.3.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/studio-server": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/studio-server/-/studio-server-4.0.468.tgz", + "integrity": "sha512-xg5BlBUSnb/t1wa84fPwfgOe5WkS25fr6JVEUt9SBRZW3bbtcrGgCfykSFvIqx/nlG6S9Rw9XZuQnO6GOKR2oQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "7.24.1", + "@babel/types": "7.24.0", + "@remotion/bundler": "4.0.468", + "@remotion/renderer": "4.0.468", + "@remotion/studio-shared": "4.0.468", + "memfs": "3.4.3", + "open": "8.4.2", + "prettier": "3.8.1", + "recast": "0.23.11", + "remotion": "4.0.468", + "semver": "7.5.3" + } + }, + "node_modules/@remotion/studio-shared": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/studio-shared/-/studio-shared-4.0.468.tgz", + "integrity": "sha512-PNfOIpQ+tSzOuEbedU+SueyvSXemp2i5IesHwlJgN+70jcbOCt29d5/1MDlJbgcydBe9fMnsrYrosP3ln5kAxA==", + "license": "MIT", + "dependencies": { + "remotion": "4.0.468" + } + }, + "node_modules/@remotion/timeline-utils": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/timeline-utils/-/timeline-utils-4.0.468.tgz", + "integrity": "sha512-B99PXVVDILfUurh8QesJAfwZOy56H2oHs6dxf8HUnUN8xMSNKJx62C0zELiYb56KBTT1ryTGxWY64lTaUIuEcw==", + "license": "MIT", + "dependencies": { + "mediabunny": "1.45.0" + } + }, + "node_modules/@remotion/web-renderer": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/web-renderer/-/web-renderer-4.0.468.tgz", + "integrity": "sha512-Ucxw9F7IkqHQdol9qu6UqpQKr29JjeyLrTDPSzDp0ec55MCO7oDvlktYkdc8IGmMQfh0FD8b603phUSXax4XnQ==", + "license": "UNLICENSED", + "dependencies": { + "@mediabunny/aac-encoder": "1.45.0", + "@mediabunny/flac-encoder": "1.45.0", + "@mediabunny/mp3-encoder": "1.45.0", + "@remotion/licensing": "4.0.468", + "mediabunny": "1.45.0", + "remotion": "4.0.468" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@remotion/zod-types": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/@remotion/zod-types/-/zod-types-4.0.468.tgz", + "integrity": "sha512-W6KejABTdCFZW0P0uVO7Ol7QMD8n/ewaKhyo5AzKx+0CfIelgQREWDXINBzJxZM5XnJXdn9Q0ffDWsBCnQFAaw==", + "license": "MIT", + "dependencies": { + "remotion": "4.0.468" + }, + "peerDependencies": { + "zod": "4.3.6" + } + }, + "node_modules/@rspack/binding": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.7.6.tgz", + "integrity": "sha512-/NrEcfo8Gx22hLGysanrV6gHMuqZSxToSci/3M4kzEQtF5cPjfOv5pqeLK/+B6cr56ul/OmE96cCdWcXeVnFjQ==", + "license": "MIT", + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.7.6", + "@rspack/binding-darwin-x64": "1.7.6", + "@rspack/binding-linux-arm64-gnu": "1.7.6", + "@rspack/binding-linux-arm64-musl": "1.7.6", + "@rspack/binding-linux-x64-gnu": "1.7.6", + "@rspack/binding-linux-x64-musl": "1.7.6", + "@rspack/binding-wasm32-wasi": "1.7.6", + "@rspack/binding-win32-arm64-msvc": "1.7.6", + "@rspack/binding-win32-ia32-msvc": "1.7.6", + "@rspack/binding-win32-x64-msvc": "1.7.6" + } + }, + "node_modules/@rspack/binding-darwin-arm64": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.7.6.tgz", + "integrity": "sha512-NZ9AWtB1COLUX1tA9HQQvWpTy07NSFfKBU8A6ylWd5KH8AePZztpNgLLAVPTuNO4CZXYpwcoclf8jG/luJcQdQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rspack/binding-darwin-x64": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.7.6.tgz", + "integrity": "sha512-J2g6xk8ZS7uc024dNTGTHxoFzFovAZIRixUG7PiciLKTMP78svbSSWrmW6N8oAsAkzYfJWwQpVgWfFNRHvYxSw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.7.6.tgz", + "integrity": "sha512-eQfcsaxhFrv5FmtaA7+O1F9/2yFDNIoPZzV/ZvqvFz5bBXVc4FAm/1fVpBg8Po/kX1h0chBc7Xkpry3cabFW8w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.7.6.tgz", + "integrity": "sha512-DfQXKiyPIl7i1yECHy4eAkSmlUzzsSAbOjgMuKn7pudsWf483jg0UUYutNgXSlBjc/QSUp7906Cg8oty9OfwPA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.7.6.tgz", + "integrity": "sha512-NdA+2X3lk2GGrMMnTGyYTzM3pn+zNjaqXqlgKmFBXvjfZqzSsKq3pdD1KHZCd5QHN+Fwvoszj0JFsquEVhE1og==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.7.6.tgz", + "integrity": "sha512-rEy6MHKob02t/77YNgr6dREyJ0e0tv1X6Xsg8Z5E7rPXead06zefUbfazj4RELYySWnM38ovZyJAkPx/gOn3VA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-wasm32-wasi": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.7.6.tgz", + "integrity": "sha512-YupOrz0daSG+YBbCIgpDgzfMM38YpChv+afZpaxx5Ml7xPeAZIIdgWmLHnQ2rts73N2M1NspAiBwV00Xx0N4Vg==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "1.0.7" + } + }, + "node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.7.6.tgz", + "integrity": "sha512-INj7aVXjBvlZ84kEhSK4kJ484ub0i+BzgnjDWOWM1K+eFYDZjLdAsQSS3fGGXwVc3qKbPIssFfnftATDMTEJHQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.7.6.tgz", + "integrity": "sha512-lXGvC+z67UMcw58In12h8zCa9IyYRmuptUBMItQJzu+M278aMuD1nETyGLL7e4+OZ2lvrnnBIcjXN1hfw2yRzw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.7.6.tgz", + "integrity": "sha512-zeUxEc0ZaPpmaYlCeWcjSJUPuRRySiSHN23oJ2Xyw0jsQ01Qm4OScPdr0RhEOFuK/UE+ANyRtDo4zJsY52Hadw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/core": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.7.6.tgz", + "integrity": "sha512-Iax6UhrfZqJajA778c1d5DBFbSIqPOSrI34kpNIiNpWd8Jq7mFIa+Z60SQb5ZQDZuUxcCZikjz5BxinFjTkg7Q==", + "license": "MIT", + "dependencies": { + "@module-federation/runtime-tools": "0.22.0", + "@rspack/binding": "1.7.6", + "@rspack/lite-tapable": "1.1.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@rspack/lite-tapable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.1.0.tgz", + "integrity": "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==", + "license": "MIT" + }, + "node_modules/@rspack/plugin-react-refresh": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.1.tgz", + "integrity": "sha512-eqqW5645VG3CzGzFgNg5HqNdHVXY+567PGjtDhhrM8t67caxmsSzRmT5qfoEIfBcGgFkH9vEg7kzXwmCYQdQDw==", + "license": "MIT", + "dependencies": { + "error-stack-parser": "^2.1.4", + "html-entities": "^2.6.0" + }, + "peerDependencies": { + "react-refresh": ">=0.10.0 <1.0.0", + "webpack-hot-middleware": "2.x" + }, + "peerDependenciesMeta": { + "webpack-hot-middleware": { + "optional": true + } + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/dom-mediacapture-transform": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@types/dom-mediacapture-transform/-/dom-mediacapture-transform-0.1.11.tgz", + "integrity": "sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ==", + "license": "MIT", + "dependencies": { + "@types/dom-webcodecs": "*" + } + }, + "node_modules/@types/dom-webcodecs": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz", + "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==", + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.0.tgz", + "integrity": "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "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/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz", + "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.40", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.6.3" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/electron-to-chromium": { + "version": "1.5.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.363.tgz", + "integrity": "sha512-VjUKPyWzGnT1fujlkEGC/BvN70Hh70KXtAqcmniXviYlJC/ivcT+BWGPyxWVbJZLfvtKR6dqg1L7T7pgAMBtWA==", + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz", + "integrity": "sha512-xYcDWrpELkFzz9SpZ3PlI6Eu6eD93Yf0WLDRxikGhWJ3MAir2SNZTIVCVZqZ/NUyx8AdMc2gT9C0gPiw18kG+A==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "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/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "license": "Unlicense" + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "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-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/loader-runner": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mediabunny": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.45.0.tgz", + "integrity": "sha512-oK3sMMYbucoF6LUX62L/2M9d+p9ve6KDQgL87kNfhsB0/XmTe9iRLUcgQgg9Gpgvi8Sb96zYfOUL6i17y0bdNg==", + "license": "MPL-2.0", + "workspaces": [ + ".", + "packages/*" + ], + "dependencies": { + "@types/dom-mediacapture-transform": "^0.1.11", + "@types/dom-webcodecs": "0.1.13" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Vanilagy" + } + }, + "node_modules/memfs": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", + "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", + "license": "Unlicense", + "dependencies": { + "fs-monkey": "1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "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-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "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/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remotion": { + "version": "4.0.468", + "resolved": "https://registry.npmjs.org/remotion/-/remotion-4.0.468.tgz", + "integrity": "sha512-TPfoy8XZgKDgZDRseAiLpWfLUdix3+d6pL7eh9myPBW2ajdb6tAPzTd9BBKMp/9j+Ajpc5DZVqKuGQPHmBkGQA==", + "license": "SEE LICENSE IN LICENSE.md", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "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/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", + "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "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/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "license": "MIT" + }, + "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/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "license": "BSD-2-Clause" + }, + "node_modules/webpack": { + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz", + "integrity": "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/remotion/package.json b/remotion/package.json new file mode 100644 index 0000000..ecacb19 --- /dev/null +++ b/remotion/package.json @@ -0,0 +1,22 @@ +{ + "name": "@ado2/higgsfield-remotion", + "version": "0.1.0", + "private": true, + "description": "ADO2 Higgsfield Shorts — Remotion 자막·타이틀 오버레이 합성", + "scripts": { + "studio": "remotion studio", + "render": "remotion render MumumShort out/mumum-poc2-final.mp4", + "upgrade": "remotion upgrade" + }, + "dependencies": { + "@remotion/cli": "4.0.468", + "@remotion/google-fonts": "4.0.468", + "remotion": "4.0.468", + "react": "19.2.0", + "react-dom": "19.2.0" + }, + "devDependencies": { + "@types/react": "19.2.0", + "typescript": "5.7.3" + } +} diff --git a/remotion/public/mumum_marketing_v1.mp4 b/remotion/public/mumum_marketing_v1.mp4 new file mode 100644 index 0000000..04c27f7 Binary files /dev/null and b/remotion/public/mumum_marketing_v1.mp4 differ diff --git a/remotion/remotion.config.ts b/remotion/remotion.config.ts new file mode 100644 index 0000000..a0087e8 --- /dev/null +++ b/remotion/remotion.config.ts @@ -0,0 +1,6 @@ +import { Config } from "@remotion/cli/config"; + +Config.setVideoImageFormat("jpeg"); +Config.setOverwriteOutput(true); +// H.264 + AAC, 모바일 9:16 Shorts/Reels 호환 +Config.setCodec("h264"); diff --git a/remotion/src/MumumShort.tsx b/remotion/src/MumumShort.tsx new file mode 100644 index 0000000..c9484bc --- /dev/null +++ b/remotion/src/MumumShort.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { AbsoluteFill, OffthreadVideo, Sequence, staticFile } from "remotion"; +import { ShortProps } from "./data/types"; +import { HookTitle } from "./components/HookTitle"; +import { BrandLines } from "./components/BrandLines"; +import { SellingBadge } from "./components/SellingBadge"; +import { EndCard } from "./components/EndCard"; + +// Higgsfield 완성본을 배경으로 깔고(오디오 포함), 자막/타이틀만 오버레이. +// 모든 콘텐츠는 props로 주입 (펜션/제품마다 교체). +export const MumumShort: React.FC = ({ + videoSrc, + hook, + sellingPoint, + brandLines, + endCard, +}) => { + return ( + + + + {/* 상단 후킹 타이틀 */} + + + + + {/* 셀링포인트: 3개 독립 박스 */} + + + {/* 브랜드 감성 2줄 (상단 중앙) */} + + + {/* 엔드카드 + AI 디스클로저 */} + + + ); +}; diff --git a/remotion/src/Root.tsx b/remotion/src/Root.tsx new file mode 100644 index 0000000..85499b5 --- /dev/null +++ b/remotion/src/Root.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { Composition } from "remotion"; +import { MumumShort } from "./MumumShort"; +import { defaultProps } from "./data/mumum"; + +export const RemotionRoot: React.FC = () => { + return ( + ({ + durationInFrames: props.durationInFrames, + fps: props.fps, + width: props.width, + height: props.height, + })} + /> + ); +}; diff --git a/remotion/src/components/BrandLines.tsx b/remotion/src/components/BrandLines.tsx new file mode 100644 index 0000000..badbe55 --- /dev/null +++ b/remotion/src/components/BrandLines.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { useCurrentFrame, interpolate, AbsoluteFill } from "remotion"; +import { serifFont } from "../fonts"; + +// 브랜드 감성 2줄: 우상단 통일 위치에 한 덩어리로 쌓아 노출. +// 첫 줄 먼저, 둘째 줄 살짝 뒤따라 등장 → 같은 자리에서 읽힘. +export const BrandLines: React.FC<{ + lines: string[]; + fromFrame: number; + toFrame: number; +}> = ({ lines, fromFrame, toFrame }) => { + const frame = useCurrentFrame(); + const LINE_STAGGER = 18; // 줄 간 등장 간격(frame) + + return ( + + {lines.map((line, i) => { + const start = fromFrame + i * LINE_STAGGER; + const opacity = interpolate( + frame, + [start, start + 12, toFrame - 12, toFrame], + [0, 1, 1, 0], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" }, + ); + const rise = interpolate(frame, [start, start + 16], [16, 0], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + return ( +
+ {line} +
+ ); + })} +
+ ); +}; diff --git a/remotion/src/components/EndCard.tsx b/remotion/src/components/EndCard.tsx new file mode 100644 index 0000000..f61cd92 --- /dev/null +++ b/remotion/src/components/EndCard.tsx @@ -0,0 +1,91 @@ +import React from "react"; +import { + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + AbsoluteFill, +} from "remotion"; +import { serifFont } from "../fonts"; + +// 엔드카드: 브랜드 + 위치 + AI 디스클로저 (가드레일 필수) +export const EndCard: React.FC<{ + brand: string; + location: string; + disclosure: string; + fromFrame: number; +}> = ({ brand, location, disclosure, fromFrame }) => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const appear = interpolate(frame, [fromFrame, fromFrame + 12], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + const brandSpring = spring({ + frame: frame - fromFrame, + fps, + config: { damping: 200, mass: 0.5 }, + }); + const brandScale = interpolate(brandSpring, [0, 1], [0.92, 1]); + + return ( + +
+
+ {brand} +
+
+ {location} +
+
+
+ {disclosure} +
+
+ ); +}; diff --git a/remotion/src/components/HookTitle.tsx b/remotion/src/components/HookTitle.tsx new file mode 100644 index 0000000..5ee3fe8 --- /dev/null +++ b/remotion/src/components/HookTitle.tsx @@ -0,0 +1,90 @@ +import React from "react"; +import { + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + AbsoluteFill, +} from "remotion"; +import { hookFont } from "../fonts"; + +export const HookTitle: React.FC<{ + eyebrow: string; + title: string; + fromFrame: number; + toFrame: number; +}> = ({ eyebrow, title, fromFrame, toFrame }) => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + // 스프링 슬라이드-다운 등장 + const enter = spring({ + frame: frame - fromFrame, + fps, + config: { damping: 200, mass: 0.6 }, + }); + const translateY = interpolate(enter, [0, 1], [-60, 0]); + // 퇴장 페이드 + const opacity = interpolate( + frame, + [fromFrame, fromFrame + 8, toFrame - 12, toFrame], + [0, 1, 1, 0], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" }, + ); + + return ( + + {/* 상단 가독성 그라데이션 */} +
+
+
+ {eyebrow} +
+
+ {title} +
+
+ + ); +}; diff --git a/remotion/src/components/SellingBadge.tsx b/remotion/src/components/SellingBadge.tsx new file mode 100644 index 0000000..ea3e657 --- /dev/null +++ b/remotion/src/components/SellingBadge.tsx @@ -0,0 +1,69 @@ +import React from "react"; +import { + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + AbsoluteFill, +} from "remotion"; +import { serifFont } from "../fonts"; + +// 셀링포인트: 3개 독립 pill 박스를 세로로 쌓고 순차 등장. +// 컬러 지양 → 반투명 다크 pill + 흰색 텍스트. 앞 구간 단독 노출(시선 집중). +export const SellingBadge: React.FC<{ + items: string[]; + fromFrame: number; + toFrame: number; +}> = ({ items, fromFrame, toFrame }) => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + const STAGGER = 9; // 박스 간 등장 간격(frame) + + return ( + + {items.map((item, i) => { + const start = fromFrame + i * STAGGER; + const enter = spring({ + frame: frame - start, + fps, + config: { damping: 200, mass: 0.5 }, + }); + const rise = interpolate(enter, [0, 1], [22, 0]); + const appear = interpolate( + frame, + [start, start + 8, toFrame - 10, toFrame], + [0, 1, 1, 0], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" }, + ); + return ( +
+ {item} +
+ ); + })} +
+ ); +}; diff --git a/remotion/src/components/Subtitle.tsx b/remotion/src/components/Subtitle.tsx new file mode 100644 index 0000000..aa09d86 --- /dev/null +++ b/remotion/src/components/Subtitle.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { + useCurrentFrame, + interpolate, + AbsoluteFill, +} from "remotion"; +import { serifFont } from "../fonts"; + +// 브랜드 자막: 페이드 + 미세 상승 (Profile A 모션 프리셋) +export const Subtitle: React.FC<{ + text: string; + fromFrame: number; + toFrame: number; +}> = ({ text, fromFrame, toFrame }) => { + const frame = useCurrentFrame(); + + const opacity = interpolate( + frame, + [fromFrame, fromFrame + 10, toFrame - 10, toFrame], + [0, 1, 1, 0], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" }, + ); + const rise = interpolate(frame, [fromFrame, fromFrame + 18], [18, 0], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + return ( + +
+ {text} +
+
+ ); +}; diff --git a/remotion/src/data/mumum.ts b/remotion/src/data/mumum.ts new file mode 100644 index 0000000..ad44444 --- /dev/null +++ b/remotion/src/data/mumum.ts @@ -0,0 +1,35 @@ +// Default props = the verified 머뭄 PoC #2 layout. +// The Python render bridge overrides these per-job via --props. +import { ShortProps } from "./types"; + +export const defaultProps: ShortProps = { + videoSrc: "mumum_marketing_v1.mp4", + fps: 30, + width: 720, + height: 1280, + durationInFrames: 241, // ≈ 8.04s + + hook: { + eyebrow: "아무도 모르는 군산", + title: "골목 안, 단 한 채의 독채 스테이 머뭄", + fromFrame: 0, + toFrame: 78, + }, + sellingPoint: { + items: ["프라이빗 독채펜션", "넓은 정원", "핵심 관광지 가까이"], + fromFrame: 80, + toFrame: 138, + }, + brandLines: { + lines: ["머무는 시간이", "다르게 흐르는 곳"], + fromFrame: 138, + toFrame: 196, + }, + endCard: { + brand: "Stay, 머뭄", + location: "전북 군산시 절골길 18", + disclosure: "실제 사진 기반, AI 카메라 효과를 적용한 영상입니다.", + fromFrame: 192, + toFrame: 241, + }, +}; diff --git a/remotion/src/data/types.ts b/remotion/src/data/types.ts new file mode 100644 index 0000000..bcc7c26 --- /dev/null +++ b/remotion/src/data/types.ts @@ -0,0 +1,19 @@ +// Props contract — shared by Remotion composition and the Python render bridge. +// Mirrors server/app/schemas.py VideoSpec (+ video config + timings). +export type ShortProps = { + videoSrc: string; // file in public/ (staticFile name) + fps: number; + width: number; + height: number; + durationInFrames: number; + hook: { eyebrow: string; title: string; fromFrame: number; toFrame: number }; + sellingPoint: { items: string[]; fromFrame: number; toFrame: number }; + brandLines: { lines: string[]; fromFrame: number; toFrame: number }; + endCard: { + brand: string; + location: string; + disclosure: string; + fromFrame: number; + toFrame: number; + }; +}; diff --git a/remotion/src/fonts.ts b/remotion/src/fonts.ts new file mode 100644 index 0000000..038d830 --- /dev/null +++ b/remotion/src/fonts.ts @@ -0,0 +1,15 @@ +// 한글 폰트 로딩 (@remotion/google-fonts, korean subset) +// Hook = Black Han Sans (굵고 강렬한 바이럴 헤드라인) +// Body/Brand = Nanum Myeongjo (우아한 명조 — 머뭄의 정적 브랜드 톤) +import { loadFont as loadHook } from "@remotion/google-fonts/BlackHanSans"; +import { loadFont as loadSerif } from "@remotion/google-fonts/NanumMyeongjo"; + +export const hookFont = loadHook("normal", { + weights: ["400"], + subsets: ["korean", "latin"], +}).fontFamily; + +export const serifFont = loadSerif("normal", { + weights: ["400", "700", "800"], + subsets: ["korean", "latin"], +}).fontFamily; diff --git a/remotion/src/index.ts b/remotion/src/index.ts new file mode 100644 index 0000000..f31c790 --- /dev/null +++ b/remotion/src/index.ts @@ -0,0 +1,4 @@ +import { registerRoot } from "remotion"; +import { RemotionRoot } from "./Root"; + +registerRoot(RemotionRoot); diff --git a/remotion/tsconfig.json b/remotion/tsconfig.json new file mode 100644 index 0000000..fea0181 --- /dev/null +++ b/remotion/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true + }, + "include": ["src"] +} diff --git a/server/.env.example b/server/.env.example new file mode 100644 index 0000000..f3eefc4 --- /dev/null +++ b/server/.env.example @@ -0,0 +1,6 @@ +# ADO2 Higgsfield Shorts server +# Claude API (spec_builder) +ANTHROPIC_API_KEY= + +# Higgsfield CLI must be authenticated separately: `higgsfield auth login` +# (the server shells out to the CLI; no key needed here) diff --git a/server/app/__init__.py b/server/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/app/main.py b/server/app/main.py new file mode 100644 index 0000000..15b6136 --- /dev/null +++ b/server/app/main.py @@ -0,0 +1,115 @@ +"""④ FastAPI orchestration — LLM → Higgsfield → Remotion wrapper. + +POST /api/generate (multipart: photos[] + interview fields) + → build_spec (Claude) → higgsfield.generate → remotion.render → mp4 +GET /api/health +Static: /outputs/ (final videos), / (the web app) +""" +from __future__ import annotations + +import shutil +import uuid +from pathlib import Path + +from fastapi import FastAPI, File, Form, UploadFile, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import FileResponse +from fastapi.staticfiles import StaticFiles + +from app.schemas import GenerateRequest, GenerateResult, ScriptResult +from app.pipeline import spec_builder, higgsfield_client, remotion_render + +ENGINE = Path(__file__).resolve().parents[2] # engine/higgsfield_shorts +WEBAPP = ENGINE / "webapp" +OUTPUTS = ENGINE / "server" / "outputs" +UPLOADS = ENGINE / "server" / ".uploads" +OUTPUTS.mkdir(parents=True, exist_ok=True) + +app = FastAPI(title="ADO2 Higgsfield Shorts") +app.add_middleware( + CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], +) + + +@app.get("/api/health") +def health(): + return {"ok": True} + + +@app.post("/api/captions", response_model=ScriptResult) +def captions(req: GenerateRequest): + """인터뷰 답변 → 인트로·셀링포인트·감성스토리·CTA 4블록 (Claude).""" + try: + return spec_builder.build_script(req) + except Exception as e: + raise HTTPException(502, f"자막 생성 실패: {e}") from e + + +@app.post("/api/generate", response_model=GenerateResult) +async def generate( + photos: list[UploadFile] = File(...), + kind: str = Form(...), + biz_name: str = Form(...), + selling: str = Form(...), + addr: str | None = Form(None), + price: str | None = Form(None), + dry_run: bool = Form(False), +): + if len(photos) < 4: + raise HTTPException(400, "사진 4장 이상이 필요합니다.") + + # 1. Persist uploaded photos + job = uuid.uuid4().hex[:8] + job_dir = UPLOADS / job + job_dir.mkdir(parents=True, exist_ok=True) + photo_paths: list[Path] = [] + for i, up in enumerate(photos): + dest = job_dir / f"{i:02d}_{Path(up.filename or 'img').name}" + with dest.open("wb") as f: + shutil.copyfileobj(up.file, f) + photo_paths.append(dest) + + req = GenerateRequest(kind=kind, biz_name=biz_name, addr=addr, price=price, selling=selling) + + # 2. LLM → spec + try: + spec = spec_builder.build_spec(req) + except Exception as e: # surface as a clean 502, don't swallow + raise HTTPException(502, f"spec 생성 실패: {e}") from e + + # 3. Higgsfield → base video + try: + base_video, credits = higgsfield_client.generate( + spec.higgsfield_prompt, photo_paths, dry_run=dry_run + ) + except Exception as e: + raise HTTPException(502, f"Higgsfield 생성 실패: {e}") from e + + # 4. Remotion → final with overlays + try: + final = remotion_render.render(spec, base_video) + except Exception as e: + raise HTTPException(502, f"Remotion 합성 실패: {e}") from e + + served = OUTPUTS / f"{job}.mp4" + shutil.copy(final, served) + + return GenerateResult( + video_url=f"/outputs/{job}.mp4", + caption=spec.caption, + profile=spec.profile, + cost_credits=credits, + job_id=job, + ) + + +# Static mounts (after API routes) +app.mount("/outputs", StaticFiles(directory=str(OUTPUTS)), name="outputs") + + +@app.get("/") +def index(): + return FileResponse(str(WEBAPP / "index.html")) + + +app.mount("/", StaticFiles(directory=str(WEBAPP)), name="webapp") diff --git a/server/app/pipeline/__init__.py b/server/app/pipeline/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/app/pipeline/higgsfield_client.py b/server/app/pipeline/higgsfield_client.py new file mode 100644 index 0000000..b8da8f9 --- /dev/null +++ b/server/app/pipeline/higgsfield_client.py @@ -0,0 +1,70 @@ +"""② Higgsfield step — photos + prompt → completed 8s base video. + +Thin wrapper around the verified CLI flow: + higgsfield generate cost/create marketing_studio_video --mode tv_spot ... +dry_run=True returns a bundled demo clip and spends no credits. +""" +from __future__ import annotations + +import json +import re +import subprocess +import urllib.request +import uuid +from pathlib import Path + +ENGINE = Path(__file__).resolve().parents[3] # engine/higgsfield_shorts +DEMO_VIDEO = ENGINE / "webapp" / "demo" / "mumum.mp4" +TMP = ENGINE / "server" / ".tmp" + +MODEL = "marketing_studio_video" +_URL_RE = re.compile(r'"result_url"\s*:\s*"([^"]+)"') +_CREDITS_RE = re.compile(r'"credits(?:_exact)?"\s*:\s*([0-9.]+)') + + +def _base_args(prompt: str, image_paths: list[Path], duration: int) -> list[str]: + args = ["--prompt", prompt] + for p in image_paths: + args += ["--image", str(p)] + args += ["--mode", "tv_spot", "--duration", str(duration), + "--generate_audio", "true", "--aspect_ratio", "9:16"] + return args + + +def cost(prompt: str, image_paths: list[Path], duration: int = 8) -> float: + out = subprocess.run( + ["higgsfield", "generate", "cost", MODEL, *_base_args(prompt, image_paths, duration), "--json"], + capture_output=True, text=True, check=True, + ) + m = _CREDITS_RE.search(out.stdout) + return float(m.group(1)) if m else 0.0 + + +def generate( + prompt: str, + image_paths: list[Path], + duration: int = 8, + dry_run: bool = False, + wait_timeout: str = "15m", +) -> tuple[Path, float]: + """Return (local mp4 path, credits spent).""" + if dry_run: + return DEMO_VIDEO, 0.0 + + out = subprocess.run( + ["higgsfield", "generate", "create", MODEL, + *_base_args(prompt, image_paths, duration), + "--wait", "--wait-timeout", wait_timeout, "--json"], + capture_output=True, text=True, check=True, + ) + m = _URL_RE.search(out.stdout) + if not m: + raise RuntimeError(f"Higgsfield returned no result_url:\n{out.stdout[-2000:]}") + url = m.group(1) + credits_m = _CREDITS_RE.search(out.stdout) + credits = float(credits_m.group(1)) if credits_m else 0.0 + + TMP.mkdir(parents=True, exist_ok=True) + dest = TMP / f"base_{uuid.uuid4().hex[:8]}.mp4" + urllib.request.urlretrieve(url, dest) + return dest, credits diff --git a/server/app/pipeline/remotion_render.py b/server/app/pipeline/remotion_render.py new file mode 100644 index 0000000..d184550 --- /dev/null +++ b/server/app/pipeline/remotion_render.py @@ -0,0 +1,90 @@ +"""③ Remotion step — VideoSpec + base video → final mp4 with overlays. + +Generalized: builds a props JSON (video config + default timings + spec content) +and runs `npx remotion render MumumShort --props=`. The base video is +copied into remotion/public/ so OffthreadVideo's staticFile() can resolve it. +""" +from __future__ import annotations + +import json +import shutil +import subprocess +import uuid +from pathlib import Path + +from app.schemas import VideoSpec + +ENGINE = Path(__file__).resolve().parents[3] # engine/higgsfield_shorts +REMOTION = ENGINE / "remotion" +PUBLIC = REMOTION / "public" +OUT_DIR = REMOTION / "out" + +FPS = 30 + + +def _probe_frames(video_path: Path, fps: int = FPS) -> tuple[int, int, int]: + """Return (durationInFrames, width, height) via ffprobe.""" + out = subprocess.run( + ["ffprobe", "-v", "error", "-select_streams", "v:0", + "-show_entries", "stream=width,height,duration", + "-of", "json", str(video_path)], + capture_output=True, text=True, check=True, + ) + info = json.loads(out.stdout)["streams"][0] + width = int(info["width"]) + height = int(info["height"]) + seconds = float(info.get("duration", 8.0)) + return max(1, round(seconds * fps)), width, height + + +def _timings(total: int) -> dict: + """Default 4-beat layout, clamped to the actual video length.""" + def clamp(f: int) -> int: + return max(0, min(f, total)) + return { + "hook": {"fromFrame": 0, "toFrame": clamp(78)}, + "sellingPoint": {"fromFrame": clamp(80), "toFrame": clamp(138)}, + "brandLines": {"fromFrame": clamp(138), "toFrame": clamp(196)}, + "endCard": {"fromFrame": clamp(total - 49), "toFrame": total}, + } + + +def build_props(spec: VideoSpec, base_video: Path) -> tuple[dict, str]: + """Copy base video into public/ and assemble the Remotion props dict.""" + PUBLIC.mkdir(parents=True, exist_ok=True) + job = f"job_{uuid.uuid4().hex[:8]}.mp4" + shutil.copy(base_video, PUBLIC / job) + + frames, width, height = _probe_frames(PUBLIC / job) + t = _timings(frames) + + props = { + "videoSrc": job, + "fps": FPS, "width": width, "height": height, "durationInFrames": frames, + "hook": {"eyebrow": spec.hook.eyebrow, "title": spec.hook.title, **t["hook"]}, + "sellingPoint": {"items": spec.selling_point.items, **t["sellingPoint"]}, + "brandLines": {"lines": spec.brand_lines, **t["brandLines"]}, + "endCard": { + "brand": spec.end_card.brand, + "location": spec.end_card.location, + "disclosure": spec.end_card.disclosure, + **t["endCard"], + }, + } + return props, job + + +def render(spec: VideoSpec, base_video: Path) -> Path: + OUT_DIR.mkdir(parents=True, exist_ok=True) + props, _ = build_props(spec, base_video) + + props_file = OUT_DIR / f"props_{uuid.uuid4().hex[:8]}.json" + props_file.write_text(json.dumps(props, ensure_ascii=False), encoding="utf-8") + + out_file = OUT_DIR / f"final_{uuid.uuid4().hex[:8]}.mp4" + subprocess.run( + ["npx", "remotion", "render", "MumumShort", str(out_file), + f"--props={props_file}"], + cwd=str(REMOTION), check=True, + ) + return out_file diff --git a/server/app/pipeline/spec_builder.py b/server/app/pipeline/spec_builder.py new file mode 100644 index 0000000..5321140 --- /dev/null +++ b/server/app/pipeline/spec_builder.py @@ -0,0 +1,171 @@ +"""① LLM step — interview answers → VideoSpec (JSON). + +Uses the Anthropic Python SDK (claude-opus-4-7) with structured outputs so the +result is guaranteed to match the Remotion data contract. The guardrail system +prompt is cached (stable prefix) to cut cost/latency across requests. +""" +from __future__ import annotations + +import json +import anthropic + +from app.schemas import GenerateRequest, VideoSpec, ScriptResult + +MODEL = "claude-opus-4-7" + +# Stable, cacheable guardrail + role prompt. +SYSTEM_PROMPT = """\ +당신은 한국 로컬 비즈니스(숙소·매장·제품)의 8초 세로형 숏폼 광고 카피 디렉터입니다. +사장/마케터가 직접 답한 정보만으로, 영상 사양(VideoSpec)을 설계합니다. 복잡한 분석은 하지 않습니다. + +[절대 가드레일] +1. 식민지 유산·역사정치 민감 용어 금지: "적산가옥", "일제", "식민지", "근대사" 등 → 미학·경험 어휘로 대체. +2. 거짓·과장 금지: 사용자가 주지 않은 거리(예: "서울 40분")·효능·수치를 지어내지 않는다. 주어진 정보만 사용. +3. 자막은 흰색 모노크롬 전제 — 카피에 색 지시어를 넣지 않는다. +4. AI 디스클로저는 end_card.disclosure에 고정 문구로 포함. + +[hook 작성 — 레퍼런스 바이럴 공식 "[반전/호기심] + [한방]"] +- eyebrow: 짧은 도발/맥락 한 줄 (예: "아무도 모르는 군산"). +- title: 굵은 한방 헤드라인. 사용자의 셀링포인트를 가장 강하게 압축. + +[selling_point.items] 정확히 3개. 사용자 한방 셀링포인트 + 유형 기반의 구체 명사구(짧게). 이모지 금지. + +[brand_lines] 감성 2줄 (각 줄 짧게). 한 덩어리로 읽히게. + +[end_card] brand=업체/상품명, location=주소나 URL(없으면 빈 문자열 ""), disclosure 고정. + +[caption] 업로드용. 정보(업체/주소/가격) + 짧은 감성 + 해시태그 5~8개. 유형에 맞는 해시태그. + +[profile 선택] +- place + 정적/프라이빗/힐링 → "Still Cinema" +- place + 뷰/활동성/포토스팟, 또는 product 일반 → "Rhythm Reveal" +- 파티/워터파크/대형 액티비티 → "Maximum Viral" + +[higgsfield_prompt] 영문. marketing_studio_video tv_spot용. 빠르고 역동적인 카메라(quick punchy moves, crash zoom, kinetic glide). 유형 반영: place=공간 투어, product=제품 쇼케이스. 반드시 "keep structure realistic, do not distort" 포함. +""" + +# Structured-output JSON schema (additionalProperties:false everywhere). +_SPEC_SCHEMA = { + "type": "object", + "additionalProperties": False, + "properties": { + "profile": {"type": "string", "enum": ["Still Cinema", "Rhythm Reveal", "Maximum Viral"]}, + "higgsfield_prompt": {"type": "string"}, + "hook": { + "type": "object", + "additionalProperties": False, + "properties": {"eyebrow": {"type": "string"}, "title": {"type": "string"}}, + "required": ["eyebrow", "title"], + }, + "selling_point": { + "type": "object", + "additionalProperties": False, + "properties": {"items": {"type": "array", "items": {"type": "string"}}}, + "required": ["items"], + }, + "brand_lines": {"type": "array", "items": {"type": "string"}}, + "end_card": { + "type": "object", + "additionalProperties": False, + "properties": { + "brand": {"type": "string"}, + "location": {"type": "string"}, + "disclosure": {"type": "string"}, + }, + "required": ["brand", "location", "disclosure"], + }, + "caption": {"type": "string"}, + }, + "required": [ + "profile", "higgsfield_prompt", "hook", "selling_point", + "brand_lines", "end_card", "caption", + ], +} + + +def build_spec(req: GenerateRequest, client: anthropic.Anthropic | None = None) -> VideoSpec: + client = client or anthropic.Anthropic() + + user_msg = ( + f"유형: {'장소' if req.kind == 'place' else '메시지(축하·안부·홍보)' if req.kind == 'message' else '물건(제품·음식 등 정적 아이템)'}\n" + f"업체/상품명: {req.biz_name}\n" + f"주소/판매URL: {req.addr or '(없음)'}\n" + f"가격: {req.price or '(없음)'}\n" + f"강력한 한방 셀링포인트: {req.selling}\n\n" + "위 정보로 VideoSpec을 설계해줘." + ) + + resp = client.messages.create( + model=MODEL, + max_tokens=2048, + thinking={"type": "adaptive"}, + output_config={ + "effort": "medium", + "format": {"type": "json_schema", "name": "video_spec", "schema": _SPEC_SCHEMA}, + }, + system=[{ + "type": "text", + "text": SYSTEM_PROMPT, + "cache_control": {"type": "ephemeral"}, + }], + messages=[{"role": "user", "content": user_msg}], + ) + + text = "".join(b.text for b in resp.content if b.type == "text") + return VideoSpec.model_validate(json.loads(text)) + + +# ---------- 자막 스크립트 4블록 ---------- +SCRIPT_SYSTEM = """\ +당신은 한국 로컬 비즈니스 숏폼 광고의 자막 스크립트 작가입니다. +사장/마케터가 답한 정보로, 영상에 얹을 4개 블록을 만듭니다. + +[4블록] +- intro: 첫 1~2초 후킹. 호기심·반전을 거는 짧은 한 줄. +- selling: 가장 강력한 셀링포인트를 한 문장으로 압축. +- story: 감성 스토리. 고객이 그 공간/제품에서 느낄 감정을 1~2문장으로. +- cta: 행동 유도. 예약·구매·방문으로 자연스럽게. + +[가드레일] +- 거짓·과장 금지(주어지지 않은 거리·효능·수치 X). 주어진 정보만. +- 식민지 유산 어휘(적산가옥/일제/식민지 등) 금지 → 미학·경험 어휘로. +- 자막은 흰색 모노크롬 전제 — 색 지시어 넣지 않음. 이모지 남발 금지. +- 각 블록은 짧고 입에 붙게. 한국어. +""" + +_SCRIPT_SCHEMA = { + "type": "object", + "additionalProperties": False, + "properties": { + "intro": {"type": "string"}, + "selling": {"type": "string"}, + "story": {"type": "string"}, + "cta": {"type": "string"}, + }, + "required": ["intro", "selling", "story", "cta"], +} + + +def build_script(req: GenerateRequest, client: anthropic.Anthropic | None = None) -> ScriptResult: + client = client or anthropic.Anthropic() + user_msg = ( + f"유형: {'장소' if req.kind == 'place' else '메시지(축하·안부·홍보)' if req.kind == 'message' else '물건(제품·음식 등 정적 아이템)'}\n" + f"업체/상품명: {req.biz_name}\n" + f"주소/판매URL: {req.addr or '(없음)'}\n" + f"가격: {req.price or '(없음)'}\n" + f"강력한 한방 셀링포인트: {req.selling}\n\n" + "위 정보로 4블록 자막 스크립트를 만들어줘." + ) + resp = client.messages.create( + model=MODEL, + max_tokens=1024, + thinking={"type": "adaptive"}, + output_config={ + "effort": "low", + "format": {"type": "json_schema", "name": "script", "schema": _SCRIPT_SCHEMA}, + }, + system=[{"type": "text", "text": SCRIPT_SYSTEM, "cache_control": {"type": "ephemeral"}}], + messages=[{"role": "user", "content": user_msg}], + ) + text = "".join(b.text for b in resp.content if b.type == "text") + return ScriptResult.model_validate(json.loads(text)) diff --git a/server/app/schemas.py b/server/app/schemas.py new file mode 100644 index 0000000..1db6d8c --- /dev/null +++ b/server/app/schemas.py @@ -0,0 +1,64 @@ +"""Pydantic schemas for the Higgsfield Shorts wrapper. + +VideoSpec mirrors the Remotion data contract (remotion/src/data/mumum.ts) 1:1. +The LLM (spec_builder) fills VideoSpec; Higgsfield consumes higgsfield_prompt; +Remotion consumes the rest (hook / selling_point / brand_lines / end_card). +""" +from __future__ import annotations + +from typing import Literal, Optional +from pydantic import BaseModel, Field + + +# ---------- Inbound: interview answers (no complex analysis) ---------- +class GenerateRequest(BaseModel): + kind: Literal["place", "product", "message"] + biz_name: str = Field(..., description="업체명·상품명·메시지 제목") + addr: Optional[str] = Field(None, description="주소 또는 판매 사이트 URL") + price: Optional[str] = Field(None, description="가격 정보") + selling: str = Field(..., description="주인/마케터가 생각하는 강력한 한방 셀링포인트") + + +# ---------- VideoSpec sub-objects (mirror mumum.ts) ---------- +class Hook(BaseModel): + eyebrow: str + title: str + + +class SellingPoint(BaseModel): + items: list[str] = Field(..., description="3개 독립 배지 카피") + + +class EndCard(BaseModel): + brand: str + location: str + disclosure: str = "실제 사진 기반, AI 카메라 효과를 적용한 영상입니다." + + +class VideoSpec(BaseModel): + # 에너지 프로파일 → Remotion 리듬/트랜지션 기본값 결정 + profile: Literal["Still Cinema", "Rhythm Reveal", "Maximum Viral"] + # Higgsfield marketing_studio_video 프롬프트 (유형별 톤) + higgsfield_prompt: str + hook: Hook + selling_point: SellingPoint + brand_lines: list[str] = Field(..., description="감성 카피 2줄") + end_card: EndCard + caption: str = Field(..., description="업로드용 캡션+해시태그") + + +# ---------- Outbound: 자막 스크립트 4블록 ---------- +class ScriptResult(BaseModel): + intro: str = Field(..., description="인트로 (후킹)") + selling: str = Field(..., description="셀링포인트") + story: str = Field(..., description="감성 스토리") + cta: str = Field(..., description="CTA") + + +# ---------- Outbound: final result ---------- +class GenerateResult(BaseModel): + video_url: str + caption: str + profile: str + cost_credits: float = 0.0 + job_id: Optional[str] = None diff --git a/server/pyproject.toml b/server/pyproject.toml new file mode 100644 index 0000000..fa296a1 --- /dev/null +++ b/server/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "ado2-higgsfield-server" +version = "0.1.0" +description = "ADO2 Higgsfield Shorts wrapper — LLM + Higgsfield + Remotion orchestration" +requires-python = ">=3.12" +dependencies = [ + "fastapi>=0.115", + "uvicorn[standard]>=0.30", + "python-multipart>=0.0.9", + "anthropic>=0.69", + "pydantic>=2.7", +] + +[tool.uvicorn] +# run: uv run uvicorn app.main:app --reload --port 8000 diff --git a/webapp/.gitignore b/webapp/.gitignore new file mode 100644 index 0000000..e985853 --- /dev/null +++ b/webapp/.gitignore @@ -0,0 +1 @@ +.vercel diff --git a/webapp/assets/ado2-logo-white.png b/webapp/assets/ado2-logo-white.png new file mode 100644 index 0000000..e3ceae5 Binary files /dev/null and b/webapp/assets/ado2-logo-white.png differ diff --git a/webapp/demo/mumum.mp4 b/webapp/demo/mumum.mp4 new file mode 100644 index 0000000..f0922fb Binary files /dev/null and b/webapp/demo/mumum.mp4 differ diff --git a/webapp/index.html b/webapp/index.html new file mode 100644 index 0000000..4886459 --- /dev/null +++ b/webapp/index.html @@ -0,0 +1,589 @@ + + + + +ADO2 Hookit — 8초 숏폼 생성기 + + + + +
+ + + + + + + + diff --git a/webapp/vercel.json b/webapp/vercel.json new file mode 100644 index 0000000..9ea00f5 --- /dev/null +++ b/webapp/vercel.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "cleanUrls": true, + "trailingSlash": false, + "headers": [ + { + "source": "/demo/(.*)", + "headers": [{ "key": "Cache-Control", "value": "public, max-age=86400" }] + }, + { + "source": "/assets/(.*)", + "headers": [{ "key": "Cache-Control", "value": "public, max-age=86400" }] + } + ] +}