From 9075b7d1bace9ac6493484cc053df9b50511cd30 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 17:40:26 -0800 Subject: [PATCH] =?UTF-8?q?Phase=200:=20=EB=AA=A8=EB=85=B8=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=20=EC=8A=A4=EC=BA=90=ED=8F=B4=EB=94=A9=20&=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=ED=99=98=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - frontend/ (Next.js 16 App Router + TS): layout/폰트, / → /dashboard 리다이렉트, styles/tokens.css·globals.css, lib/api.ts·types.ts, vitest 스모크, /api rewrites - backend/ (FastAPI + SQLite + Ollama): config/db, /api/health, /api/llm/health (Ollama on/off 양쪽 200), CORS, alembic init, pytest 2건 - 루트 Makefile/.gitignore/README 검증: pytest 2 passed, vitest 1 passed, ruff/eslint clean, next build OK, / → /dashboard 리다이렉트(Playwright), health 200, Ollama reachable, CORS 200 Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 22 + .../page-2026-06-14T01-38-55-253Z.yml | 7 + Makefile | 59 + README.md | 40 + backend/.env.example | 4 + backend/.python-version | 1 + backend/alembic.ini | 149 + backend/app/__init__.py | 0 backend/app/config.py | 21 + backend/app/db.py | 18 + backend/app/llm/__init__.py | 0 backend/app/llm/ollama.py | 33 + backend/app/main.py | 22 + backend/app/routers/__init__.py | 0 backend/app/routers/health.py | 9 + backend/app/routers/llm.py | 11 + backend/app/services/__init__.py | 0 backend/migrations/README | 1 + backend/migrations/env.py | 81 + backend/migrations/script.py.mako | 28 + backend/pyproject.toml | 35 + backend/tests/__init__.py | 0 backend/tests/conftest.py | 9 + backend/tests/test_health.py | 14 + backend/uv.lock | 1062 ++++ design-reference/HANDOFF-README.md | 25 + design-reference/PROJECT-README.md | 174 + design-reference/assets/approve-data.js | 56 + design-reference/assets/approve-page.jsx | 97 + design-reference/assets/approve.css | 213 + design-reference/assets/approve.jsx | 112 + design-reference/assets/auto-data.js | 105 + design-reference/assets/auto.css | 231 + design-reference/assets/auto.jsx | 267 + design-reference/assets/cal-data.js | 146 + design-reference/assets/cal-meet.jsx | 187 + design-reference/assets/cal.css | 429 ++ design-reference/assets/cal.jsx | 446 ++ design-reference/assets/dash-v2.jsx | 499 ++ design-reference/assets/dash.css | 800 +++ design-reference/assets/data.js | 145 + design-reference/assets/journey-clean-data.js | 77 + design-reference/assets/journey-clean.css | 236 + design-reference/assets/journey-clean.jsx | 369 ++ design-reference/assets/life-data.js | 132 + design-reference/assets/life.css | 587 ++ design-reference/assets/life.jsx | 675 +++ design-reference/assets/mail-data.js | 294 + design-reference/assets/mail.css | 722 +++ design-reference/assets/mail.jsx | 647 +++ design-reference/assets/notify-data.js | 146 + design-reference/assets/notify.css | 185 + design-reference/assets/notify.jsx | 260 + design-reference/assets/research-data.js | 88 + design-reference/assets/research.css | 216 + design-reference/assets/research.jsx | 304 + design-reference/assets/shell.jsx | 123 + design-reference/assets/sinbox-data.js | 48 + design-reference/assets/sinbox-page.jsx | 109 + design-reference/assets/sinbox.css | 165 + design-reference/assets/sinbox.jsx | 156 + design-reference/assets/tasks-data.js | 252 + design-reference/assets/tasks-risk.jsx | 126 + design-reference/assets/tasks.css | 860 +++ design-reference/assets/tasks.jsx | 848 +++ design-reference/assets/trip-data.js | 229 + design-reference/assets/trip.css | 333 ++ design-reference/assets/trip.jsx | 509 ++ design-reference/assets/wrap-data.js | 55 + design-reference/assets/wrap.css | 275 + design-reference/assets/wrap.jsx | 170 + design-reference/scraps/ai2.png | Bin 0 -> 12468 bytes design-reference/scraps/reader-ai.png | Bin 0 -> 76067 bytes design-reference/themes/lumi/Lumi People.html | 368 ++ design-reference/themes/lumi/README.md | 11 + design-reference/themes/lumi/ari-lumi.css | 1141 ++++ design-reference/themes/lumi/image-slot.js | 643 +++ design-reference/themes/lumi/people.css | 1009 ++++ .../lumi/아리 대시보드 라이트.html | 396 ++ design-reference/themes/ron/README.md | 19 + design-reference/themes/ron/dash-bento.css | 261 + design-reference/themes/ron/dash-ron.jsx | 372 ++ design-reference/themes/ron/data.js | 145 + design-reference/themes/ron/journey.css | 476 ++ design-reference/themes/ron/journey.jsx | 384 ++ design-reference/themes/ron/tasks-ron-data.js | 143 + design-reference/themes/ron/tasks-ron.css | 443 ++ design-reference/themes/ron/tasks-ron.jsx | 582 ++ .../ron/아리 대시보드 (RON v2).html | 42 + .../themes/ron/아리 대시보드 (RON).html | 43 + .../themes/ron/작업 (RON v2).html | 44 + design-reference/결재함.html | 47 + design-reference/대시보드.html | 47 + design-reference/라이프.html | 43 + design-reference/리서치.html | 44 + design-reference/메일.html | 43 + design-reference/알림.html | 46 + design-reference/여정.html | 44 + design-reference/여행.html | 46 + design-reference/인박스.html | 48 + design-reference/일정.html | 44 + design-reference/자동화.html | 46 + design-reference/작업.html | 44 + design-reference/하루 마감.html | 44 + dev/overview.md | 755 +++ dev/phase-0-foundation.md | 1045 ++++ dev/phase-1-design-system.md | 1422 +++++ dev/phase-10-research-travel.md | 1804 ++++++ dev/phase-11-life-care.md | 1704 ++++++ dev/phase-12-daily-narrative.md | 1768 ++++++ dev/phase-13-integrations.md | 1540 ++++++ dev/phase-14-proactive-agent.md | 1622 ++++++ dev/phase-15-production.md | 2253 ++++++++ dev/phase-2-backend.md | 2633 +++++++++ dev/phase-3-tasks.md | 1419 +++++ dev/phase-4-inbox.md | 1296 +++++ dev/phase-5-dashboard.md | 1531 ++++++ dev/phase-6-integration.md | 1070 ++++ dev/phase-7-approvals-automation.md | 1988 +++++++ dev/phase-8-calendar-meetings.md | 1750 ++++++ dev/phase-9-mail-notifications.md | 1795 ++++++ dev/post-mvp-overview.md | 655 +++ frontend/.gitignore | 41 + frontend/.prettierrc | 6 + frontend/AGENTS.md | 5 + frontend/CLAUDE.md | 1 + frontend/README.md | 36 + frontend/app/dashboard/page.tsx | 9 + frontend/app/favicon.ico | Bin 0 -> 25931 bytes frontend/app/layout.tsx | 30 + frontend/app/page.tsx | 6 + frontend/components/.gitkeep | 0 frontend/eslint.config.mjs | 26 + frontend/lib/api.ts | 15 + frontend/lib/hooks/.gitkeep | 0 frontend/lib/types.ts | 4 + frontend/next.config.ts | 18 + frontend/package.json | 35 + frontend/playwright/.gitkeep | 0 frontend/pnpm-lock.yaml | 4881 +++++++++++++++++ frontend/public/file.svg | 1 + frontend/public/globe.svg | 1 + frontend/public/next.svg | 1 + frontend/public/vercel.svg | 1 + frontend/public/window.svg | 1 + frontend/styles/globals.css | 21 + frontend/styles/tokens.css | 7 + frontend/tests/smoke.test.ts | 9 + frontend/tsconfig.json | 34 + frontend/vitest.config.ts | 16 + 150 files changed, 55117 insertions(+) create mode 100644 .gitignore create mode 100644 .playwright-mcp/page-2026-06-14T01-38-55-253Z.yml create mode 100644 Makefile create mode 100644 README.md create mode 100644 backend/.env.example create mode 100644 backend/.python-version create mode 100644 backend/alembic.ini create mode 100644 backend/app/__init__.py create mode 100644 backend/app/config.py create mode 100644 backend/app/db.py create mode 100644 backend/app/llm/__init__.py create mode 100644 backend/app/llm/ollama.py create mode 100644 backend/app/main.py create mode 100644 backend/app/routers/__init__.py create mode 100644 backend/app/routers/health.py create mode 100644 backend/app/routers/llm.py create mode 100644 backend/app/services/__init__.py create mode 100644 backend/migrations/README create mode 100644 backend/migrations/env.py create mode 100644 backend/migrations/script.py.mako create mode 100644 backend/pyproject.toml create mode 100644 backend/tests/__init__.py create mode 100644 backend/tests/conftest.py create mode 100644 backend/tests/test_health.py create mode 100644 backend/uv.lock create mode 100644 design-reference/HANDOFF-README.md create mode 100644 design-reference/PROJECT-README.md create mode 100644 design-reference/assets/approve-data.js create mode 100644 design-reference/assets/approve-page.jsx create mode 100644 design-reference/assets/approve.css create mode 100644 design-reference/assets/approve.jsx create mode 100644 design-reference/assets/auto-data.js create mode 100644 design-reference/assets/auto.css create mode 100644 design-reference/assets/auto.jsx create mode 100644 design-reference/assets/cal-data.js create mode 100644 design-reference/assets/cal-meet.jsx create mode 100644 design-reference/assets/cal.css create mode 100644 design-reference/assets/cal.jsx create mode 100644 design-reference/assets/dash-v2.jsx create mode 100644 design-reference/assets/dash.css create mode 100644 design-reference/assets/data.js create mode 100644 design-reference/assets/journey-clean-data.js create mode 100644 design-reference/assets/journey-clean.css create mode 100644 design-reference/assets/journey-clean.jsx create mode 100644 design-reference/assets/life-data.js create mode 100644 design-reference/assets/life.css create mode 100644 design-reference/assets/life.jsx create mode 100644 design-reference/assets/mail-data.js create mode 100644 design-reference/assets/mail.css create mode 100644 design-reference/assets/mail.jsx create mode 100644 design-reference/assets/notify-data.js create mode 100644 design-reference/assets/notify.css create mode 100644 design-reference/assets/notify.jsx create mode 100644 design-reference/assets/research-data.js create mode 100644 design-reference/assets/research.css create mode 100644 design-reference/assets/research.jsx create mode 100644 design-reference/assets/shell.jsx create mode 100644 design-reference/assets/sinbox-data.js create mode 100644 design-reference/assets/sinbox-page.jsx create mode 100644 design-reference/assets/sinbox.css create mode 100644 design-reference/assets/sinbox.jsx create mode 100644 design-reference/assets/tasks-data.js create mode 100644 design-reference/assets/tasks-risk.jsx create mode 100644 design-reference/assets/tasks.css create mode 100644 design-reference/assets/tasks.jsx create mode 100644 design-reference/assets/trip-data.js create mode 100644 design-reference/assets/trip.css create mode 100644 design-reference/assets/trip.jsx create mode 100644 design-reference/assets/wrap-data.js create mode 100644 design-reference/assets/wrap.css create mode 100644 design-reference/assets/wrap.jsx create mode 100644 design-reference/scraps/ai2.png create mode 100644 design-reference/scraps/reader-ai.png create mode 100644 design-reference/themes/lumi/Lumi People.html create mode 100644 design-reference/themes/lumi/README.md create mode 100644 design-reference/themes/lumi/ari-lumi.css create mode 100644 design-reference/themes/lumi/image-slot.js create mode 100644 design-reference/themes/lumi/people.css create mode 100644 design-reference/themes/lumi/아리 대시보드 라이트.html create mode 100644 design-reference/themes/ron/README.md create mode 100644 design-reference/themes/ron/dash-bento.css create mode 100644 design-reference/themes/ron/dash-ron.jsx create mode 100644 design-reference/themes/ron/data.js create mode 100644 design-reference/themes/ron/journey.css create mode 100644 design-reference/themes/ron/journey.jsx create mode 100644 design-reference/themes/ron/tasks-ron-data.js create mode 100644 design-reference/themes/ron/tasks-ron.css create mode 100644 design-reference/themes/ron/tasks-ron.jsx create mode 100644 design-reference/themes/ron/아리 대시보드 (RON v2).html create mode 100644 design-reference/themes/ron/아리 대시보드 (RON).html create mode 100644 design-reference/themes/ron/작업 (RON v2).html create mode 100644 design-reference/결재함.html create mode 100644 design-reference/대시보드.html create mode 100644 design-reference/라이프.html create mode 100644 design-reference/리서치.html create mode 100644 design-reference/메일.html create mode 100644 design-reference/알림.html create mode 100644 design-reference/여정.html create mode 100644 design-reference/여행.html create mode 100644 design-reference/인박스.html create mode 100644 design-reference/일정.html create mode 100644 design-reference/자동화.html create mode 100644 design-reference/작업.html create mode 100644 design-reference/하루 마감.html create mode 100644 dev/overview.md create mode 100644 dev/phase-0-foundation.md create mode 100644 dev/phase-1-design-system.md create mode 100644 dev/phase-10-research-travel.md create mode 100644 dev/phase-11-life-care.md create mode 100644 dev/phase-12-daily-narrative.md create mode 100644 dev/phase-13-integrations.md create mode 100644 dev/phase-14-proactive-agent.md create mode 100644 dev/phase-15-production.md create mode 100644 dev/phase-2-backend.md create mode 100644 dev/phase-3-tasks.md create mode 100644 dev/phase-4-inbox.md create mode 100644 dev/phase-5-dashboard.md create mode 100644 dev/phase-6-integration.md create mode 100644 dev/phase-7-approvals-automation.md create mode 100644 dev/phase-8-calendar-meetings.md create mode 100644 dev/phase-9-mail-notifications.md create mode 100644 dev/post-mvp-overview.md create mode 100644 frontend/.gitignore create mode 100644 frontend/.prettierrc create mode 100644 frontend/AGENTS.md create mode 100644 frontend/CLAUDE.md create mode 100644 frontend/README.md create mode 100644 frontend/app/dashboard/page.tsx create mode 100644 frontend/app/favicon.ico create mode 100644 frontend/app/layout.tsx create mode 100644 frontend/app/page.tsx create mode 100644 frontend/components/.gitkeep create mode 100644 frontend/eslint.config.mjs create mode 100644 frontend/lib/api.ts create mode 100644 frontend/lib/hooks/.gitkeep create mode 100644 frontend/lib/types.ts create mode 100644 frontend/next.config.ts create mode 100644 frontend/package.json create mode 100644 frontend/playwright/.gitkeep create mode 100644 frontend/pnpm-lock.yaml create mode 100644 frontend/public/file.svg create mode 100644 frontend/public/globe.svg create mode 100644 frontend/public/next.svg create mode 100644 frontend/public/vercel.svg create mode 100644 frontend/public/window.svg create mode 100644 frontend/styles/globals.css create mode 100644 frontend/styles/tokens.css create mode 100644 frontend/tests/smoke.test.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/vitest.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7a3f7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# ── Node / Next.js ── +frontend/node_modules/ +frontend/.next/ +frontend/out/ +frontend/.env.local +frontend/coverage/ +frontend/playwright-report/ +frontend/test-results/ + +# ── Python / FastAPI ── +backend/.venv/ +backend/__pycache__/ +backend/**/__pycache__/ +backend/.env +backend/ari.db +backend/.pytest_cache/ +backend/.ruff_cache/ +*.pyc + +# ── 공통 ── +.DS_Store +*.log diff --git a/.playwright-mcp/page-2026-06-14T01-38-55-253Z.yml b/.playwright-mcp/page-2026-06-14T01-38-55-253Z.yml new file mode 100644 index 0000000..9fc8661 --- /dev/null +++ b/.playwright-mcp/page-2026-06-14T01-38-55-253Z.yml @@ -0,0 +1,7 @@ +- generic [active] [ref=e1]: + - main [ref=e2]: + - heading "아리 — 대시보드 (준비 중)" [level=1] [ref=e3] + - paragraph [ref=e4]: Phase 0 스캐폴딩. 실제 대시보드는 phase-5-dashboard.md 에서 구현됩니다. + - button "Open Next.js Dev Tools" [ref=e10] [cursor=pointer]: + - img [ref=e11] + - alert [ref=e14] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..118a61a --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +# Makefile (레포 루트) +.PHONY: help install fe be dev test lint format health seed migrate clean + +FRONT := frontend +BACK := backend + +help: + @echo "make install - 프론트/백 의존성 설치" + @echo "make dev - 프론트(3000)+백(8000) 동시 실행" + @echo "make fe - 프론트만 (next dev)" + @echo "make be - 백엔드만 (uvicorn --reload)" + @echo "make test - pytest + vitest" + @echo "make lint - ruff + eslint" + @echo "make health - 두 health 엔드포인트 curl" + @echo "make seed - DB 시드 적재" + @echo "make migrate - alembic upgrade head" + +install: + cd $(FRONT) && pnpm install + cd $(BACK) && uv sync + +fe: + cd $(FRONT) && pnpm dev + +be: + cd $(BACK) && uv run uvicorn app.main:app --reload --port 8000 + +# 두 서버 동시 실행: Ctrl-C 시 둘 다 정리. +dev: + @echo "▶ backend :8000 / frontend :3000 (Ctrl-C 로 종료)" + @trap 'kill 0' INT TERM EXIT; \ + ( cd $(BACK) && uv run uvicorn app.main:app --reload --port 8000 ) & \ + ( cd $(FRONT) && pnpm dev ) & \ + wait + +test: + cd $(BACK) && uv run pytest + cd $(FRONT) && pnpm test + +lint: + cd $(BACK) && uv run ruff check . + cd $(FRONT) && pnpm lint + +format: + cd $(BACK) && uv run ruff format . && uv run black . + cd $(FRONT) && pnpm format + +health: + @echo "── /api/health ──" && curl -s http://localhost:8000/api/health | python3 -m json.tool + @echo "── /api/llm/health ──" && curl -s http://localhost:8000/api/llm/health | python3 -m json.tool + +seed: + cd $(BACK) && uv run python -m app.seed + +migrate: + cd $(BACK) && uv run alembic upgrade head + +clean: + rm -rf $(FRONT)/.next $(BACK)/ari.db $(BACK)/.pytest_cache diff --git a/README.md b/README.md new file mode 100644 index 0000000..417ad83 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# 아리(Ari) — AI Life OS + +일·삶을 한곳에서 관리하는 한국어 AI 개인비서 **아리**의 MVP. +핵심 철학: **"적을 때는 분류하지 않는다 — 분류·배치·자동화는 아리가."** + +모노레포: `frontend/`(Next.js App Router + TS) · `backend/`(FastAPI + SQLite + Ollama). +개발 문서는 `dev/`(진입: `dev/overview.md`), 디자인 기준은 `design-reference/`. + +## 빠른 시작 (Phase 0) + +```bash +# 0) 사전 설치: Node20+/pnpm, Python3.11+/uv, Ollama +make install # 프론트+백 의존성 + +# 1) 환경변수 +cp backend/.env.example backend/.env # 필요 시 OLLAMA_MODEL 등 수정 +echo 'NEXT_PUBLIC_API_BASE=http://localhost:8000' > frontend/.env.local + +# 2) (선택) Ollama +ollama serve & +ollama pull <설치할_모델> # .env 의 OLLAMA_MODEL 과 맞춤 (특정 모델 강제 아님) + +# 3) 실행 +make dev # 프론트 :3000 + 백 :8000 + +# 4) 스모크 +make health # /api/health, /api/llm/health +make test # pytest + vitest +make lint # ruff + eslint +``` + +## 구조 + +``` +workspace/ +├─ frontend/ Next.js (App Router, TypeScript) +├─ backend/ FastAPI (SQLite, SQLModel, Alembic, Ollama) +├─ dev/ 개발 문서 (overview.md + phase-*.md) +└─ design-reference/ 원본 프로토타입 (픽셀 충실 재현 기준) +``` diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..d28bed6 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,4 @@ +DATABASE_URL=sqlite:///./ari.db +FRONTEND_ORIGIN=http://localhost:3000 +OLLAMA_HOST=http://localhost:11434 +OLLAMA_MODEL=llama3.1 diff --git a/backend/.python-version b/backend/.python-version new file mode 100644 index 0000000..2c07333 --- /dev/null +++ b/backend/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/backend/alembic.ini b/backend/alembic.ini new file mode 100644 index 0000000..08f1d9f --- /dev/null +++ b/backend/alembic.ini @@ -0,0 +1,149 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = %(here)s/migrations + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s +# Or organize into date-based subdirectories (requires recursive_version_locations = true) +# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. +prepend_sys_path = . + + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the tzdata library which can be installed by adding +# `alembic[tz]` to the pip requirements. +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/config.py b/backend/app/config.py new file mode 100644 index 0000000..e37b6d6 --- /dev/null +++ b/backend/app/config.py @@ -0,0 +1,21 @@ +# backend/app/config.py +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + model_config = SettingsConfigDict( + env_file=".env", env_file_encoding="utf-8", extra="ignore" + ) + + # ── DB ── + database_url: str = "sqlite:///./ari.db" + + # ── CORS ── + frontend_origin: str = "http://localhost:3000" + + # ── Ollama (모델 비종속: 모델명은 환경변수로 주입) ── + ollama_host: str = "http://localhost:11434" + ollama_model: str = "llama3.1" # 예시 기본값 — 강제 아님, 설치된 모델로 덮어쓰기 + + +settings = Settings() diff --git a/backend/app/db.py b/backend/app/db.py new file mode 100644 index 0000000..d0ad515 --- /dev/null +++ b/backend/app/db.py @@ -0,0 +1,18 @@ +# backend/app/db.py +from collections.abc import Generator + +from sqlmodel import Session, create_engine + +from app.config import settings + +# SQLite + 단일 프로세스 dev: check_same_thread=False 필요 +engine = create_engine( + settings.database_url, + echo=False, + connect_args={"check_same_thread": False}, +) + + +def get_session() -> Generator[Session, None, None]: + with Session(engine) as session: + yield session diff --git a/backend/app/llm/__init__.py b/backend/app/llm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/llm/ollama.py b/backend/app/llm/ollama.py new file mode 100644 index 0000000..1742307 --- /dev/null +++ b/backend/app/llm/ollama.py @@ -0,0 +1,33 @@ +# backend/app/llm/ollama.py +import httpx + +from app.config import settings + + +async def check_ollama() -> dict: + """Ollama 데몬 reachability + 설치 모델 목록을 비차단으로 점검. + Ollama 가 꺼져 있어도 예외를 삼키고 reachable=False 로 반환한다(서버는 죽지 않음). + """ + url = f"{settings.ollama_host}/api/tags" + try: + async with httpx.AsyncClient(timeout=2.0) as client: + res = await client.get(url) + res.raise_for_status() + data = res.json() + models = [m.get("name") for m in data.get("models", [])] + return { + "reachable": True, + "host": settings.ollama_host, + "model": settings.ollama_model, + "model_installed": settings.ollama_model in models, + "available_models": models, + } + except Exception as exc: # 연결 거부/타임아웃/JSON 오류 모두 포함 + return { + "reachable": False, + "host": settings.ollama_host, + "model": settings.ollama_model, + "model_installed": False, + "available_models": [], + "error": type(exc).__name__, + } diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..466e965 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,22 @@ +# backend/app/main.py +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.config import settings +from app.routers import health, llm + +app = FastAPI(title="아리 — AI LIFE OS API", version="0.1.0") + +# CORS: 프론트 origin 허용 +app.add_middleware( + CORSMiddleware, + allow_origins=[settings.frontend_origin], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# 모든 API 는 /api 프리픽스 (CONTRACT) +# 라우터는 내부 prefix 없이 경로를 정의하고, /api 프리픽스는 여기서만 붙인다 +app.include_router(health.router, prefix="/api", tags=["health"]) +app.include_router(llm.router, prefix="/api", tags=["llm"]) diff --git a/backend/app/routers/__init__.py b/backend/app/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/routers/health.py b/backend/app/routers/health.py new file mode 100644 index 0000000..4b1a369 --- /dev/null +++ b/backend/app/routers/health.py @@ -0,0 +1,9 @@ +# backend/app/routers/health.py +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/health") +async def health() -> dict: + return {"status": "ok"} diff --git a/backend/app/routers/llm.py b/backend/app/routers/llm.py new file mode 100644 index 0000000..37ef266 --- /dev/null +++ b/backend/app/routers/llm.py @@ -0,0 +1,11 @@ +# backend/app/routers/llm.py +from fastapi import APIRouter + +from app.llm.ollama import check_ollama + +router = APIRouter() + + +@router.get("/llm/health") +async def llm_health() -> dict: + return await check_ollama() diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/migrations/README b/backend/migrations/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/backend/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/backend/migrations/env.py b/backend/migrations/env.py new file mode 100644 index 0000000..5959deb --- /dev/null +++ b/backend/migrations/env.py @@ -0,0 +1,81 @@ +from logging.config import fileConfig + +from alembic import context +from sqlalchemy import engine_from_config, pool + +from app.config import settings + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# settings 에서 DB URL 주입 (alembic.ini 하드코딩 대신) +config.set_main_option("sqlalchemy.url", settings.database_url) + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/migrations/script.py.mako b/backend/migrations/script.py.mako new file mode 100644 index 0000000..1101630 --- /dev/null +++ b/backend/migrations/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..3da58ee --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,35 @@ +[project] +name = "backend" +version = "0.1.0" +description = "Add your description here" +requires-python = ">=3.11" +dependencies = [ + "alembic>=1.18.4", + "fastapi>=0.136.3", + "httpx>=0.28.1", + "pydantic-settings>=2.14.1", + "python-dotenv>=1.2.2", + "sqlmodel>=0.0.38", + "uvicorn[standard]>=0.49.0", +] + +[dependency-groups] +dev = [ + "black>=26.5.1", + "pytest>=9.1.0", + "ruff>=0.15.17", +] + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "I", "UP", "B"] + +[tool.black] +line-length = 100 + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-q" diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py new file mode 100644 index 0000000..85362a7 --- /dev/null +++ b/backend/tests/conftest.py @@ -0,0 +1,9 @@ +import pytest +from fastapi.testclient import TestClient + +from app.main import app + + +@pytest.fixture() +def client() -> TestClient: + return TestClient(app) diff --git a/backend/tests/test_health.py b/backend/tests/test_health.py new file mode 100644 index 0000000..8129363 --- /dev/null +++ b/backend/tests/test_health.py @@ -0,0 +1,14 @@ +def test_health(client): + res = client.get("/api/health") + assert res.status_code == 200 + assert res.json() == {"status": "ok"} + + +def test_llm_health_shape(client): + """Ollama on/off 무관하게 200 + 필수 키를 반환해야 한다(서버가 죽지 않음).""" + res = client.get("/api/llm/health") + assert res.status_code == 200 + body = res.json() + assert "reachable" in body + assert "model" in body + assert "host" in body diff --git a/backend/uv.lock b/backend/uv.lock new file mode 100644 index 0000000..bb34d94 --- /dev/null +++ b/backend/uv.lock @@ -0,0 +1,1062 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" + +[[package]] +name = "alembic" +version = "1.18.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "backend" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "alembic" }, + { name = "fastapi" }, + { name = "httpx" }, + { name = "pydantic-settings" }, + { name = "python-dotenv" }, + { name = "sqlmodel" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[package.dev-dependencies] +dev = [ + { name = "black" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "alembic", specifier = ">=1.18.4" }, + { name = "fastapi", specifier = ">=0.136.3" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "pydantic-settings", specifier = ">=2.14.1" }, + { name = "python-dotenv", specifier = ">=1.2.2" }, + { name = "sqlmodel", specifier = ">=0.0.38" }, + { name = "uvicorn", extras = ["standard"], specifier = ">=0.49.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "black", specifier = ">=26.5.1" }, + { name = "pytest", specifier = ">=9.1.0" }, + { name = "ruff", specifier = ">=0.15.17" }, +] + +[[package]] +name = "black" +version = "26.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/96/3c3e09f09f44a37aac36b178a279cd19aa7001bd796187a7b162a294c81f/black-26.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c", size = 1970639, upload-time = "2026-05-18T17:05:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/83/ea/5ad117b9ee3ecd933c712bcbae610006e5b7cc9f41c526cd7ed3b6c4124c/black-26.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7", size = 1792130, upload-time = "2026-05-18T17:05:12.983Z" }, + { url = "https://files.pythonhosted.org/packages/06/3a/7c448bc623fcdfa96672531beb5a616ea5e64f6975955254d7731ffb0ad9/black-26.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59", size = 1846134, upload-time = "2026-05-18T17:05:14.506Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5b/0b39b3a5917f0657ac014ad2edb58c139553a478adfe7f817abf1622ff6e/black-26.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3", size = 1478883, upload-time = "2026-05-18T17:05:16.542Z" }, + { url = "https://files.pythonhosted.org/packages/4c/48/dc222692e0f95030db1bbfb6c857e76858bad09058221ea7aae815255327/black-26.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe", size = 1277776, upload-time = "2026-05-18T17:05:18.029Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/7744b906703228264ef73bdd534df88ec1ef3de45c4e78f6d31b9e32d0c9/black-26.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8", size = 2012518, upload-time = "2026-05-18T17:05:20.108Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c0/c5a3b1636dfd09c42534f2b3cf33506814f6d3e066fb0879ffa16c1ae860/black-26.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217", size = 1816016, upload-time = "2026-05-18T17:05:21.84Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0e/36044316b65ca471d3bb6d3703fd06fb50c6b727c3562f6a5a3153634f88/black-26.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d", size = 1884150, upload-time = "2026-05-18T17:05:23.546Z" }, + { url = "https://files.pythonhosted.org/packages/b3/33/dafc5808c2af43672912111d7c3354af1615f7e2be3bed7a878461abbe4d/black-26.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264", size = 1486825, upload-time = "2026-05-18T17:05:25.004Z" }, + { url = "https://files.pythonhosted.org/packages/82/14/b965ee6ad2a311f28bdbf692def3ee9848d2ae289dab28b27657fcee3e78/black-26.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418", size = 1288646, upload-time = "2026-05-18T17:05:26.477Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5c/c384363980e11e25ca6b93205949bb331fbf35f4e0dbec376dfa6326cec8/black-26.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3", size = 2009020, upload-time = "2026-05-18T17:05:28.132Z" }, + { url = "https://files.pythonhosted.org/packages/0b/df/9f31c5e0babbfed77d505fc5d120beb98b21b33feaeded3924ea941fe360/black-26.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0", size = 1813335, upload-time = "2026-05-18T17:05:31.266Z" }, + { url = "https://files.pythonhosted.org/packages/fb/24/8e7b9a2fa61b0afd82209efe937557d180a1fa055bd7f6161eb9defc3719/black-26.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294", size = 1881614, upload-time = "2026-05-18T17:05:32.718Z" }, + { url = "https://files.pythonhosted.org/packages/49/ad/b4e0d9365ba8ac34f6bbab62a4b1b2dd5d618fac3fa1b8db968c844201b5/black-26.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a", size = 1488925, upload-time = "2026-05-18T17:05:34.259Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4b/652b859bf5df88a751c30451b09338f7fd26a77d1271c666992f836b7711/black-26.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52", size = 1289883, upload-time = "2026-05-18T17:05:36.019Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" }, + { url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" }, +] + +[[package]] +name = "certifi" +version = "2026.5.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, +] + +[[package]] +name = "click" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "fastapi" +version = "0.136.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" }, +] + +[[package]] +name = "greenlet" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/6e/802acd792aebb2256fbbee8cacf2727faaeb6f240ac11008f09eae4414bc/greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", size = 197356, upload-time = "2026-05-20T15:05:03.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/3c/ff890b466eaba2b0f5e6bdfff025f8c75f41b8ffdc3dbc3d24ad261e764a/greenlet-3.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:73f78f9b9f0a5c06e5c946ba1e8e36f5114923b6be109ee618c54f079c3ea14f", size = 284764, upload-time = "2026-05-20T13:09:10.204Z" }, + { url = "https://files.pythonhosted.org/packages/81/0e/5e5457be3d256918f6a4756f073548a3f0190836e2cc94aa6d0d617a940b/greenlet-3.5.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0cbed8bb44e23c5b199f888f4e4ce096b45ad9f25ff74a7ad0213875e936bb2", size = 603479, upload-time = "2026-05-20T14:00:04.757Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e1/f89a21d58d308298e6f275f13a1b472ed96c680b601a371b08be6a725989/greenlet-3.5.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a203a8bd0acb0701653d3bbb26e404854a68674139ed5cbb778830f42b09bb33", size = 615495, upload-time = "2026-05-20T14:05:40.87Z" }, + { url = "https://files.pythonhosted.org/packages/75/de/af6cef182862d2ccd6975440d21c9058a77c3f9b469abf94e322dfd2e0e3/greenlet-3.5.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a271fcd66c74615cda6a964fda3f304267a12e50a084472218a39bb0376f563", size = 614754, upload-time = "2026-05-20T13:14:24.947Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c6/50e520283a9f19388a7326b05f9e8637e566003475eacaadad04f558c68d/greenlet-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ded7b068c7c31c1a8657d4fd42d886b3e051ae29f88b80c5ff9d502257b0f071", size = 1574097, upload-time = "2026-05-20T14:02:24.003Z" }, + { url = "https://files.pythonhosted.org/packages/21/1c/13abd1f4860d987fa5e1170a01930d6e6cd40d328de487a3c9fdaff0ffd0/greenlet-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0932b81d72f552ded9d810d00021b64d89f2195a91ce115b893f943b7a4ab3c", size = 1641058, upload-time = "2026-05-20T13:14:31.83Z" }, + { url = "https://files.pythonhosted.org/packages/f5/56/5f332b7705545eac2dc01b4e9254d24a793f2656d55d5cc6b94ee59d22ae/greenlet-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:88e300d136eac057b2397aa1cfd7328b4c87c7eb66a09c7bc6a1292234db474e", size = 238089, upload-time = "2026-05-20T13:14:03.229Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a9/a3c2fa886c5b94863fb0e61b3bc14610b7aa94cf4f17f8741b11708305fc/greenlet-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:cc6ab7e555c8a112ad3a76e368e86e12a2754bcae1652a5602e133ec7b635523", size = 234989, upload-time = "2026-05-20T13:08:27.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/37/4549f149c9797c21b32c2683c33522af22522099de128b2406672526d005/greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2", size = 286220, upload-time = "2026-05-20T13:07:28.463Z" }, + { url = "https://files.pythonhosted.org/packages/38/ff/a4f436709716965eaab9f36ea7b906c8a927fbe32fb1372a2071d964f6b1/greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed", size = 601585, upload-time = "2026-05-20T14:00:06.141Z" }, + { url = "https://files.pythonhosted.org/packages/65/ad/54bc3fcee3ad368a61b19b67d88117f7a8c29727bf71fffdeda81fbd946e/greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10", size = 614215, upload-time = "2026-05-20T14:05:42.675Z" }, + { url = "https://files.pythonhosted.org/packages/40/69/b91cda0647df839483201545913514c2827ebea5e5ccdf931842763bc127/greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b", size = 611358, upload-time = "2026-05-20T13:14:26.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/90/3cf77e080350cd02fa307bb2abf05df48f4482c240275bbd2c203ba8bb1c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207", size = 1570475, upload-time = "2026-05-20T14:02:25.29Z" }, + { url = "https://files.pythonhosted.org/packages/65/2c/18cece62045e74598c3c393f70dce4a63f56222015ba29a5d4eeb04f764c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823", size = 1635625, upload-time = "2026-05-20T13:14:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/30/f5/310d104ddf41eb5a70f4c268d22508dfb0c3c8e86fec152be34d0d2ed819/greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b", size = 238791, upload-time = "2026-05-20T13:10:39.018Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/ceca11f504cd23a8047a3dea31919adc48df9b626dd0c13f0d858734fdfd/greenlet-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:80eb4b04dadc4e67df3fae179a32c4706a3f495bc7f22fc8a81115d5f5512188", size = 235580, upload-time = "2026-05-20T13:08:45.056Z" }, + { url = "https://files.pythonhosted.org/packages/27/69/7f7e5372d998b81001899b1c0823c957aa413ba0f2662e65821611cc31e4/greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", size = 285060, upload-time = "2026-05-20T13:08:51.899Z" }, + { url = "https://files.pythonhosted.org/packages/b1/bf/387f9b6b865fd2ae0d0be09e0004827295a01b71be76ed350dd1e28a91a4/greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", size = 604370, upload-time = "2026-05-20T14:00:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/32/f5/169ce3d4e4c67291bd18f8cbe0299c9f3e45102c7f1fb3c14780c93e4532/greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", size = 616987, upload-time = "2026-05-20T14:05:44.237Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e5/7f2e41d5273be07e77560d61ea4e56485b4d6c316d2a84518c62d1364061/greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", size = 613911, upload-time = "2026-05-20T13:14:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a4/fbdc67579b73615a1f91615e814303cc71e06128f7baaba87be79b8fb90c/greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", size = 1570689, upload-time = "2026-05-20T14:02:27.225Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b4/77abbe35078be39718a46cd49caf16bceb35662f97a34101dca28aa98e47/greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", size = 1635602, upload-time = "2026-05-20T13:14:36.344Z" }, + { url = "https://files.pythonhosted.org/packages/37/f7/129f27ca700845b8ee8ca88ce7f43435a1239c2eddb7677fc938822762cf/greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", size = 238683, upload-time = "2026-05-20T13:11:50.57Z" }, + { url = "https://files.pythonhosted.org/packages/6d/5c/a485a36e87df8d8fd0632ee01511244f5156a20ed3746cc6599340326395/greenlet-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f16ba1efc0715b680a18b8123d90dad887c6112ae3555b4b5c32c149540c6b4e", size = 235499, upload-time = "2026-05-20T13:12:42.028Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" }, + { url = "https://files.pythonhosted.org/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" }, + { url = "https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" }, + { url = "https://files.pythonhosted.org/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/8e8e8417b7bf28639a5a56356ef934d0375e1d0c70a57e04d7701e870ffe/greenlet-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54", size = 236862, upload-time = "2026-05-20T13:09:10.498Z" }, + { url = "https://files.pythonhosted.org/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" }, + { url = "https://files.pythonhosted.org/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" }, + { url = "https://files.pythonhosted.org/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" }, + { url = "https://files.pythonhosted.org/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" }, + { url = "https://files.pythonhosted.org/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" }, + { url = "https://files.pythonhosted.org/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ae/4e623a7e6d4d2a5f4cb8e4c82de4169fc637942caae68d6e676b8a128ac5/greenlet-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6", size = 236853, upload-time = "2026-05-20T13:15:37.301Z" }, + { url = "https://files.pythonhosted.org/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" }, + { url = "https://files.pythonhosted.org/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" }, + { url = "https://files.pythonhosted.org/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" }, + { url = "https://files.pythonhosted.org/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/4f/fd/d3baea2eeb7b617efd47e87ca06e2ec2c6118d303aa9e918e0ce16eadc10/greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a", size = 239590, upload-time = "2026-05-20T13:13:37.382Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/d2/c3eedaef57de65c3cc5f8dc244cf12d09c84ad258a479055aad6db23206c/httptools-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168", size = 208428, upload-time = "2026-05-25T22:16:59.717Z" }, + { url = "https://files.pythonhosted.org/packages/f1/94/dfe435d90d0ef61ec0f2cc3d480eef78c59727c6c2ce039f433882f6131a/httptools-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d", size = 113366, upload-time = "2026-05-25T22:17:00.795Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d4/13025f1a56e615dcb331e0bbe2d9a1143212b58c263385fc5d2e558f5bac/httptools-0.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376", size = 464676, upload-time = "2026-05-25T22:17:02.014Z" }, + { url = "https://files.pythonhosted.org/packages/bf/95/4c1c26c0b985f8a3331682d802598f14e32dc41bf7509266eb2c04ad4801/httptools-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d", size = 464235, upload-time = "2026-05-25T22:17:03.109Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/6735be2b0ca527718c431cdb8e5f70c3862c0844a687df0f572c51e11497/httptools-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085", size = 449809, upload-time = "2026-05-25T22:17:04.443Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f9/5811c74f37a758c8a4aa3dc430375119d335947e883efc4664d8f3559a41/httptools-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124", size = 452174, upload-time = "2026-05-25T22:17:05.476Z" }, + { url = "https://files.pythonhosted.org/packages/cc/94/97b75870dea07b71e3ec535cebe525b08d723152e4c7d13fa887e51f4de2/httptools-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07", size = 90991, upload-time = "2026-05-25T22:17:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" }, + { url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" }, + { url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" }, + { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" }, + { url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" }, + { url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" }, + { url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" }, + { url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" }, + { url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" }, + { url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "mako" +version = "1.3.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/62/791b31e69ae182791ec67f04850f2f062716bbd205483d63a215f3e062d3/mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a", size = 400219, upload-time = "2026-04-28T19:01:08.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/b1/a0ec7a5a9db730a08daef1fdfb8090435b82465abbf758a596f0ea88727e/mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9", size = 78521, upload-time = "2026-04-28T19:01:10.393Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, + { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, + { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, + { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, + { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, + { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/a9/3abdf488f1bf3d24c699415e454ed554a6350d5d89ce183be1ee0a3361ac/ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219", size = 4743346, upload-time = "2026-06-11T17:54:47.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/4d/e11259f5da07cb6afb2d074c31bf09da9671993f7329d4f15d2fdc458301/ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f", size = 10856677, upload-time = "2026-06-11T17:54:49.533Z" }, + { url = "https://files.pythonhosted.org/packages/29/3e/772d679e1a0dc058e58875bd2c0cb713a0530877b4a76fee3c7966df0d49/ruff-0.15.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7", size = 11223443, upload-time = "2026-06-11T17:55:00.573Z" }, + { url = "https://files.pythonhosted.org/packages/68/58/bd41f7688b2fd5623012605130ed70e60aa7f2244baa3d5066bdd61530c8/ruff-0.15.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d", size = 10566458, upload-time = "2026-06-11T17:55:07.52Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5b/733371013fcf1ec339e477ece6ab42bfe10bdd9bba8ee88a9516aa56bfc0/ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed", size = 10914483, upload-time = "2026-06-11T17:55:05.501Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cc/6f24251cc0252f7239391ccb85833f320efad14ebe5b443943f37ced6332/ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e", size = 10647497, upload-time = "2026-06-11T17:54:57.733Z" }, + { url = "https://files.pythonhosted.org/packages/68/dd/0d10c17ce1a1624d6fc3156309c3f834fdb5dfaad026ec90c85684f3990e/ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d", size = 11416967, upload-time = "2026-06-11T17:54:51.461Z" }, + { url = "https://files.pythonhosted.org/packages/2f/91/556bfb156f6144f355e831c23db00b2fc4120f86b3ce81cc5f7fd2df51f3/ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c", size = 12335770, upload-time = "2026-06-11T17:54:45.793Z" }, + { url = "https://files.pythonhosted.org/packages/88/82/8b5999aa13355e926f06d9f42a32dcca862f623bf0363785ff89d607dffd/ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978", size = 11575441, upload-time = "2026-06-11T17:54:32.661Z" }, + { url = "https://files.pythonhosted.org/packages/11/93/f10377bb04109ca0e8cbc483ff1982c54b6d418210041776f93e8cdc7fa9/ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719", size = 11557614, upload-time = "2026-06-11T17:54:34.698Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a6/eeeae7f7d5493df41649ab3db92f086b2d0a30199e4efdf8e3dd7a033f24/ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4", size = 11544450, upload-time = "2026-06-11T17:54:39.042Z" }, + { url = "https://files.pythonhosted.org/packages/32/88/5991ce565129a24dd4a00db1254b3b5db2e53018cbe4018ea5a89738e727/ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7", size = 10892524, upload-time = "2026-06-11T17:55:09.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1d/0fdd248313425f55223968af04b0a42125466a8d88d21c1d99c6af0a51e8/ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f", size = 10659573, upload-time = "2026-06-11T17:54:36.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0e/072e8260deb9461062ce9311ced27a8e541229a6ffd483013dd37661e43e/ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f", size = 11127818, upload-time = "2026-06-11T17:55:03.124Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b4/55060a34163121498014696b5f656db5b8c6963768f227dbf0d76b311073/ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8", size = 11655901, upload-time = "2026-06-11T17:54:53.562Z" }, + { url = "https://files.pythonhosted.org/packages/49/71/9b29d6b87cef468d697f43c6a91e3fae4a80185779d7d5a4ef27d173439f/ruff-0.15.17-py3-none-win32.whl", hash = "sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392", size = 10925574, upload-time = "2026-06-11T17:54:55.723Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b2/8fc77f3723228836fa5d12497eb71c808f83782e10d058d2b15cfa14640b/ruff-0.15.17-py3-none-win_amd64.whl", hash = "sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084", size = 12058788, upload-time = "2026-06-11T17:54:41.042Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/da/6fbf010c8ebb347679d0d100b22fe9ba5e13fd04046c5df7280d2f0bf706/sqlalchemy-2.0.50.tar.gz", hash = "sha256:af5607d11ef90fd6a5c0549fe0045dce1663d427426bcfb506dcb5346a85a3b9", size = 9907424, upload-time = "2026-05-24T19:20:04.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/5d/3172686af1770e4de2805f919a51441085f589ddadf3dd76ec582f84f497/sqlalchemy-2.0.50-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa6e403663a9c43c8fef7ce4bdb4cf48bcd8d352e91deda2a99f963270bd508", size = 2161366, upload-time = "2026-05-24T20:00:02.061Z" }, + { url = "https://files.pythonhosted.org/packages/0f/90/e98dedea3c3e663a17afcd003a34ba45efdac2cea3b6f2e4585e2b1e2537/sqlalchemy-2.0.50-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51b637a84f9fa35ae1f9017e786cb142974a25305085e1b378b3647a67f65ad3", size = 3318926, upload-time = "2026-05-24T20:07:42.369Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4f/501308c2babb62c11753ecb4ee88ba9eef019419a4d6cbf7cb13e2bad353/sqlalchemy-2.0.50-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2dab927761d9108550f0cf8e66ff21af56f907a0ce0a689793db615e2b55f62c", size = 3319199, upload-time = "2026-05-24T20:14:28.551Z" }, + { url = "https://files.pythonhosted.org/packages/ac/39/d88996c5e03ed6248c3a788d20f0b8d8b376b9f8a495e4bab9df7c72d2f8/sqlalchemy-2.0.50-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:545eae198d37bcf837a10ede3684e2af32458d6f35c597c35c2de7502dc38fc4", size = 3270301, upload-time = "2026-05-24T20:07:44.917Z" }, + { url = "https://files.pythonhosted.org/packages/42/1b/1ae0e65161b51cc43e5ca75430ef79d80e23b5042d645586c2c342c3b92e/sqlalchemy-2.0.50-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fec460e18cdbb4c7773531122ce9a27e96c6ca17af3933941d94da475ad2c86", size = 3293465, upload-time = "2026-05-24T20:14:30.501Z" }, + { url = "https://files.pythonhosted.org/packages/83/29/17c0003f2c0dfa6d1b97672475707e3ec5980db09defd7fa20beb6833bbd/sqlalchemy-2.0.50-cp311-cp311-win32.whl", hash = "sha256:e6e814658818fd165e749e3d8490ef16cc7f379a118c37ada8b0589ffbaaac22", size = 2120694, upload-time = "2026-05-24T20:08:09.237Z" }, + { url = "https://files.pythonhosted.org/packages/c9/18/280d00654cc19d1fccf236fa5070f6dd04b84dde6f1b2e637bde0ff340a7/sqlalchemy-2.0.50-cp311-cp311-win_amd64.whl", hash = "sha256:1c5f858fe79c9f5d8fda065c06186356acb7f8df3cd52dbd5ee3f200e4b144f5", size = 2145315, upload-time = "2026-05-24T20:08:10.952Z" }, + { url = "https://files.pythonhosted.org/packages/be/b0/a9d19b43f38f878b1278bca5b00b909f7540d41494396dd2561f9ad0956d/sqlalchemy-2.0.50-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae23d8b9d344d30d0a92f06d45825024a5790f1c1dd4cf452636a50d3e58cb", size = 2159807, upload-time = "2026-05-24T19:27:53.086Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/191dd58a248fd2cfd4780fa82c375c505e4ad98c8b522fa69ec492130d77/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47b71b933e7b4ebad407c8fdfd70d2c4f08b78b3238bb30eebdd6eb32ca51b89", size = 3343358, upload-time = "2026-05-24T20:09:29.279Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2b/514fce8a7df81cf5bad7ff7865de7ac0c5776a38cc043475c4703eb7fe8b/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:110fdac56ace278949f00de805edacbd6141e382d992f9ba28238b3a0827a600", size = 3357994, upload-time = "2026-05-24T20:17:13.495Z" }, + { url = "https://files.pythonhosted.org/packages/35/a6/a0e283f5494f92b0d77e319ff77e437b1ffe4a051ba67c81d53234825475/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5e4ac70e9e757f6b3e87c0491ff034442ecd8dfd36d041a50564c322dafc0e", size = 3289399, upload-time = "2026-05-24T20:09:32.239Z" }, + { url = "https://files.pythonhosted.org/packages/b7/96/1b07325ba71752d6a028b77d07bed1483ad545f794e8b1dc89b3ba3b3c68/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:724f3dcbe53dd0151e3cb5e7ec4ba4c620bede579caacd16275dc35ce06e8615", size = 3321216, upload-time = "2026-05-24T20:17:15.581Z" }, + { url = "https://files.pythonhosted.org/packages/ed/8e/bad6ed253e8a99edfc99af02f7173ec48a1d3ed1b9b35a1b8bc1700900cc/sqlalchemy-2.0.50-cp312-cp312-win32.whl", hash = "sha256:1208050441471d003b7c8cb4054fb084f185cf35ac3f0ea270803865bca9939a", size = 2119194, upload-time = "2026-05-24T19:50:04.943Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2d/314a6690dda4b9cfc571eab1a63cf6fe6e1470aa3759ccda6aa016ee0f5a/sqlalchemy-2.0.50-cp312-cp312-win_amd64.whl", hash = "sha256:9d1af51558029a156a70986b7df88f042b3d158d7c8d8fb5072912d4b32d89c7", size = 2146186, upload-time = "2026-05-24T19:50:06.74Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c4/c42356b527296e9862f67990efce31ef78b4cf69cd3f80873a528a060320/sqlalchemy-2.0.50-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:06a9210bdc5f4298cff0781087e2ff45683922252dacc452846373a58761f093", size = 2156697, upload-time = "2026-05-24T19:27:54.764Z" }, + { url = "https://files.pythonhosted.org/packages/60/a1/b1a70e3c4365ac7fe9e347f3710f19b562c866fb96d45e3c891588789a7b/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b53784972ade4f8174b9aa661f31a06f8a936d2cfdd602913ff3c6dd40ae873", size = 3284260, upload-time = "2026-05-24T20:09:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4a/f3ac3caa19f263d57b0a47f8c91bbf56583dc2d3fc63acfbf644abb24fe0/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31648fa14460537e768a7303b078e4344d208e0d23e06867c1f376a227ed82db", size = 3302280, upload-time = "2026-05-24T20:17:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/66/55/ccada3e3d62254587819749a0bc69f41173eb48a6e385d10e66d32a9c88e/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:03f4323c980ad0e918cc9e5369b015f759f4e534db5bbaf4dc36832c10d05064", size = 3231580, upload-time = "2026-05-24T20:09:36.406Z" }, + { url = "https://files.pythonhosted.org/packages/05/f6/6809349130a2de0e109e7f00fd7d431da9565b9b2868b32ee684754f672b/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b9dcc43afef8ac157cd92fce96985d6b8b0cfbd3df4d666f66b4d55a75d202f", size = 3269375, upload-time = "2026-05-24T20:17:20.34Z" }, + { url = "https://files.pythonhosted.org/packages/48/84/278a811ef4e07be9c89dc5cdd7be833268509a66a68c4897cf585e67428f/sqlalchemy-2.0.50-cp313-cp313-win32.whl", hash = "sha256:60922d6599065ddca2c6f376b9aa2f41a6b85a271725e0909490bbc50b1998a5", size = 2117229, upload-time = "2026-05-24T19:50:08.215Z" }, + { url = "https://files.pythonhosted.org/packages/f6/1c/067cc6187ed32d2ec222fe6d2643acc1659a6d0659f8a7cbc5ad3ae83280/sqlalchemy-2.0.50-cp313-cp313-win_amd64.whl", hash = "sha256:287086e67275a212c4582d166a6fb03a65ccc5551d80866270ce0dd9f34eccd3", size = 2143126, upload-time = "2026-05-24T19:50:09.691Z" }, + { url = "https://files.pythonhosted.org/packages/df/32/10ac51b4be7cdecd7e93d069251c86dfbf70b7adbd7c67b48ccea6c49e1c/sqlalchemy-2.0.50-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c966932507a4d7d0a37314927dbfcd89720e3f37d2a1e3352e7ae7939fa8e8a0", size = 2158519, upload-time = "2026-05-24T19:27:56.472Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/e703d2f7681d7d66c4c891af3f07c7ccf4c76ad7f18351de035b5eda007a/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:faffef4bcc20a1892e65e155293d99d60855bbbc79250ab712819cfd56a8e6bb", size = 3282063, upload-time = "2026-05-24T20:09:38.57Z" }, + { url = "https://files.pythonhosted.org/packages/31/26/ef168b184a25701f9995e8fb7e503fafd7a99c1c77cda1bc1a26ea2ed486/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c206aec519a2e7bd08abbfb33436e325fd22c632d9c21a9047e376ce241646e", size = 3287069, upload-time = "2026-05-24T20:17:21.942Z" }, + { url = "https://files.pythonhosted.org/packages/c2/15/765acc2bc693bccc43ca4a95d5b69750da8aaf6db1b5c616536e087f8920/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bef4ac756363227ef6402a75fee025a4bc690f92328e825868939b3b3a446a6d", size = 3230453, upload-time = "2026-05-24T20:09:40.398Z" }, + { url = "https://files.pythonhosted.org/packages/63/61/08e03c3adbf5db0087a0b6816746fec8f3032fb2f7fc899a9bb9b2a48ce4/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96fbee6b19c19cd1556c8bf9419447cf2ec149ffcab7ab64348c23e54ef8547f", size = 3252413, upload-time = "2026-05-24T20:17:24.067Z" }, + { url = "https://files.pythonhosted.org/packages/03/0c/370a1f2db38436c615e10134c8a37de3688e74084792380695f3f5083860/sqlalchemy-2.0.50-cp314-cp314-win32.whl", hash = "sha256:8f00e3eb43ba30eb1b238ee03a8a62309486d1321eda3328bb611e0340033ad8", size = 2120063, upload-time = "2026-05-24T19:50:11.08Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a0/fe92bb9817863bc13ba093bda931979a26cc2ca69f8e8f26d07add3d7c6f/sqlalchemy-2.0.50-cp314-cp314-win_amd64.whl", hash = "sha256:15708c613cd5005b7dffe1f66ee6a63ee8f5e46799f71c70ebad74178c676a39", size = 2145830, upload-time = "2026-05-24T19:50:12.452Z" }, + { url = "https://files.pythonhosted.org/packages/cc/ff/e5640a98a0b2f491eb8fde10fb6c773621a2e44340de231fafcc9370f4a9/sqlalchemy-2.0.50-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3699dac4be410e97049a1658e9480da9cde956594aa0f3aebc60b88f21c5ba70", size = 2178435, upload-time = "2026-05-24T19:42:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/337116e186f1236375b5fb70c21cfac98e8e8ab0d3a47be838dc47a59e08/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96233858e3df43932ac11589e22520da6e8aeb624b03fedfeebb0e8ea213086", size = 3566059, upload-time = "2026-05-24T20:01:20.848Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/bb0e190e161c3c2c24314a65add57218be14a4a9486886b7f5047c1ff7c8/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4e70c46fad30c3bcc6a4708bc0130a3173e11a5b25f0ea4a9d8911b450f1f52", size = 3535366, upload-time = "2026-05-24T20:03:56.768Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/a7f759f97e4fd499c5d4e4488c760d5a7fbecf3028b465a04274fcd52384/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1918a3cf564d16d95bca7301005f41ab2ad50b07cd3b9da50d3ed986db148d6a", size = 3474879, upload-time = "2026-05-24T20:01:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d9/2907ea38eb60687d297bf9c39e5ee58053c87b57fe8a9cae97090cecbf10/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b00098cdbdbd38c7be3d568b0c9c3122b8c0ec62b911b57cd5e6e0254d60a76d", size = 3486117, upload-time = "2026-05-24T20:03:59.052Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e3/5aa06f167559f8c0bdae487e297d23ba548150ab016a3418265d617a4985/sqlalchemy-2.0.50-cp314-cp314t-win32.whl", hash = "sha256:1fbd55a969d7ac44a98e3dec75016074f809fa08f871585ace58dde110d1bf3e", size = 2150823, upload-time = "2026-05-24T20:08:58.644Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/112fb8f977582d7489d036e409e3723948bcf5320b3ac465f3c481bbe8f9/sqlalchemy-2.0.50-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c3cdb753a9004183e1ccb634b41611654c989e61bc68617ce878e46d6f1e51", size = 2185794, upload-time = "2026-05-24T20:09:00.319Z" }, + { url = "https://files.pythonhosted.org/packages/d0/10/f7220e9b784d295d241c86ed99aeb537f92afcd469a64861f2717e9bb077/sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9", size = 1943861, upload-time = "2026-05-24T19:59:01.119Z" }, +] + +[[package]] +name = "sqlmodel" +version = "0.0.38" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/0d/26ec1329960ea9430131fe63f63a95ea4cb8971d49c891ff7e1f3255421c/sqlmodel-0.0.38.tar.gz", hash = "sha256:d583ec237b14103809f74e8630032bc40ab68cd6b754a610f0813c56911a547b", size = 86710, upload-time = "2026-04-02T21:03:55.571Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/c7/10c60af0607ab6fa136264f7f39d205932218516226d38585324ffda705d/sqlmodel-0.0.38-py3-none-any.whl", hash = "sha256:84e3fa990a77395461ded72a6c73173438ce8449d5c1c4d97fbff1b1df692649", size = 27294, upload-time = "2026-04-02T21:03:56.406Z" }, +] + +[[package]] +name = "starlette" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.49.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/3d/8024c801df84d1587740d0359e7fdd80afeae3d159011f3d5376dd82f18e/watchfiles-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201", size = 400242, upload-time = "2026-05-18T04:31:19.014Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/f4dfd45323e949984a3a7f9dc31d1cbb049921e7d98253488dda72ccdaa9/watchfiles-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5", size = 394562, upload-time = "2026-05-18T04:30:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/19483ef075d601c409bce8bcbb5c0f81a10876fff870400568f08ce484a1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a", size = 456611, upload-time = "2026-05-18T04:30:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6a/cc81fbe7ee42f2f22e661a6e12def7807e01b14b2f39e0ff83fd373fd307/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1", size = 461379, upload-time = "2026-05-18T04:31:29.292Z" }, + { url = "https://files.pythonhosted.org/packages/b1/57/7e669002082c0a0f4fb5113bb70125f7110124b846b0a11bc5ae8e90eac1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717", size = 493556, upload-time = "2026-05-18T04:30:05.44Z" }, + { url = "https://files.pythonhosted.org/packages/45/7d/f60a2b19807b21fe8281f3a8da4f59eef0d5f96825ac4680ba2d4f2ebf91/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b", size = 575255, upload-time = "2026-05-18T04:30:40.568Z" }, + { url = "https://files.pythonhosted.org/packages/bd/49/77f5b5e6efbcd57482f74948ebb1b97e5c0046d6b61475042d830c84b3ff/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5", size = 467052, upload-time = "2026-05-18T04:31:17.942Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5a/73e2959af1b97fd5d556f9a8bdba017be23ceeef731869d5eaa0a753d5a3/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e", size = 456858, upload-time = "2026-05-18T04:30:30.182Z" }, + { url = "https://files.pythonhosted.org/packages/50/57/1bc8c27fad7e6c19bddee15d276dbb6ab72480ec01c127afff1673aee417/watchfiles-1.2.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165", size = 467579, upload-time = "2026-05-18T04:32:15.897Z" }, + { url = "https://files.pythonhosted.org/packages/09/6c/3c2e44edba3553c5e3c3b8c8a2a6dee6b9e12ae2cf4bd2378bebf9dc3038/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6", size = 633253, upload-time = "2026-05-18T04:31:37.123Z" }, + { url = "https://files.pythonhosted.org/packages/30/c2/d8c84a882ab39bbefcc4915ab3e91830b7a7e990c5570b0b69075aba3faf/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5", size = 660713, upload-time = "2026-05-18T04:31:24.62Z" }, + { url = "https://files.pythonhosted.org/packages/a9/07/f97736a5fc605364fe67b25e9fa4a6965dfd4840d50c406ada507e9d735f/watchfiles-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8", size = 277222, upload-time = "2026-05-18T04:31:21.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/99/2b04981977fc2608afd60360d928c6aecf6b950292ca221d98f4005f6694/watchfiles-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22", size = 290274, upload-time = "2026-05-18T04:31:45.966Z" }, + { url = "https://files.pythonhosted.org/packages/3c/74/f7f58a7075ee9cf612b0cfcddb78b8cd8234f0742d6f0075cf0da2dde1c6/watchfiles-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7", size = 283460, upload-time = "2026-05-18T04:31:39.126Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" }, + { url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" }, + { url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" }, + { url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" }, + { url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, + { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, + { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, + { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, + { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, + { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, + { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, + { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, + { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, + { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, + { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, + { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, + { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, + { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/23/f4/7513ef1e85fc4c6331b59479d6d72661fc391fbe543678052ac72c8b6c19/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2", size = 403050, upload-time = "2026-05-18T04:30:36.753Z" }, + { url = "https://files.pythonhosted.org/packages/27/0b/a54103cfd732bb703c7a749222011a0483ef3705948dae3b203158601119/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db", size = 396629, upload-time = "2026-05-18T04:32:03.268Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2c/73f31a3b893886206c3f54d73e8ad8dee58cdb2f69ad2622e0a8a9e07f4e/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7", size = 457318, upload-time = "2026-05-18T04:31:01.932Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f9/45d021e4a5cc7b9dd567f7cbb06d3b75f751a690063fb6cc7ec60f4e46b7/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0", size = 457771, upload-time = "2026-05-18T04:30:56.331Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, + { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, + { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, + { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +] diff --git a/design-reference/HANDOFF-README.md b/design-reference/HANDOFF-README.md new file mode 100644 index 0000000..373d323 --- /dev/null +++ b/design-reference/HANDOFF-README.md @@ -0,0 +1,25 @@ +# CODING AGENTS: READ THIS FIRST + +This is a **handoff bundle** from Claude Design (claude.ai/design). + +A user mocked up designs in HTML/CSS/JS using an AI design tool, then exported this bundle so a coding agent can implement the designs for real. + +## What you should do — IMPORTANT + +**Read the chat transcripts first.** There are 3 chat transcript(s) in `ari/chats/`. The transcripts show the full back-and-forth between the user and the design assistant — they tell you **what the user actually wants** and **where they landed** after iterating. Don't skip them. The final HTML files are the output, but the chat is where the intent lives. + +**Read `ari/project/index.html` in full.** The user had this file open when they triggered the handoff, so it's almost certainly the primary design they want built. Read it top to bottom — don't skim. Then **follow its imports**: open every file it pulls in (shared components, CSS, scripts) so you understand how the pieces fit together before you start implementing. + +**If anything is ambiguous, ask the user to confirm before you start implementing.** It's much cheaper to clarify scope up front than to build the wrong thing. + +## About the design files + +The design medium is **HTML/CSS/JS** — these are prototypes, not production code. Your job is to **recreate them pixel-perfectly** in whatever technology makes sense for the target codebase (React, Vue, native, whatever fits). Match the visual output; don't copy the prototype's internal structure unless it happens to fit. + +**Don't render these files in a browser or take screenshots unless the user asks you to.** Everything you need — dimensions, colors, layout rules — is spelled out in the source. Read the HTML and CSS directly; a screenshot won't tell you anything they don't. + +## Bundle contents + +- `ari/README.md` — this file +- `ari/chats/` — conversation transcripts (read these!) +- `ari/project/` — the `Ari` project files (HTML prototypes, assets, components) diff --git a/design-reference/PROJECT-README.md b/design-reference/PROJECT-README.md new file mode 100644 index 0000000..e01de4a --- /dev/null +++ b/design-reference/PROJECT-README.md @@ -0,0 +1,174 @@ +# 아리 (Ari) — AI Life OS + +> 일·삶을 한곳에서 관리하는 AI 비서 **아리**의 한국어 인터랙티브 프로토타입. +> "적을 때는 분류하지 않는다 — 분류·배치·자동화는 아리가." 사용자는 **읽고 탭 한 번**, 나머지는 아리가 합니다. + +데모 사용자는 **지우님**(PM), 6월 7~12일 가상의 한 주가 배경입니다. 모든 데이터는 목업이며 실제 연동·백엔드는 없습니다. + +--- + +## 1. 빠른 시작 + +- 프로젝트 루트의 **`index.html`** 을 열면 자동으로 `ari/대시보드.html`로 진입합니다 (메인 페이지). +- 모든 페이지는 단일 HTML + React 18 + Babel(인브라우저 트랜스파일)로 동작하며, 빌드 과정이 없습니다. +- 각 페이지 상단 내비게이션으로 13개 화면을 자유롭게 오갑니다. + +--- + +## 2. 폴더 구조 + +``` +(루트) +├─ index.html ← 메인 진입점 (→ ari/대시보드.html 리다이렉트) +├─ uploads/ 업로드 원본 +├─ README.md 이 문서 +└─ ari/ + ├─ 대시보드.html ① 홈 — 하루 요약 + 아리 브리핑 + ├─ 인박스.html ② 스마트 인박스 — 멀티모달 캡처 자동 분류 + ├─ 결재함.html ③ 아리 결재함 — 자동 처리 승인/되돌리기 + ├─ 자동화.html ③′ 자동화 규칙 빌더 — 자연어 한 문장 → 규칙 + ├─ 여정.html ④ 하루 여정 — 시간 흐름 + 협업 맵 + ├─ 일정.html ⑤ 캘린더 + 회의 도우미 + ├─ 작업.html ⑥ 작업(프로젝트 트리) + 리스크 레이더 + ├─ 메일.html ⑦ 멀티 계정 메일 + AI 요약 + ├─ 알림.html ⑦′ 알림 트리아지 — 지금/나중에/아리가 처리 + ├─ 리서치.html ⑧ 지능형 리서치 파트너 + ├─ 여행.html ⑧′ 여행·출장 도우미 — 이동·체크리스트·경비 + ├─ 라이프.html ⑨ 라이프 케어 — 건강·금융·지식 + ├─ 하루 마감.html ⑩ 이브닝 브리핑 + │ + ├─ assets/ 활성 앱 에셋 (CSS · JSX · 데이터) 일체 + ├─ themes/ 대안 디자인 보관 (현재 비활성) + │ ├─ ron/ RON 테마 (다크/벤토 방향) — 자급자족 + README + │ └─ lumi/ Lumi 테마 (앰버 방향) — 자급자족 + README + └─ scraps/ 스크래치 이미지 +``` + +### 페이지 ↔ 에셋 매핑 + +| 페이지 | CSS | 데이터 | 로직(JSX) | +|---|---|---|---| +| 대시보드 | `dash.css` | `data.js`, `approve-data.js`, `sinbox-data.js` | `dash-v2.jsx` | +| 인박스 | `dash.css`, `sinbox.css`, `approve.css` | `data.js`, `sinbox-data.js` | `shell.jsx`, `sinbox.jsx`, `sinbox-page.jsx` | +| 결재함 | `dash.css`, `approve.css` | `data.js`, `approve-data.js` | `shell.jsx`, `approve.jsx`, `approve-page.jsx` | +| 여정 | `dash.css`, `journey-clean.css` | `journey-clean-data.js` | `journey-clean.jsx` | +| 일정 | `cal.css` | `cal-data.js` | `cal.jsx`, `cal-meet.jsx` | +| 작업 | `tasks.css` | `tasks-data.js` | `tasks.jsx`, `tasks-risk.jsx` | +| 메일 | `mail.css` | `mail-data.js` | `mail.jsx` | +| 리서치 | `tasks.css`, `research.css` | `research-data.js` | `research.jsx` | +| 자동화 | `dash.css`, `auto.css` | `data.js`, `auto-data.js` | `shell.jsx`, `auto.jsx` | +| 알림 | `dash.css`, `notify.css` | `data.js`, `notify-data.js` | `shell.jsx`, `notify.jsx` | +| 여행 | `dash.css`, `trip.css` | `data.js`, `trip-data.js` | `shell.jsx`, `trip.jsx` | +| 라이프 | `life.css` | `life-data.js` | `life.jsx` | +| 하루 마감 | `dash.css`, `wrap.css` | `wrap-data.js` | `wrap.jsx` | + +> `shell.jsx`(`window.AriShell`)는 인박스·결재함·자동화·알림·여행이 공유하는 상단바/아이콘 셸입니다. +> `AriShell.SubRail`은 좌측 아이콘 레일로 한 페이지 안의 하위 화면을 전환합니다(자동화·알림·여행이 사용). items: `[{id,icon,label,dot?}|{sep:true}]`, props: `active`/`onPick`. +> `dash.css`는 공용 디자인 토큰(색·여백·글래스·라이트/다크 테마)의 기준 파일입니다. + +--- + +## 3. 공통 시스템 + +- **디자인**: 클린 화이트 글래스 테마. 크림 배경 + 반투명 글래스 카드 + 부드러운 그림자. 포인트 색 `blue / violet / coral / green / amber`. +- **타이포**: Pretendard(본문) + Onest·DM Mono(숫자/영문). +- **테마 토글**: 상단바 달/해 아이콘으로 라이트 ↔ 다크 전환 (`data-theme` 속성, 선택값은 localStorage 저장). +- **상단 내비게이션 (전 페이지 동일 13항목)**: + 대시보드 · 인박스 · 결재함(배지 3) · 자동화 · 여정 · 일정 · 작업(배지 4) · 메일 · 알림(배지 6) · 리서치 · 여행 · 라이프 · 하루 마감 +- **상태 저장**: 일부 상호작용(테마, 작업 보드 상태 등)은 페이지별 localStorage 키(`ariC.`, `ariT.`, `ariR.` 등)에 저장됩니다. + +--- + +## 4. 페이지별 기능 + +### ① 대시보드 (`대시보드.html`) +하루의 관제탑. 아리의 아침 브리핑(날씨·출근·수면), 오늘 일정/할 일/목표 요약, 자연어 명령 입력창, 그리고 인박스·결재함·집중 모드·위임 등 핵심 모듈로 가는 베n토 위젯들을 모았습니다. + +### ② 스마트 인박스 (`인박스.html`) +**일단 적으세요 — 분류는 아리가.** 텍스트·음성 메모·생각을 멀티모달로 던지면 아리가 4원칙으로 자동 분류합니다: +- 행동이 있으면 → **작업** (작업 트리의 해당 프로젝트로) +- 시간이 정해지면 → **일정** (캘린더 등록) +- 막연하면 → **아이디어** (보드 보관 + 관련 프로젝트 연결) +- 개인 일도 숨기지 않고 같은 트리에서 필터로만 구분, 배치만 저녁·주말 빈 시간으로 추천 +분류된 항목은 작업/일정/아이디어 목적지로 바로 이어집니다. + +### ③ 아리 결재함 (`결재함.html`) +아리가 **밤사이/자동으로 처리한 일**을 모아 한 번에 승인·되돌리기. 위험도(low/high)로 구분 — 낮은 건은 이미 처리(되돌리기만 제공), 높은 건은 사용자 확인 후 실행(예: 회신 보내기, 위임 전달, 구독 일시정지). "오늘 아낀 시간"과 자동 처리 건수를 함께 보여줍니다. + +### ④ 하루 여정 (`여정.html`) +하루를 **아침 준비 → 오전 집중 → 오후 협업 → 마무리** 4단계 흐름으로 시각화. 각 단계의 루틴·작업·미팅 카드와, 팀원(현우·민서·재호·수아)이 어떤 일에 연결돼 있는지 협업 맵을 함께 보여줍니다. + +### ⑤ 일정 + 회의 도우미 (`일정.html`) +주/월/일 캘린더. 핵심은 일(日) 뷰의 **회의 도우미** 드로어 — 회의 상태별로 다르게 동작합니다: +- **예정 회의**: 사전 브리핑(안건·지난 회의 요약·지난 액션 완료 체크·인사이트) +- **진행 중**: 실시간 기록 상태 +- **끝난 회의**: 음성 기록 기반 요약·결정 사항·**액션 아이템 추출 → "작업으로" 한 번에 전송** +- **1:1**: 지난 대화 요약, 약속 사항, 상대방 시그널(업무량·위임 과제·성과), 대화 주제 추천 + +### ⑥ 작업 + 리스크 레이더 (`작업.html`) +중첩 프로젝트 트리(업무/개인 폴더 → 프로젝트 → 작업)를 칸반·리스트·캘린더 뷰로. 상단의 **리스크 레이더**가 작업 데이터를 직접 계산해 경고합니다: +- **지연 위험**: 오늘 마감인데 미완료 +- **업무 쏠림**: 담당자별 미완료 편차 → 위임/재배분 제안 +- **의존성**: 선행 작업이 늦어지면 밀리는 후속 작업 + +### ⑦ 메일 (`메일.html`) +회사·개인·사이드 **3개 계정 통합** 받은편지함. 메일별 AI 요약·우선순위·분류 라벨을 달고, 본문에서 **할 일/일정을 자동 추출**해 작업·캘린더로 보낼 수 있습니다. 회신 초안도 준비합니다. + +### ⑧ 리서치 (`리서치.html`) +지능형 리서치 파트너. **좌측 사이드바의 뷰 전환 4개**로 하위 화면을 오가고(선택값 localStorage 저장), 그 아래에 컬렉션·학습된 자료가 이어집니다 — 각 기능을 한 스크롤에 쌓지 않고 풀폭으로 보여줍니다: +- **새 조사**: 자연어 명령 컴포저 + 세 결과로 가는 입구 카드(클릭 → 해당 뷰) +- **멀티소스 종합 리포트**: 논문·뉴스·보고서를 모아 출처군별 핵심 주장·논조를 **교차 분석 표**로 정리 + 종합 + 교차 확인 경고 +- **지식 베이스 Q&A**: 저장한 PDF·웹·메모를 학습해 **근거 링크와 함께** 답변 (개인 위키) +- **시각화·추세 예측**: 데이터를 막대 차트로 그리고 예측치(점선 막대)와 한계 주의까지 + +### ⑨ 라이프 케어 (`라이프.html`) +일 밖의 삶을 코칭. 세 영역 탭: +- **건강**: 걸음·운동·수면 링, 바이탈, 수면 분석, 습관 추적 (Apple Health·Watch 연동 가정) +- **금융**: 예산·카테고리별 지출, 구독 관리, 절약 인사이트(잠자는 구독 발견 등) (카드·토스·뱅크 연동 가정) +- **지식(제2의 뇌)**: 아티클·메모·하이라이트 컬렉션 + 자연어 검색 (Notion·Readwise·웹 클리퍼 연동 가정) + +### ⑩ 하루 마감 (`하루 마감.html`) +이브닝 브리핑(전용 풀스크린, 상단 메뉴 없음). 오늘의 숫자(완료·딥 워크·미팅·자동 처리), 하이라이트, 남은 일 자동 이월, 내일 미리보기, 수면 모드 CTA. 다른 페이지와 동일한 라이트 글래스 테마. + +### ⑪ 자동화 규칙 빌더 (`자동화.html`) +**한 문장이면 규칙이 돼요.** 좌측 레일로 4개 하위 화면을 오갑니다 — **내 규칙**(토글 목록 + 작동 방식 설명) · **새 자동화**(자연어 입력 → 트리거·조건·동작 미리보기 → 생성) · **아리 제안**(반복 패턴 탐지 → 규칙 제안, 레일에 알림 점) · **실행 기록**. 새 규칙을 만들면 토스트와 함께 목록으로 돌아갑니다. + +### ⑫ 알림 트리아지 (`알림.html`) +좌측 레일 3단: **트리아지**(지금/나중에/아리가 처리 탭 + 오늘 요약) · **집중 보호·묶음 배달** · **발신자 규칙**. 왜 그렇게 분류했는지 이유 칩과 되돌리기 제공. + +### ⑬ 여행·출장 도우미 (`여행.html`) +좌측 레일 3단: **다가오는 출장**(부산 출장 D-4 개요·이동·체크리스트·일정표·경비) · **새 여행 계획** · **찜·가격 추적**. +**새 여행 계획**이 핵심 — "다음 달 말 제주 가족여행 3박 4일, 예산 150만원"처럼 한 문장을 말하면 아리가 항공·숙소·날씨·동선을 조사하는 과정을 보여주고(단계별 진행 애니메이션), 추천 이동/숙소 옵션·일정 초안·예상 경비·준비물을 출처와 함께 생성합니다. 예시 2개(제주·도쿄)는 각각 완성된 결과를, 자유 입력은 제주 결과로 폴백. 체크 상태는 localStorage 저장. + +--- + +## 5. 페이지가 **연합**해 만드는 가치 + +아리의 핵심은 각 화면이 따로 노는 게 아니라 **데이터를 주고받으며 하나의 흐름**을 이루는 데 있습니다. + +- **캡처 → 분류 → 실행**: 인박스에 던진 한 줄이 작업 트리·캘린더·아이디어 보드로 자동 배치. +- **메일·회의 → 작업**: 메일 본문과 회의 녹취에서 액션 아이템을 추출해 담당자 작업으로 전송 → 작업 보드에 등장. +- **일정 ↔ 작업 ↔ 집중 모드**: 캘린더 빈 블록을 읽어 딥 워크/가벼운 일을 자동 배치(집중 모드), 마감·업무량은 리스크 레이더가 감시. +- **위임 루프**: 내 일정 과부하를 감지해 위임을 제안(결재함) → 1:1 도우미가 넘긴 일의 진행을 추적. +- **자동 처리 → 결재함 → 하루 마감**: 밤사이 처리한 일은 결재함에서 승인하고, 하루 마감에서 "아리가 아낀 시간"으로 합산. +- **공통 인물·프로젝트**: 지우·현우·민서·재호·수아와 온보딩·분기 리포트 등 프로젝트가 전 페이지에 일관되게 등장해, 한 사람의 하루가 끊김 없이 이어집니다. + +--- + +## 6. 보관 테마 (`ari/themes/`) + +현재 활성 앱은 **클린 화이트 글래스** 방향입니다. 과거 탐색했던 두 대안 방향을 폴더째 보관합니다 (각 폴더의 `README.md` 참고): + +- **`ron/`** — RON 테마. 다크 톤 + 벤토 그리드 대시보드, 여정 중심 v2, 작업 화면. 폴더 내에서 자급자족 동작. +- **`lumi/`** — Lumi(앰버) 테마. Lumi People(HR/피플 대시보드) 원본과 그 스타일을 적용한 아리 대시보드 라이트. + +> 다시 메인으로 승격하려면 필요한 파일을 `../assets/`로 옮기고 각 HTML의 경로·내비 링크를 조정하세요. + +--- + +## 7. 개발 메모 + +- **각 JSX는 독립 스코프**입니다. 아이콘 path는 각 파일의 `P`/`PATHS` 맵에 정의되어 있어, 한 파일에 추가해도 다른 파일에는 반영되지 않습니다(누락 시 렌더 크래시 주의). +- 전역 스타일 객체는 컴포넌트별 고유명(예: `terminalStyles`)을 사용합니다. 절대 `const styles = {…}` 같은 충돌 이름을 쓰지 않습니다. +- 페이지 추가/이동 시 **13개 페이지의 상단 내비(`MAIN` 배열)와 href를 함께 갱신**해야 메뉴 일관성이 유지됩니다. +- 한글 파일명을 쓰므로, 링크/경로 수정 시 정확한 파일명을 확인하세요. diff --git a/design-reference/assets/approve-data.js b/design-reference/assets/approve-data.js new file mode 100644 index 0000000..554f1a0 --- /dev/null +++ b/design-reference/assets/approve-data.js @@ -0,0 +1,56 @@ +/* 아리 — 결재함(승인 큐) 데이터 + 핵심 컨셉: 아리는 "할까요?"라고 묻지 않고 미리 해둔다. + - risk: "low" → 자율성 '혼합' 이상에서 자동 실행 (되돌리기 가능) + - risk: "high" → 보내기/결제/타인에게 전달 등은 승인 대기 + 사용자는 읽고 탭 한 번이면 끝. */ +window.AriApprovals = { + savedToday: "47분", // 오늘 아리가 아껴준 시간 (데모) + autoCountNight: 7, // 밤사이 자동 처리 건수 + + items: [ + { + id: "a1", icon: "cal", tone: "coral", risk: "low", time: "07:42", + title: "치과 예약을 16:00로 옮겼어요", + detail: "14시 분기 전략 미팅과 겹침 · 병원 예약 시스템에서 빈 슬롯 확인 후 변경", + undoLabel: "원래 시간으로", + }, + { + id: "a2", icon: "mail", tone: "blue", risk: "low", time: "06:10", + title: "영수증·뉴스레터 7통을 정리했어요", + detail: "영수증 3통 → 금융 폴더 · 뉴스레터 4통 → 읽을거리, 받은편지함은 중요한 것만 남김", + undoLabel: "되돌리기", + }, + { + id: "a3", icon: "cal", tone: "violet", risk: "low", time: "07:40", + title: "내일 오전 딥 워크 2시간을 예약했어요", + detail: "분기 리포트 마감(내일 18시) 대비 · 9:00–11:00, 방해 금지로 설정", + undoLabel: "블록 해제", + }, + { + id: "a4", icon: "mail", tone: "violet", risk: "high", time: "보내기 대기", + title: "현우님께 회신 초안이 준비됐어요", + detail: "“잘 받았어요! 금요일 오전까지 화면별 코멘트 정리해서 드릴게요.”", + cta: "보내기", alt: "수정", + }, + { + id: "a5", icon: "users", tone: "green", risk: "high", time: "전달 대기", + title: "민서님께 ‘데이터 전처리’ 위임 요청", + detail: "오늘 일정 과부하 감지 · 맥락 요약과 마감(목)을 담은 요청 메시지 작성 완료", + cta: "전달", alt: "내가 할게", + }, + { + id: "a6", icon: "wallet", tone: "amber", risk: "high", time: "확인 필요", + title: "Netflix 일시정지를 추천해요", + detail: "최근 2개월 시청 기록 없음 · 모레 17,000원 결제 예정 — 정지 절차는 준비해뒀어요", + cta: "일시정지", alt: "유지", + }, + ], + + /* 밤사이/오늘 자동 처리 로그 (신뢰 + 투명성) */ + log: [ + { time: "08:55", text: "스탠드업 직전 — 어제 진행 요약 노트 생성" }, + { time: "07:30", text: "출근 경로 확인 · 평소보다 4분 빠른 경로로 안내 예약" }, + { time: "06:00", text: "구독 결제 캘린더 동기화 (Netflix·Spotify 6/9)" }, + { time: "어제 23:10", text: "수면 모드 — 알림 음소거 · 내일 브리핑 예약" }, + ], +}; diff --git a/design-reference/assets/approve-page.jsx b/design-reference/assets/approve-page.jsx new file mode 100644 index 0000000..c632df4 --- /dev/null +++ b/design-reference/assets/approve-page.jsx @@ -0,0 +1,97 @@ +/* 아리 — 결재함 전용 페이지 + 대시보드는 요약만, 실행(승인·되돌리기)은 여기서. */ +const { useState } = React; +const I = window.AriShell.Icon; +const Topbar = window.AriShell.Topbar; +const A = window.AriApprovals; + +const MODES = [ + { + id: "approve", title: "승인 우선", + desc: "모든 실행 전에 내 확인을 받아요. 아리를 알아가는 단계에 좋아요.", + }, + { + id: "mixed", title: "혼합", rec: true, + desc: "되돌릴 수 있는 일(일정 이동·메일 정리)은 자동, 보내기·결제·위임은 승인 대기.", + }, + { + id: "auto", title: "완전 자율", + desc: "다 해두고 보고만 해요. 모든 실행은 탭 한 번으로 되돌릴 수 있어요.", + }, +]; + +function App() { + const [autonomy, setAutonomy] = useState("mixed"); + + return ( +
+ + +
+
+
+ 오늘 {A.savedToday} 아껴드렸어요 + + 밤사이 자동 처리 {A.autoCountNight}건 +
+

아리 결재함 확인만 하면 끝

+
+
+ +
+
+ {/* 결재함 본체 (approve.jsx) */} + + + {/* 자율성 설정 — 이 페이지의 핵심 컨트롤 */} +
+
+
+

아리 자율성

어디까지 맡길지 정해요
+
+
+ {MODES.map((m) => ( + + ))} +
+
+ 어떤 모드든, 아리가 한 일은 전부 기록되고 되돌릴 수 있어요. +
+
+ + {/* 활동 로그 */} +
+
+
+

활동 로그

아리가 조용히 한 일
+ {A.autoCountNight}건 +
+
+ {A.log.map((l, i) => ( +
+ {l.time} + {l.text} +
+ ))} +
+
+ 보내기·결제·삭제는 자율 모드에서도 항상 로그에 강조 표시돼요. +
+
+
+
+
+ ); +} + +ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/design-reference/assets/approve.css b/design-reference/assets/approve.css new file mode 100644 index 0000000..cb4e988 --- /dev/null +++ b/design-reference/assets/approve.css @@ -0,0 +1,213 @@ +/* =========================================================== + 아리 — 결재함 위젯 (클린 화이트 테마 확장) + dash.css 뒤에 로드. 같은 글래스 비주얼 보캐뷸러리 사용. + =========================================================== */ + +.appr .ico.lime { + background: var(--lime); + color: var(--lime-ink); + border-color: color-mix(in oklab, var(--lime-ink) 12%, transparent); +} +.appr .count.warm { + color: #fff; + background: var(--coral); + border-color: transparent; +} + +/* ---------- 승인 대기 행 ---------- */ +.appr-list { display: flex; flex-direction: column; gap: 9px; } +.ap-row { + display: flex; align-items: center; gap: 12px; + padding: 12px 13px; + border-radius: var(--radius-sm); + border: 1px solid var(--glass-brd); + background: var(--glass-2); + transition: background 0.14s, border-color 0.14s; +} +.ap-row:hover { background: var(--card-2); } +.ap-ic { + width: 34px; height: 34px; border-radius: 10px; flex-shrink: 0; + display: grid; place-items: center; + background: color-mix(in oklab, var(--tone, var(--blue)) 14%, transparent); + color: var(--tone, var(--blue)); +} +.ap-ic .ic { width: 16px; height: 16px; } +.ap-body { flex: 1; min-width: 0; } +.ap-title { font-size: 13.5px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.3; } +.ap-detail { font-size: 12px; color: var(--muted); margin-top: 3px; line-height: 1.45; } +.ap-acts { display: flex; align-items: center; gap: 6px; flex-shrink: 0; } +.ap-ok { + display: inline-flex; align-items: center; gap: 6px; + font-size: 12.5px; font-weight: 700; + color: var(--lime-ink); background: var(--lime); + padding: 8px 14px; border-radius: 999px; + box-shadow: 0 4px 12px -6px color-mix(in oklab, var(--lime-ink) 50%, transparent); + transition: background 0.13s, transform 0.12s; + white-space: nowrap; +} +.ap-ok .ic { width: 13px; height: 13px; } +.ap-ok:hover { background: var(--lime-hi); transform: translateY(-1px); } +.ap-alt { + font-size: 12px; font-weight: 600; color: var(--muted); + padding: 8px 11px; border-radius: 999px; + transition: background 0.13s, color 0.13s; + white-space: nowrap; +} +.ap-alt:hover { background: var(--card-2); color: var(--ink); } +.ap-all { + display: inline-flex; align-items: center; justify-content: center; gap: 7px; + margin-top: 3px; padding: 11px; + border-radius: 13px; + background: var(--fill); color: var(--on-fill); + font-size: 13px; font-weight: 600; + transition: filter 0.14s; +} +.ap-all .ic { width: 14px; height: 14px; } +.ap-all:hover { filter: brightness(1.12); } + +/* ---------- 빈 상태 ---------- */ +.appr-empty { + display: flex; align-items: center; gap: 10px; + font-size: 13px; color: var(--ink-2); line-height: 1.5; + background: linear-gradient(100deg, color-mix(in oklab, var(--green) 10%, transparent), transparent 70%), var(--glass-2); + border: 1px solid color-mix(in oklab, var(--green) 28%, var(--glass-brd)); + border-radius: var(--radius-sm); + padding: 13px 14px; +} +.ae-tick { + width: 24px; height: 24px; border-radius: 50%; flex-shrink: 0; + display: grid; place-items: center; + background: var(--green); color: #fff; +} +.ae-tick .ic { width: 12px; height: 12px; } + +/* ---------- 자동 처리됨 ---------- */ +.appr-done { margin-top: 14px; } +.ad-label { + font-size: 10.5px; font-weight: 700; letter-spacing: 0.05em; + color: var(--muted); text-transform: uppercase; + margin-bottom: 6px; +} +.ad-row { + display: flex; align-items: center; gap: 9px; + padding: 8px 2px; + border-top: 1px solid var(--line); +} +.ad-row:first-of-type { border-top: none; } +.ad-tick { + width: 18px; height: 18px; border-radius: 6px; flex-shrink: 0; + display: grid; place-items: center; + background: color-mix(in oklab, var(--green) 16%, transparent); + color: var(--green); +} +.ad-tick .ic { width: 11px; height: 11px; } +.ad-title { + flex: 1; min-width: 0; + font-size: 12.5px; font-weight: 500; color: var(--ink-2); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.ad-time { font-size: 10.5px; color: var(--faint); font-family: var(--font-mono); flex-shrink: 0; } +.ad-undo { + display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0; + font-size: 11.5px; font-weight: 600; color: var(--muted); + padding: 5px 10px; border-radius: 999px; + border: 1px solid var(--line); + transition: color 0.13s, border-color 0.13s, background 0.13s; +} +.ad-undo .ic { width: 12px; height: 12px; } +.ad-undo:hover { color: var(--coral); border-color: color-mix(in oklab, var(--coral) 40%, transparent); background: color-mix(in oklab, var(--coral) 7%, transparent); } + +/* ---------- 활동 로그 ---------- */ +.appr-log { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); } +.al-row { display: flex; gap: 11px; padding: 4px 0; align-items: baseline; } +.al-time { + font-family: var(--font-mono); font-size: 10.5px; color: var(--faint); + width: 64px; flex-shrink: 0; text-align: right; +} +.al-text { font-size: 12px; color: var(--muted); line-height: 1.45; } + + +/* =========================================================== + 결재함 전용 페이지 + 대시보드 요약 카드 + =========================================================== */ + +/* ---------- 자율성 설정 ---------- */ +.auton-list { display: flex; flex-direction: column; gap: 8px; } +.au-opt { + display: flex; gap: 11px; align-items: flex-start; text-align: left; + padding: 12px 13px; border-radius: var(--radius-sm); + border: 1px solid var(--glass-brd); background: var(--glass-2); + transition: background 0.14s, border-color 0.14s, box-shadow 0.14s; +} +.au-opt:hover { background: var(--card-2); } +.au-opt.on { + background: linear-gradient(100deg, color-mix(in oklab, var(--lime) 22%, transparent), transparent 70%), var(--glass); + border-color: color-mix(in oklab, var(--lime-ink) 26%, var(--glass-brd)); + box-shadow: var(--shadow-sm); +} +.au-radio { + width: 18px; height: 18px; border-radius: 50%; flex-shrink: 0; margin-top: 1px; + border: 2px solid var(--line-2); + display: grid; place-items: center; + transition: border-color 0.14s; +} +.au-opt.on .au-radio { border-color: var(--lime-ink); } +.au-opt.on .au-radio::after { + content: ""; width: 8px; height: 8px; border-radius: 50%; + background: var(--lime-ink); +} +.au-body { min-width: 0; } +.au-title { display: block; font-size: 13.5px; font-weight: 700; letter-spacing: -0.01em; } +.au-rec { + font-style: normal; font-size: 10px; font-weight: 700; + color: var(--lime-ink); background: var(--lime); + padding: 1px 7px; border-radius: 999px; margin-left: 7px; vertical-align: 1px; +} +.au-desc { display: block; font-size: 11.5px; color: var(--muted); margin-top: 3px; line-height: 1.5; } + +.auton-note { + margin-top: 13px; + display: flex; gap: 8px; align-items: flex-start; + font-size: 11.5px; color: var(--ink-2); line-height: 1.5; + background: var(--glass-2); border: 1px solid var(--glass-brd); + border-radius: var(--radius-sm); padding: 10px 12px; +} +.auton-note .ic { width: 13px; height: 13px; flex-shrink: 0; margin-top: 2px; color: var(--coral); } +.auton-note.dim { color: var(--muted); } +.auton-note.dim .ic { color: var(--muted); } + +/* 페이지용 로그 — 카드 단독이라 경계선 제거 */ +.appr-log.pg { margin-top: 0; padding-top: 0; border-top: none; margin-bottom: 12px; } +.appr-log.pg .al-row { padding: 6px 0; border-top: 1px solid var(--line); } +.appr-log.pg .al-row:first-child { border-top: none; padding-top: 0; } + +/* ---------- 대시보드 요약 카드 ---------- */ +.mini-list { display: flex; flex-direction: column; margin-bottom: 12px; } +.mini-row { + display: flex; align-items: center; gap: 9px; + padding: 9px 0; border-top: 1px solid var(--line); +} +.mini-row:first-child { border-top: none; padding-top: 2px; } +.mini-ic { + width: 26px; height: 26px; border-radius: 8px; flex-shrink: 0; + display: grid; place-items: center; + background: color-mix(in oklab, var(--tone, var(--blue)) 13%, transparent); + color: var(--tone, var(--blue)); +} +.mini-ic .ic { width: 13px; height: 13px; } +.mini-text { + flex: 1; min-width: 0; + font-size: 12.5px; font-weight: 600; letter-spacing: -0.01em; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.mini-sub { font-size: 10.5px; color: var(--faint); flex-shrink: 0; white-space: nowrap; } +.mini-cta { + margin-top: auto; + display: flex; align-items: center; justify-content: center; gap: 7px; + padding: 11px; border-radius: 13px; + background: var(--fill); color: var(--on-fill); + font-size: 13px; font-weight: 600; text-decoration: none; + transition: filter 0.14s; +} +.mini-cta .ic { width: 14px; height: 14px; } +.mini-cta:hover { filter: brightness(1.12); } diff --git a/design-reference/assets/approve.jsx b/design-reference/assets/approve.jsx new file mode 100644 index 0000000..369e985 --- /dev/null +++ b/design-reference/assets/approve.jsx @@ -0,0 +1,112 @@ +/* 아리 — 결재함 (승인 큐) 위젯 + 아리가 미리 해둔 일 = 되돌리기만, 위험한 일 = 승인 대기. + dash-v2.jsx에서 로 사용. */ +(function () { + const { useState, useEffect } = React; + const A = window.AriApprovals; + + /* autonomy: "approve"(승인 우선) | "mixed"(혼합) | "auto"(완전 자율) */ + function initStatus(autonomy) { + const m = {}; + A.items.forEach((it) => { + if (autonomy === "auto") m[it.id] = "done"; + else if (autonomy === "mixed") m[it.id] = it.risk === "low" ? "done" : "pending"; + else m[it.id] = "pending"; + }); + return m; + } + + function ApprovalCard({ I, autonomy, showLog }) { + const [st, setSt] = useState(() => initStatus(autonomy)); + useEffect(() => { setSt(initStatus(autonomy)); }, [autonomy]); + + const pending = A.items.filter((it) => st[it.id] === "pending"); + const done = A.items.filter((it) => st[it.id] === "done"); + + const approve = (id) => setSt((s) => ({ ...s, [id]: "done" })); + const undo = (id) => setSt((s) => ({ ...s, [id]: "pending" })); + const approveAll = () => setSt((s) => { + const n = { ...s }; + pending.forEach((it) => { n[it.id] = "done"; }); + return n; + }); + + return ( +
+
+
+
+

아리 결재함

+
확인만 하면 끝 · 오늘 {A.savedToday} 아껴드렸어요
+
+ {pending.length > 0 + ? 대기 {pending.length}건 + : 모두 처리됨} +
+ + {/* ----- 승인 대기 ----- */} + {pending.length === 0 ? ( +
+ + 결재할 게 없어요 — 아리가 알아서 하고 있어요. 아래에서 언제든 되돌릴 수 있어요. +
+ ) : ( +
+ {pending.map((it) => ( +
+
+
+
{it.title}
+
{it.detail}
+
+
+ + +
+
+ ))} + {pending.length >= 2 && ( + + )} +
+ )} + + {/* ----- 자동 처리됨 (되돌리기) ----- */} + {done.length > 0 && ( +
+
처리됨 — 탭 한 번으로 되돌릴 수 있어요
+ {done.map((it) => ( +
+ + {it.title} + {it.time} + +
+ ))} +
+ )} + + {/* ----- 활동 로그 ----- */} + {showLog && ( +
+
밤사이 조용히 한 일 {A.autoCountNight}건
+ {A.log.map((l, i) => ( +
+ {l.time} + {l.text} +
+ ))} +
+ )} +
+ ); + } + + window.ApprovalCard = ApprovalCard; +})(); diff --git a/design-reference/assets/auto-data.js b/design-reference/assets/auto-data.js new file mode 100644 index 0000000..f5a4ab8 --- /dev/null +++ b/design-reference/assets/auto-data.js @@ -0,0 +1,105 @@ +/* 아리 — 자동화 규칙 빌더 데이터 */ +window.AriAuto = { + stats: { active: 7, runsWeek: 31, saved: "1시간 40분" }, + + /* 자연어 → 규칙 예시 (데모 파서) */ + examples: [ + { + text: "출장 전날엔 저녁 일정 비워줘", + name: "출장 전날 저녁 비우기", + cat: "cal", + parse: { + trigger: "출장 전날이 되면", + cond: "18시 이후 일정이 있으면", + action: "다른 날로 옮기자고 제안", + }, + }, + { + text: "뉴스레터는 모아서 저녁에 보여줘", + name: "뉴스레터는 저녁에", + cat: "mail", + parse: { + trigger: "뉴스레터 메일 도착", + cond: null, + action: "받은편지함 건너뛰고 18:30 다이제스트로", + }, + }, + { + text: "운동을 3일 거르면 산책 잡아줘", + name: "산책 리마인더", + cat: "life", + parse: { + trigger: "운동 기록 3일 연속 없음", + cond: "저녁에 빈 시간이 있으면", + action: "30분 산책 블록 제안", + }, + }, + ], + + rules: [ + { + id: "r1", on: true, cat: "mail", name: "수아님 메일은 바로", + trigger: "수아님 발신 메일", cond: null, action: "즉시 알림 + 3줄 요약", + last: "오늘 09:12", runs: 9, + }, + { + id: "r2", on: true, cat: "focus", name: "딥 워크 보호막", + trigger: "집중 모드 시작", cond: "긴급 표시가 아니면", action: "알림 보류 + 메신저 상태 '집중'", + last: "오늘 10:00", runs: 5, + }, + { + id: "r3", on: true, cat: "cal", name: "30분 빈틈 채우기", + trigger: "일정 사이 30분 빈틈", cond: "이동이 없으면", action: "가벼운 할 일 자동 배치", + last: "어제 15:40", runs: 7, + }, + { + id: "r4", on: true, cat: "mail", name: "뉴스레터는 저녁에", + trigger: "뉴스레터 도착", cond: null, action: "보관 후 저녁 다이제스트로 묶음", + last: "어제 18:30", runs: 14, + }, + { + id: "r5", on: true, cat: "life", name: "영수증 자동 정리", + trigger: "카드 결제 알림", cond: null, action: "지출 기록 + 카테고리 분류", + last: "오늘 12:48", runs: 11, + }, + { + id: "r6", on: false, cat: "cal", name: "금요일 오후 비우기", + trigger: "금 13:00 이후 회의 초대", cond: "내가 필수 참석자가 아니면", action: "다음 주 시간 제안", + last: "지난주 금", runs: 2, + }, + { + id: "r7", on: true, cat: "life", name: "수면 모드 연동", + trigger: "23:00이 되면", cond: "내일 첫 일정이 9시 전이면", action: "수면 모드 제안 + 기상 알람 조정", + last: "어제 23:00", runs: 6, + }, + ], + + /* 아리가 발견한 반복 패턴 → 규칙 제안 */ + suggests: [ + { + id: "s1", + pattern: "월요일 아침마다 주간 리포트 초안을 직접 만드시네요. 최근 4주 연속이에요.", + offer: { + name: "주간 리포트 미리 준비", cat: "focus", + trigger: "일요일 21:00", cond: null, action: "지난주 작업·일정으로 초안 생성", + }, + }, + { + id: "s2", + pattern: "18시 이후 도착한 메일은 다음 날 아침에 읽으세요. 이번 주 밤 알림만 14번 울렸어요.", + offer: { + name: "퇴근 후 메일은 아침에", cat: "mail", + trigger: "18:00~08:00 메일 도착", cond: "VIP가 아니면", action: "아침 브리핑으로 묶음", + }, + }, + ], + + log: [ + { time: "12:48", rule: "영수증 자동 정리", text: "점심 결제 13,500원 → 식비로 분류" }, + { time: "10:00", rule: "딥 워크 보호막", text: "알림 4건 보류 — 16:00에 묶음 전달 예정" }, + { time: "09:12", rule: "수아님 메일은 바로", text: "분기 리포트 피드백 도착 → 요약과 함께 알림" }, + { time: "08:55", rule: "30분 빈틈 채우기", text: "10:30 빈틈에 '온보딩 문구 검토' 배치" }, + { time: "어제 18:30", rule: "뉴스레터는 저녁에", text: "뉴스레터 5통 → 저녁 다이제스트로 묶음" }, + { time: "어제 23:00", rule: "수면 모드 연동", text: "내일 8:30 첫 회의 → 수면 모드 제안" }, + ], +}; diff --git a/design-reference/assets/auto.css b/design-reference/assets/auto.css new file mode 100644 index 0000000..fa5b942 --- /dev/null +++ b/design-reference/assets/auto.css @@ -0,0 +1,231 @@ +/* =========================================================== + 아리 — 자동화 규칙 빌더 (dash.css 토큰 위에 얹는 페이지 전용) + =========================================================== */ + +/* 하위 화면마다 카드 수가 적어 — 보드를 단일 열 풀폭으로 */ +.dash .board { grid-template-columns: 1fr; gap: 16px; } +.dash .card.sp2 { grid-column: auto; } +.dash .card.r2 { grid-row: auto; } +/* 작동 방식 설명은 풀폭에서 가로 3열로 */ +@media (min-width: 900px) { + .how-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; } + .how { border-top: none; padding: 0; } + .how:first-child { padding-top: 0; } +} + +/* ---------- 토글 스위치 ---------- */ +.sw { + width: 42px; height: 24px; border-radius: 999px; + background: var(--card-3); + border: 1px solid var(--line-2); + position: relative; flex-shrink: 0; + padding: 0; + transition: background 0.18s, border-color 0.18s; +} +.sw::after { + content: ""; position: absolute; top: 2px; left: 2px; + width: 18px; height: 18px; border-radius: 50%; + background: #fff; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.22); + transition: transform 0.18s; +} +.sw.on { background: var(--green); border-color: var(--green); } +.sw.on::after { transform: translateX(18px); } + +/* ---------- 트리거 → 조건 → 동작 플로우 칩 ---------- */ +.fl { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; } +.fl-chip { + display: inline-flex; flex-direction: column; gap: 2px; + padding: 7px 12px; border-radius: 12px; + background: var(--glass-2); border: 1px solid var(--glass-brd); + min-width: 0; +} +.fl-chip .k { font-size: 9.5px; font-weight: 700; letter-spacing: 0.06em; color: var(--muted); } +.fl-chip .v { font-size: 12.5px; font-weight: 600; color: var(--ink); line-height: 1.3; } +.fl-chip.tg .k { color: var(--blue); } +.fl-chip.cd .k { color: var(--amber); } +.fl-chip.ac .k { color: var(--coral); } +.fl-arr { color: var(--faint); display: inline-flex; } +.fl-arr .ic { width: 14px; height: 14px; } + +/* ---------- 새 규칙 작성기 ---------- */ +.rb-parse { + margin-top: 14px; padding: 13px; + border-radius: var(--radius-sm); + background: var(--glass-2); border: 1px solid var(--glass-brd); +} +.rb-parse.dim { color: var(--muted); } +.rb-parse .lb { + display: flex; align-items: center; gap: 6px; + font-size: 11.5px; font-weight: 700; color: var(--ink-2); + margin-bottom: 10px; +} +.rb-parse.dim .lb { margin-bottom: 0; color: var(--muted); font-weight: 600; } +.rb-parse .lb .ic { width: 13px; height: 13px; color: var(--coral); flex-shrink: 0; } +.rb-make-btn { + margin-top: 13px; + display: inline-flex; align-items: center; justify-content: center; gap: 7px; + width: 100%; padding: 11px; border-radius: 12px; + background: var(--fill); color: var(--on-fill); + font-size: 13px; font-weight: 600; + transition: filter 0.14s; +} +.rb-make-btn:hover { filter: brightness(1.1); } +.rb-make-btn .ic { width: 15px; height: 15px; } +.rb-done { + display: flex; gap: 8px; align-items: center; + margin-top: 14px; padding: 11px 13px; + border-radius: var(--radius-sm); + background: color-mix(in oklab, var(--green) 12%, transparent); + border: 1px solid color-mix(in oklab, var(--green) 30%, var(--glass-brd)); + font-size: 12.5px; color: var(--ink); font-weight: 600; +} +.rb-done .ic { width: 15px; height: 15px; color: var(--green); flex-shrink: 0; } + +/* ---------- 내 규칙 목록 ---------- */ +.rl-list { display: flex; flex-direction: column; } +.rl-row { + display: flex; gap: 13px; align-items: flex-start; + padding: 15px 0; border-top: 1px solid var(--line); +} +.rl-row:first-child { border-top: none; padding-top: 2px; } +.rl-row .sw { margin-top: 3px; } +.rl-row.off .rl-body { opacity: 0.5; } +.rl-body { flex: 1; min-width: 0; transition: opacity 0.18s; } +.rl-top { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; } +.rl-name { font-size: 14px; font-weight: 700; letter-spacing: -0.01em; } +.rl-cat { + font-size: 10px; font-weight: 700; color: #fff; + padding: 2px 8px; border-radius: 999px; +} +.rl-flow .fl-chip { padding: 4px 9px; border-radius: 9px; } +.rl-flow .fl-chip .k { font-size: 9px; } +.rl-flow .fl-chip .v { font-size: 11.5px; font-weight: 500; } +.rl-flow .fl-arr .ic { width: 12px; height: 12px; } +.rl-meta { font-size: 11px; color: var(--muted); margin-top: 8px; display: flex; gap: 8px; align-items: center; } +.rl-new { animation: rlin 0.5s cubic-bezier(0.2, 0.7, 0.2, 1); } +@keyframes rlin { from { opacity: 0; transform: translateY(-7px); } to { opacity: 1; transform: none; } } + +/* ---------- 아리 제안 ---------- */ +.sg { display: flex; flex-direction: column; gap: 12px; } +.sg-card { + border-radius: var(--radius-sm); + border: 1px solid var(--glass-brd); background: var(--glass-2); + padding: 13px; +} +.sg-pat { + display: flex; gap: 9px; + font-size: 12.5px; color: var(--ink-2); line-height: 1.55; + margin-bottom: 11px; +} +.sg-pat .ic { width: 15px; height: 15px; color: var(--violet); flex-shrink: 0; margin-top: 2px; } +.sg-offer { + padding: 11px 12px; border-radius: 11px; + background: var(--card); border: 1px solid var(--line); + margin-bottom: 11px; +} +.sg-name { font-size: 12.5px; font-weight: 700; margin-bottom: 8px; } +.sg-btns { display: flex; gap: 7px; } +.sg-yes { + flex: 1; + display: inline-flex; justify-content: center; align-items: center; gap: 6px; + padding: 9px; border-radius: 10px; + background: var(--fill); color: var(--on-fill); + font-size: 12.5px; font-weight: 600; + transition: filter 0.14s; +} +.sg-yes:hover { filter: brightness(1.1); } +.sg-yes .ic { width: 13px; height: 13px; } +.sg-no { + padding: 9px 14px; border-radius: 10px; + background: var(--glass-2); border: 1px solid var(--glass-brd); + font-size: 12.5px; font-weight: 600; color: var(--ink-2); + transition: background 0.14s; +} +.sg-no:hover { background: var(--card-2); } +.sg-empty { + font-size: 12.5px; color: var(--muted); + display: flex; gap: 8px; align-items: center; + padding: 6px 2px; +} +.sg-empty .ic { width: 15px; height: 15px; color: var(--green); flex-shrink: 0; } + +/* ---------- 실행 기록 ---------- */ +.rb-log { display: flex; flex-direction: column; } +.lg-row { + display: flex; gap: 11px; align-items: flex-start; + padding: 12px 0; border-top: 1px solid var(--line); +} +.lg-row:first-child { border-top: none; padding-top: 2px; } +.lg-time { + font-family: var(--font-mono); font-size: 11.5px; color: var(--muted); + width: 66px; flex-shrink: 0; padding-top: 3px; white-space: nowrap; +} +.lg-body { flex: 1; min-width: 0; } +.lg-rule { + font-size: 10.5px; font-weight: 700; color: var(--ink-2); + background: var(--glass-2); border: 1px solid var(--glass-brd); + border-radius: 999px; padding: 2px 8px; + display: inline-block; margin-bottom: 5px; +} +.lg-text { font-size: 12.5px; color: var(--ink-2); line-height: 1.45; } +.lg-undo { + font-size: 11px; font-weight: 600; color: var(--muted); + padding: 5px 10px; border-radius: 8px; background: var(--card-2); + flex-shrink: 0; align-self: center; + transition: color 0.14s; +} +.lg-undo:hover { color: var(--ink); } + +/* ---------- 작동 방식 (내 규칙 사이드 카드) ---------- */ +.how-list { display: flex; flex-direction: column; gap: 4px; } +.how { + display: flex; gap: 11px; align-items: flex-start; + padding: 13px 0; border-top: 1px solid var(--line); +} +.how:first-child { border-top: none; padding-top: 2px; } +.how-ic { + width: 30px; height: 30px; border-radius: 9px; flex-shrink: 0; + display: grid; place-items: center; + background: color-mix(in oklab, var(--coral) 13%, transparent); + color: var(--coral); +} +.how-ic .ic { width: 15px; height: 15px; } +.how b { font-size: 13px; font-weight: 700; display: block; letter-spacing: -0.01em; } +.how span { font-size: 12px; color: var(--muted); line-height: 1.5; margin-top: 3px; display: block; } + +/* ---------- 새 자동화 · 미리보기 ---------- */ +.nv-name { font-size: 16px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 13px; } +.nv-hint { + margin-top: 14px; + display: flex; gap: 8px; align-items: center; + font-size: 12px; color: var(--muted); line-height: 1.45; +} +.nv-hint .ic { width: 14px; height: 14px; color: var(--ink-2); flex-shrink: 0; } +.nv-empty { + display: flex; gap: 13px; align-items: center; + padding: 18px 4px 8px; +} +.nv-empty-ic { + width: 46px; height: 46px; border-radius: 13px; flex-shrink: 0; + display: grid; place-items: center; + background: var(--glass-2); border: 1px solid var(--glass-brd); + color: var(--coral); +} +.nv-empty-ic .ic { width: 22px; height: 22px; } +.nv-empty p { font-size: 13px; color: var(--ink-2); line-height: 1.6; margin: 0; } +.nv-empty p b { color: var(--ink); font-weight: 700; } + +/* ---------- 토스트 ---------- */ +.au-toast { + position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%); + display: flex; align-items: center; gap: 9px; + padding: 13px 20px; border-radius: 999px; + background: var(--fill); color: var(--on-fill); + font-size: 13.5px; font-weight: 600; + box-shadow: var(--shadow); z-index: 80; + animation: toastin 0.3s cubic-bezier(0.2, 0.7, 0.2, 1); +} +.au-toast .ic { width: 16px; height: 16px; color: var(--lime); } +@keyframes toastin { from { opacity: 0; transform: translate(-50%, 12px); } to { opacity: 1; transform: translate(-50%, 0); } } +.send:disabled { opacity: 0.4; cursor: default; } diff --git a/design-reference/assets/auto.jsx b/design-reference/assets/auto.jsx new file mode 100644 index 0000000..35846b4 --- /dev/null +++ b/design-reference/assets/auto.jsx @@ -0,0 +1,267 @@ +/* 아리 — 자동화 규칙 빌더 페이지 + 좌측 레일로 하위 화면 전환: 내 규칙 · 새 자동화 · 아리 제안 · 실행 기록 */ +const { useState } = React; +const I = window.AriShell.Icon; +const Topbar = window.AriShell.Topbar; +const SubRail = window.AriShell.SubRail; +const AU = window.AriAuto; + +const AU_TONE = { mail: "var(--blue)", cal: "var(--violet)", focus: "var(--coral)", life: "var(--green)" }; +const AU_CAT = { mail: "메일", cal: "일정", focus: "집중", life: "라이프" }; + +function Flow({ p, small }) { + return ( +
+ 트리거{p.trigger} + {p.cond && ( + + + 조건{p.cond} + + )} + + 동작{p.action} +
+ ); +} + +/* ===== 내 규칙 ===== */ +function RulesView({ rules, toggle }) { + const activeN = rules.filter((r) => r.on).length; + return ( +
+
+
+
+

내 규칙

켜고 끄는 건 언제든지
+ {activeN}/{rules.length} 켜짐 +
+
+ {rules.map((r) => ( +
+ +
+
+ {r.name} + {AU_CAT[r.cat]} +
+ +
+ 마지막 실행 {r.last} + · + 이번 주 {r.runs}회 +
+
+
+ ))} +
+
+ +
+
+
+

자동화 작동 방식

안심하고 맡기세요
+
+
+
되돌릴 수 있어요모든 자동 실행은 결재함에 기록되고 한 번에 되돌릴 수 있어요.
+
위험한 건 물어봐요보내기·결제·삭제는 규칙이 있어도 실행 전에 확인받아요.
+
스스로 배워요반복되는 손길을 발견하면 새 규칙을 먼저 제안해요.
+
+
+
+ ); +} + +/* ===== 새 자동화 (작성 플로우) ===== */ +function NewView({ onCreate }) { + const [text, setText] = useState(""); + const match = AU.examples.find((e) => e.text === text.trim()); + + return ( +
+
+ 새 자동화 만들기 +

하고 싶은 일을 한 문장으로 적으세요. 트리거·조건·동작은 아리가 찾아요.

+
+ + setText(e.target.value)} + onKeyDown={(e) => { if (e.key === "Enter" && match) onCreate(match); }} + placeholder="예: 출장 전날엔 저녁 일정 비워줘" + aria-label="새 규칙 문장" + autoFocus + /> + +
+
+ {AU.examples.map((e) => ( + + ))} +
+
+ + {/* 미리보기 */} +
+
+
+

미리보기

{match ? "아리가 이렇게 이해했어요" : "문장을 적으면 여기에 규칙이 그려져요"}
+ {match && {AU_CAT[match.cat]}} +
+ {match ? ( + +
{match.name}
+ + +
만든 규칙은 ‘내 규칙’에서 언제든 끄거나 지울 수 있어요.
+
+ ) : ( +
+ +

예시 문장을 누르거나 직접 적어보세요. 아리가 트리거 → 조건 → 동작으로 나눠 보여드려요.

+
+ )} +
+
+ ); +} + +/* ===== 아리 제안 ===== */ +function SuggestView({ suggests, accept, dismiss }) { + return ( +
+
+
+
+

아리 제안

반복 패턴을 발견했어요
+ {suggests.length}건 +
+
+ {suggests.map((s) => ( +
+
{s.pattern}
+
+
{s.offer.name}
+ +
+
+ + +
+
+ ))} + {suggests.length === 0 && ( +
지금은 새 제안이 없어요. 패턴이 보이면 알려드릴게요.
+ )} +
+
+
+ ); +} + +/* ===== 실행 기록 ===== */ +function LogView() { + return ( +
+
+
+
+

실행 기록

규칙이 움직인 순간들
+ 오늘 {AU.log.filter((l) => !l.time.startsWith("어제")).length}건 +
+
+ {AU.log.map((l, i) => ( +
+ {l.time} +
+ {l.rule} +
{l.text}
+
+ +
+ ))} +
+
+
+ ); +} + +const AU_RAIL = (suggestN) => [ + { id: "rules", icon: "zap", label: "내 규칙" }, + { id: "new", icon: "plus", label: "새 자동화" }, + { sep: true }, + { id: "suggest", icon: "brain", label: "아리 제안", dot: suggestN > 0 }, + { id: "log", icon: "clock", label: "실행 기록" }, +]; + +const AU_HEAD = { + rules: { eye: ["규칙으로 굴러가는 하루"], title: "내 규칙", em: "켜두면 알아서 굴러가요" }, + new: { eye: ["한 문장이면 충분해요"], title: "새 자동화", em: "말하듯 적으면 규칙이 돼요" }, + suggest: { eye: ["아리가 지켜보다 발견한 것"], title: "아리 제안", em: "반복되는 일을 자동으로" }, + log: { eye: ["조용히 일한 기록"], title: "실행 기록", em: "규칙이 움직인 순간들" }, +}; + +function App() { + const [view, setView] = useState("rules"); + const [rules, setRules] = useState(AU.rules); + const [suggests, setSuggests] = useState(AU.suggests); + const [toast, setToast] = useState(null); + + const activeN = rules.filter((r) => r.on).length; + const toggle = (id) => setRules(rules.map((r) => (r.id === id ? { ...r, on: !r.on } : r))); + + const addRule = (cat, name, parse) => { + setRules((rs) => [ + { id: "new-" + Date.now(), on: true, cat, name, trigger: parse.trigger, cond: parse.cond, action: parse.action, last: "방금 만듦", runs: 0, fresh: true }, + ...rs, + ]); + setToast("‘" + name + "’ 규칙을 켰어요."); + setView("rules"); + setTimeout(() => setToast(null), 3200); + }; + + const createFromExample = (m) => addRule(m.cat, m.name, m.parse); + const accept = (s) => { addRule(s.offer.cat, s.offer.name, s.offer); setSuggests((x) => x.filter((y) => y.id !== s.id)); }; + const dismiss = (s) => setSuggests((x) => x.filter((y) => y.id !== s.id)); + + const h = AU_HEAD[view]; + + return ( +
+ + +
+
+
+ 활성 규칙 {activeN}개 + + 이번 주 {AU.stats.runsWeek}회 실행 + + {AU.stats.saved} 아꼈어요 +
+

{h.title} {h.em}

+
+
+ +
+ + {view === "rules" && } + {view === "new" && } + {view === "suggest" && } + {view === "log" && } +
+ + {toast &&
{toast}
} +
+ ); +} + +ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/design-reference/assets/cal-data.js b/design-reference/assets/cal-data.js new file mode 100644 index 0000000..25f0622 --- /dev/null +++ b/design-reference/assets/cal-data.js @@ -0,0 +1,146 @@ +/* 아리 — 일정(캘린더) 데이터 · 2026년 6월 (오늘 = 6월 8일 월) */ +(function () { + // 캘린더 카테고리 + const cals = [ + { id: "work", name: "업무", tone: "blue", on: true }, + { id: "meeting", name: "미팅", tone: "amber", on: true }, + { id: "personal", name: "개인", tone: "violet", on: true }, + { id: "team", name: "팀", tone: "green", on: true }, + { id: "health", name: "건강", tone: "coral", on: true }, + ]; + + // day = 6월 날짜, start/end = "HH:MM" + const events = [ + // 일 6/7 + { id: "e1", day: 7, start: "10:00", end: "11:00", title: "모닝 러닝 5km", cal: "health", loc: "한강공원" }, + { id: "e2", day: 7, start: "15:00", end: "16:30", title: "독서모임 — 몰입", cal: "personal", loc: "북카페" }, + // 월 6/8 (오늘) + { id: "e3", day: 8, start: "09:30", end: "09:45", title: "팀 데일리 스탠드업", cal: "team", loc: "Zoom", + actions: [ + { text: "온보딩 와이어프레임 피드백 정리", when: "오늘" }, + { text: "푸시 알림 QA 결과 공유", when: "내일" }, + ] }, + { id: "e4", day: 8, start: "11:00", end: "11:45", title: "디자인 리뷰 — 온보딩 플로우", cal: "work", loc: "회의실 B", + actions: [ + { text: "온보딩 3번 화면 CTA 수정안 반영", when: "내일" }, + { text: "음성 안내 카피 검토", when: "이번 주" }, + ] }, + { id: "e5", day: 8, start: "13:00", end: "13:30", title: "점심 — 현우님", cal: "personal", loc: "1층 식당" }, + { id: "e6", day: 8, start: "14:00", end: "15:00", title: "분기 전략 미팅", cal: "meeting", loc: "대회의실", soon: true, people: ["대표님", "재무팀장", "나 외 3명"], note: "지난 회의 액션 4/5 완료 · 리텐션 +6%p · 예산 재배분 안건" }, + { id: "e7", day: 8, start: "16:30", end: "17:00", title: "1:1 — 민서님", cal: "team", loc: "라운지" }, + { id: "e8", day: 8, start: "20:00", end: "20:30", title: "스트레칭 & 명상", cal: "health" }, + // 화 6/9 + { id: "e9", day: 9, start: "10:00", end: "11:30", title: "사용자 인터뷰 ×2", cal: "work", loc: "리서치룸" }, + { id: "e10", day: 9, start: "14:00", end: "15:00", title: "온보딩 와이어프레임 싱크", cal: "work", loc: "Figma" }, + { id: "e11", day: 9, start: "18:00", end: "19:00", title: "PT 트레이닝", cal: "health", loc: "피트니스" }, + // 수 6/10 + { id: "e12", day: 10, start: "09:30", end: "09:45", title: "팀 데일리 스탠드업", cal: "team", loc: "Zoom" }, + { id: "e13", day: 10, start: "11:00", end: "12:00", title: "예산 재배분 검토", cal: "meeting", loc: "회의실 A" }, + { id: "e14", day: 10, start: "15:00", end: "16:00", title: "분기 리포트 작성 블록", cal: "work", note: "딥 워크 — 방해 금지" }, + // 목 6/11 + { id: "e15", day: 11, start: "10:00", end: "11:00", title: "OKR 중간 점검", cal: "meeting", loc: "대회의실" }, + { id: "e16", day: 11, start: "13:30", end: "14:30", title: "음성 인터페이스 톤 워크숍", cal: "work", loc: "회의실 C" }, + { id: "e17", day: 11, start: "19:30", end: "21:00", title: "저녁 — 가족 모임", cal: "personal", loc: "본가" }, + // 금 6/12 + { id: "e18", day: 12, start: "09:30", end: "09:45", title: "팀 데일리 스탠드업", cal: "team", loc: "Zoom" }, + { id: "e19", day: 12, start: "11:00", end: "12:00", title: "스프린트 리뷰 & 회고", cal: "team", loc: "대회의실" }, + { id: "e20", day: 12, start: "14:00", end: "15:30", title: "신규 입사자 온보딩 세션", cal: "work", loc: "교육장" }, + { id: "e21", day: 12, start: "16:00", end: "16:30", title: "치과 예약", cal: "health", loc: "강남 치과" }, + // 토 6/13 + { id: "e22", day: 13, start: "11:00", end: "13:00", title: "브런치 — 수아님", cal: "personal", loc: "성수" }, + ]; + + // 이번 주 (일~토): 6/7 ~ 6/13 + const week = [7, 8, 9, 10, 11, 12, 13]; + const weekdays = ["일", "월", "화", "수", "목", "금", "토"]; + + // 오늘의 집중 모드 — 아리가 빈 블록에 자동 배치한 작업 블록 (day 8) + const focusBlocks = [ + { id: "f1", day: 8, start: "09:00", end: "09:20", title: "현우님 온보딩 시안 회신", type: "light", tag: "메일" }, + { id: "f2", day: 8, start: "10:05", end: "10:25", title: "PR #482 빠른 리뷰", type: "light", tag: "개발" }, + { id: "f3", day: 8, start: "15:10", end: "16:25", title: "분기 리포트 초안 마무리", type: "deep", tag: "딥 워크" }, + { id: "f4", day: 8, start: "17:10", end: "17:35", title: "독서모임 책 2장 읽기", type: "light", tag: "성장" }, + ]; + + /* 회의 도우미 — 이벤트별 사전 브리핑 · 회의 노트 자동 정리 · 1:1 어시스턴트 + phase: done(끝난 회의 → 노트·액션) / live(기록 중) / upcoming(사전 브리핑) */ + const meets = { + e3: { + phase: "done", + meta: "09:30–09:45 · 15분 · 음성 기록 → 노트 자동 정리됨", + summary: [ + "온보딩 와이어프레임 피드백이 중심 안건 — 3번 화면 CTA 수정안으로 정리", + "푸시 알림 QA는 iOS 완료 · Android 진행 중", + "분기 리포트는 매출 섹션만 남음 — 현우님이 오늘 중 데이터 전달", + ], + decisions: ["온보딩 3번 화면 CTA는 하단 고정형으로 확정"], + actions: [ + { text: "온보딩 와이어프레임 피드백 정리", who: "나", when: "오늘", added: true }, + { text: "푸시 알림 QA 결과 공유", who: "수아", when: "내일", added: true }, + { text: "매출 데이터 전달", who: "현우", when: "오늘", added: false }, + ], + }, + e4: { + phase: "live", + meta: "11:00–11:45 · 진행 중 · 아리가 실시간으로 받아적고 있어요", + summary: [ + "환영 화면 카피 톤 — ‘친근하게, 두 문장 이내’로 의견 수렴 중", + "권한 요청 화면 재배치안 공유 — 이탈률 데이터 근거로 논의", + ], + decisions: [], + actions: [ + { text: "온보딩 3번 화면 CTA 수정안 반영", who: "나", when: "내일", added: false }, + ], + }, + e6: { + phase: "upcoming", + startsIn: "2시간 50분 후 시작", + agenda: [ + "Q2 핵심 지표 리뷰 — 리텐션 · 매출", + "예산 재배분 안건 — 오늘 의사결정 필요", + "다음 분기 OKR 방향 합의", + ], + lastMeeting: { + when: "지난주 월 14:00 · 분기 전략 미팅", + note: "리텐션 개선 실험 3종 승인 · 예산 재배분은 데이터 보강 후 재논의하기로", + }, + lastActions: [ + { text: "리텐션 코호트 분석", who: "수아", done: true }, + { text: "MRR / ARR 표 정리", who: "나", done: true }, + { text: "예산 시나리오 2안 작성", who: "재무팀장", done: true }, + { text: "경쟁사 가격 조사", who: "현우", done: true }, + { text: "예측 시나리오 3종", who: "나", done: false }, + ], + insights: [ + "리텐션 KPI가 전분기 대비 +6%p — 첫 화두로 추천드려요", + "내 액션 중 ‘예측 시나리오 3종’이 미완 — 미팅 전 12:00 빈 블록을 비워뒀어요", + ], + docs: ["분기 리포트 초안 · 75%", "예산 재배분 시나리오 v2"], + }, + e7: { + phase: "upcoming", oneOnOne: true, + startsIn: "오늘 16:30", + person: { name: "민서", initial: "민", tone: "green" }, + lastMeeting: { + when: "5월 25일 · 지난 1:1", + note: "성장 로드맵 — 데이터 분석 역량을 키우고 싶다는 의사. 분석 과제 1건을 맡겨보기로 약속", + }, + promises: [ + { text: "데이터 전처리 파이프라인 과제 위임", done: true }, + { text: "분석 강의 수강 지원 품의 올리기", done: false }, + ], + signals: [ + { k: "업무량", v: "진행 4건 · 평균보다 +1건", tone: "amber" }, + { k: "위임 과제", v: "전처리 파이프라인 60% · 순항", tone: "green" }, + { k: "최근 성과", v: "회고 문서 정리 — 팀 반응 좋음", tone: "blue" }, + ], + talkingPoints: [ + "전처리 과제에서 막히는 부분이 있는지", + "강의 지원 품의 — 지난 약속, 진행 상황 공유", + "업무량이 살짝 높아요 — 우선순위 조정이 필요한지", + ], + }, + }; + + window.CalData = { cals, events, week, weekdays, focusBlocks, meets, today: 8, month: 6, year: 2026 }; +})(); diff --git a/design-reference/assets/cal-meet.jsx b/design-reference/assets/cal-meet.jsx new file mode 100644 index 0000000..1e5deef --- /dev/null +++ b/design-reference/assets/cal-meet.jsx @@ -0,0 +1,187 @@ +/* 아리 — 회의 도우미 드로어 (일정 페이지) + 사전 브리핑(upcoming) · 실시간 기록(live) · 회의 노트 + 액션 추출(done) · 1:1 어시스턴트 + window.CalMeet = { MeetDrawer } */ +(function () { + const { useState, useEffect } = React; + + const MP = { + spark: "M12 3l1.9 5.2L19 10l-5.1 1.8L12 17l-1.9-5.2L5 10l5.1-1.8z|M19 4v3|M5 17v3", + x: "M18 6 6 18M6 6l12 12", + tick: "M20 6 9 17l-5-5", + check: "M9 11l3 3L22 4|M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11", + clock: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z|M12 7v5l3 2", + mic: "M12 2a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z|M19 10v1a7 7 0 0 1-14 0v-1|M12 18v4M8 22h8", + file: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z|M14 2v6h6M9 13h6M9 17h4", + users: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2|M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z|M23 21v-2a4 4 0 0 0-3-3.87|M16 3.13a4 4 0 0 1 0 7.75", + list: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01", + msg: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z", + target: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z|M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z|M12 12h.01", + flag: "M4 22V4M4 4s1.5-1 5-1 5 2 8 2 4-1 4-1v10s-1.5 1-4 1-5-2-8-2-5 1-5 1", + heart: "M19 14c1.5-1.5 3-3.4 3-5.5A4.5 4.5 0 0 0 12 5.5 4.5 4.5 0 0 0 2 8.5c0 2.1 1.5 4 3 5.5l7 7z", + plus: "M12 5v14M5 12h14", + zap: "M13 2 3 14h9l-1 8 10-12h-9z", + }; + function MI({ d, w }) { + return ( + + {MP[d].split("|").map((s, i) => )} + + ); + } + + function Sec({ icon, title, hint, children }) { + return ( +
+
{title}{hint && {hint}}
+ {children} +
+ ); + } + + /* 액션 아이템 한 줄 — '작업으로' 보내기 */ + function ActionRow({ a, onAdd }) { + return ( +
+ + {a.text} + {a.who} · {a.when} + {a.added + ? 작업에 있음 + : } +
+ ); + } + + function MeetDrawer({ ev, meet, calName, tone, onClose }) { + const [actions, setActions] = useState(meet.actions || []); + useEffect(() => setActions(meet.actions || []), [ev.id]); + useEffect(() => { + const h = (e) => { if (e.key === "Escape") onClose(); }; + window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h); + }, [onClose]); + + const addOne = (i) => setActions((as) => as.map((a, j) => (j === i ? { ...a, added: true } : a))); + const addAll = () => setActions((as) => as.map((a) => ({ ...a, added: true }))); + const remaining = actions.filter((a) => !a.added).length; + + const phaseBadge = meet.phase === "done" + ? 회의 노트 정리됨 + : meet.phase === "live" + ? 기록 중 + : {meet.startsIn}; + + return ( + <> +
+ + + ); + } + + window.CalMeet = { MeetDrawer }; +})(); diff --git a/design-reference/assets/cal.css b/design-reference/assets/cal.css new file mode 100644 index 0000000..7f95388 --- /dev/null +++ b/design-reference/assets/cal.css @@ -0,0 +1,429 @@ +/* 아리 — 일정(Calendar) · 클린 화이트 글래스 + 상단바=메인 메뉴 · 좌측 사이드바=서브 메뉴(미니 달력 + 캘린더 목록) · 주/월/일 뷰 */ + +* { box-sizing: border-box; } +html, body { margin: 0; padding: 0; } + +:root { + --bg-top: #f6efe7; --bg-mid: #eef0f1; --bg-bot: #e9ebed; + --card: #ffffff; --card-2: #f4f5f6; --card-3: #eef0f1; + --ink: #211f1c; --ink-2: #514d47; --muted: #8d8a85; --faint: #b6b3ad; + --line: rgba(33, 31, 28, 0.07); --line-2: rgba(33, 31, 28, 0.13); + --fill: #29241f; --fill-soft: #36302a; --on-fill: #f4efe6; + --blue: #4f72e0; --coral: #df7256; --green: #4e9b66; --violet: #8b6fd4; --amber: #e0a23c; + --radius: 22px; --radius-sm: 14px; + --shadow: 0 10px 30px -14px rgba(30, 26, 22, 0.22), 0 2px 6px -2px rgba(30, 26, 22, 0.06); + --shadow-sm: 0 4px 14px -8px rgba(30, 26, 22, 0.18); + --glass: rgba(255, 255, 255, 0.4); --glass-2: rgba(255, 255, 255, 0.26); + --glass-brd: rgba(255, 255, 255, 0.7); --blur: blur(26px) saturate(190%); + --glass-hi: inset 0 1px 0 rgba(255, 255, 255, 0.75); + --font-disp: "Onest", "Pretendard", system-ui, sans-serif; + --font-ui: "Pretendard", "Onest", system-ui, sans-serif; + --font-mono: "DM Mono", ui-monospace, monospace; +} +[data-theme="dark"] { + --bg-top: #201d1a; --bg-mid: #1a1816; --bg-bot: #161413; + --card: #2c2925; --card-2: #353029; --card-3: #3b352d; + --ink: #f3eee6; --ink-2: #c3bdb4; --muted: #948e85; --faint: #6f6a62; + --line: rgba(244, 239, 230, 0.09); --line-2: rgba(244, 239, 230, 0.16); + --fill: #f4efe6; --fill-soft: #e7e0d4; --on-fill: #211f1c; + --shadow: 0 14px 36px -16px rgba(0, 0, 0, 0.7), 0 2px 8px -3px rgba(0, 0, 0, 0.4); + --shadow-sm: 0 6px 16px -10px rgba(0, 0, 0, 0.6); + --glass: rgba(48, 43, 38, 0.4); --glass-2: rgba(70, 63, 55, 0.28); + --glass-brd: rgba(255, 255, 255, 0.1); --glass-hi: inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +body { + font-family: var(--font-ui); color: var(--ink); + background: linear-gradient(178deg, var(--bg-top) 0%, var(--bg-mid) 20%, var(--bg-bot) 100%); + background-attachment: fixed; min-height: 100vh; + -webkit-font-smoothing: antialiased; letter-spacing: -0.011em; word-break: keep-all; +} +button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; } +input { font-family: inherit; } +:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; border-radius: 6px; } +.ic { width: 1em; height: 1em; flex-shrink: 0; display: block; } +.tpage { max-width: 1620px; margin: 0 auto; padding: 0 26px 26px; } + +/* ====================== 상단바 (공유) ====================== */ +.topbar { position: sticky; top: 0; z-index: 40; display: flex; align-items: center; gap: 24px; height: 70px; margin: 0 -26px; padding: 0 30px; -webkit-backdrop-filter: blur(14px) saturate(150%); backdrop-filter: blur(14px) saturate(150%); } +.brand { display: flex; align-items: center; gap: 11px; flex-shrink: 0; } +.brand-mark { width: 40px; height: 40px; border-radius: 12px; background: var(--fill); color: var(--coral); display: grid; place-items: center; position: relative; } +.brand-mark .ic { width: 21px; height: 21px; } +.brand-mark::after { content: ""; position: absolute; top: -2px; right: -2px; width: 9px; height: 9px; border-radius: 50%; background: var(--coral); border: 2px solid var(--bg-top); } +.brand-tag { line-height: 1.15; } +.brand-tag b { display: block; font-size: 15px; font-weight: 700; letter-spacing: -0.02em; white-space: nowrap; } +.brand-tag span { font-size: 9.5px; font-weight: 700; letter-spacing: 0.08em; color: var(--muted); text-transform: uppercase; white-space: nowrap; } +.mainnav { display: flex; align-items: center; gap: 2px; min-width: 0; overflow-x: auto; scrollbar-width: none; } +.mainnav::-webkit-scrollbar { display: none; } +.mainnav a { text-decoration: none; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 500; color: var(--ink-2); padding: 9px 16px; border-radius: 999px; white-space: nowrap; transition: background 0.16s, color 0.16s, box-shadow 0.16s; } +.mainnav a .ic { width: 16px; height: 16px; color: var(--muted); transition: color 0.16s; } +.mainnav a:hover { color: var(--ink); } +.mainnav a:hover .ic { color: var(--ink-2); } +.mainnav a.active { background: var(--glass); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); color: var(--ink); font-weight: 600; box-shadow: var(--shadow-sm); border: 1px solid var(--glass-brd); } +.mainnav a.active .ic { color: var(--ink); } +.mainnav a .badge { font-size: 11px; font-weight: 700; background: var(--coral); color: #fff; border-radius: 999px; padding: 0 6px; min-width: 18px; text-align: center; } +.top-actions { margin-left: auto; display: flex; align-items: center; gap: 9px; flex-shrink: 0; } +.t-btn { width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; color: var(--ink-2); background: var(--glass); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); border: 1px solid var(--glass-brd); box-shadow: var(--shadow-sm); position: relative; transition: transform 0.12s, color 0.16s; } +.t-btn .ic { width: 18px; height: 18px; } +.t-btn:hover { color: var(--ink); transform: translateY(-1px); } +.t-btn.dot::after { content: ""; position: absolute; top: 9px; right: 10px; width: 7px; height: 7px; border-radius: 50%; background: var(--coral); border: 2px solid var(--card); } +.t-ava { width: 40px; height: 40px; border-radius: 50%; background: linear-gradient(135deg, var(--coral), var(--violet)); color: #fff; display: grid; place-items: center; font-weight: 700; font-size: 15px; box-shadow: var(--shadow-sm); cursor: pointer; } + +/* ====================== 본문 ====================== */ +.twork { display: flex; gap: 18px; align-items: flex-start; padding-top: 12px; } + +/* -------- 사이드바 -------- */ +.subnav { width: 256px; flex-shrink: 0; position: sticky; top: 86px; max-height: calc(100vh - 104px); background: var(--glass); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); border: 1px solid var(--glass-brd); border-radius: var(--radius); box-shadow: var(--shadow), var(--glass-hi); padding: 16px 14px 14px; display: flex; flex-direction: column; } +.sn-title { font-family: var(--font-disp); font-size: 19px; font-weight: 700; letter-spacing: -0.02em; padding: 0 4px 14px; } +.sn-new { display: flex; align-items: center; justify-content: center; gap: 8px; white-space: nowrap; width: 100%; padding: 11px; border-radius: 13px; font-size: 13.5px; font-weight: 700; background: var(--fill); color: var(--on-fill); box-shadow: var(--shadow-sm); margin-bottom: 16px; transition: transform 0.12s; } +.sn-new .ic { width: 16px; height: 16px; } +.sn-new:hover { transform: translateY(-1px); } + +/* 미니 달력 */ +.minical { margin-bottom: 4px; } +.mc-head { display: flex; align-items: center; gap: 6px; padding: 0 4px 10px; } +.mc-title { font-size: 13.5px; font-weight: 700; } +.mc-nav { margin-left: auto; display: flex; gap: 2px; } +.mc-nav button { width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center; color: var(--muted); transition: background 0.13s, color 0.13s; } +.mc-nav button .ic { width: 15px; height: 15px; } +.mc-nav button:hover { background: var(--glass-2); color: var(--ink); } +.mc-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; } +.mc-wd { font-size: 10px; font-weight: 700; color: var(--faint); text-align: center; padding-bottom: 5px; } +.mc-day { aspect-ratio: 1; display: grid; place-items: center; border-radius: 8px; font-size: 12px; color: var(--ink-2); font-variant-numeric: tabular-nums; position: relative; transition: background 0.12s, color 0.12s; } +.mc-day.out { color: var(--faint); } +.mc-day:hover { background: var(--glass-2); } +.mc-day.today { background: var(--coral); color: #fff; font-weight: 700; } +.mc-day.sel { box-shadow: inset 0 0 0 1.5px var(--ink); font-weight: 700; color: var(--ink); } +.mc-day.today.sel { color: #fff; box-shadow: inset 0 0 0 1.5px var(--coral); } +.mc-day .evdot { position: absolute; bottom: 3px; width: 4px; height: 4px; border-radius: 50%; background: var(--blue); } +.mc-day.today .evdot { background: rgba(255,255,255,0.85); } + +.sn-label { font-size: 10.5px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); padding: 16px 4px 9px; } +.cal-list { display: flex; flex-direction: column; gap: 1px; overflow-y: auto; scrollbar-width: thin; } +.cal-toggle { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 8px; border-radius: 10px; font-size: 13.5px; color: var(--ink-2); white-space: nowrap; transition: background 0.12s; } +.cal-toggle:hover { background: var(--glass-2); } +.cal-toggle .cbx { width: 17px; height: 17px; border-radius: 5px; flex-shrink: 0; display: grid; place-items: center; color: #fff; transition: opacity 0.12s; } +.cal-toggle .cbx .ic { width: 12px; height: 12px; } +.cal-toggle.off .cbx { opacity: 0.25; } +.cal-toggle.off { color: var(--muted); } +.cal-toggle > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; } +.cal-toggle .ct-n { font-size: 11.5px; color: var(--faint); font-variant-numeric: tabular-nums; flex-shrink: 0; } + +.sn-foot { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--line); display: flex; align-items: center; gap: 10px; } +.sn-foot .av { width: 32px; height: 32px; border-radius: 50%; background: linear-gradient(135deg, var(--coral), var(--violet)); color: #fff; display: grid; place-items: center; font-weight: 700; font-size: 13px; flex-shrink: 0; } +.sn-foot .txt { min-width: 0; flex: 1; } +.sn-foot .txt b { font-size: 13px; font-weight: 600; display: block; } +.sn-foot .txt span { font-size: 11.5px; color: var(--muted); } + +/* -------- 메인 -------- */ +.cmain { flex: 1; min-width: 0; display: flex; flex-direction: column; } +.chead { display: flex; align-items: center; gap: 13px; margin-bottom: 16px; } +.ch-title { font-family: var(--font-disp); font-size: clamp(23px, 2.5vw, 31px); font-weight: 700; letter-spacing: -0.03em; margin: 0; white-space: nowrap; } +.ch-arrow { width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; color: var(--ink-2); background: var(--glass); border: 1px solid var(--glass-brd); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); box-shadow: var(--shadow-sm); transition: color 0.13s; } +.ch-arrow .ic { width: 18px; height: 18px; } +.ch-arrow:hover { color: var(--ink); } +.ch-today { padding: 9px 15px; border-radius: 999px; font-size: 13px; font-weight: 600; color: var(--ink-2); background: var(--glass); border: 1px solid var(--glass-brd); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); box-shadow: var(--shadow-sm); white-space: nowrap; transition: color 0.13s; } +.ch-today:hover { color: var(--ink); } +.ch-spacer { flex: 1; } +.view-seg { display: flex; gap: 2px; padding: 4px; background: var(--glass); border: 1px solid var(--glass-brd); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); border-radius: 13px; box-shadow: var(--shadow-sm); } +.view-seg button { padding: 8px 16px; border-radius: 10px; font-size: 13px; font-weight: 600; color: var(--ink-2); white-space: nowrap; transition: background 0.14s, color 0.14s; } +.view-seg button:hover { color: var(--ink); } +.view-seg button.on { background: var(--fill); color: var(--on-fill); } + +.cpanel { background: var(--glass); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); border: 1px solid var(--glass-brd); border-radius: var(--radius); box-shadow: var(--shadow), var(--glass-hi); overflow: hidden; } + +/* 이벤트 색 토큰: --evc(테두리/점), --evbg(배경), --evtx(글자) */ +.tint-blue { --evc: var(--blue); --evbg: color-mix(in oklab, var(--blue) 15%, var(--card)); --evtx: color-mix(in oklab, var(--blue) 72%, var(--ink)); } +.tint-amber { --evc: var(--amber); --evbg: color-mix(in oklab, var(--amber) 17%, var(--card)); --evtx: color-mix(in oklab, var(--amber) 70%, var(--ink)); } +.tint-violet { --evc: var(--violet); --evbg: color-mix(in oklab, var(--violet) 15%, var(--card)); --evtx: color-mix(in oklab, var(--violet) 72%, var(--ink)); } +.tint-green { --evc: var(--green); --evbg: color-mix(in oklab, var(--green) 15%, var(--card)); --evtx: color-mix(in oklab, var(--green) 72%, var(--ink)); } +.tint-coral { --evc: var(--coral); --evbg: color-mix(in oklab, var(--coral) 16%, var(--card)); --evtx: color-mix(in oklab, var(--coral) 72%, var(--ink)); } + +/* ====================== 주 뷰 ====================== */ +.wk-head { display: grid; grid-template-columns: 56px repeat(7, 1fr); border-bottom: 1px solid var(--line); } +.wk-corner { border-right: 1px solid var(--line); } +.wk-dh { text-align: center; padding: 11px 4px; border-right: 1px solid var(--line); } +.wk-dh:last-child { border-right: none; } +.wk-dh .wd { font-size: 11.5px; font-weight: 600; color: var(--muted); } +.wk-dh .dn { font-family: var(--font-disp); font-size: 19px; font-weight: 700; margin-top: 3px; width: 34px; height: 34px; line-height: 34px; border-radius: 50%; display: inline-block; } +.wk-dh.today .wd { color: var(--coral); } +.wk-dh.today .dn { background: var(--coral); color: #fff; } +.wk-dh.wknd .wd, .wk-dh.wknd .dn { color: var(--faint); } +.wk-dh.today.wknd .dn { color: #fff; } +.wk-body { display: grid; grid-template-columns: 56px repeat(7, 1fr); max-height: calc(100vh - 252px); overflow-y: auto; scrollbar-width: thin; } +.wk-body::-webkit-scrollbar { width: 8px; } +.wk-body::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 99px; } +.wk-times { border-right: 1px solid var(--line); overflow: visible; } +.wk-thour { height: 52px; position: relative; } +.wk-thour > span { position: absolute; top: -7px; left: 2px; right: 7px; text-align: right; font-size: 10px; color: var(--faint); font-family: var(--font-mono); white-space: nowrap; } +.wk-thour > span span { position: static; } +.wk-col { border-right: 1px solid var(--line); position: relative; } +.wk-col:last-child { border-right: none; } +.wk-col.wknd { background: color-mix(in oklab, var(--ink) 2.5%, transparent); } +.wk-hr { height: 52px; border-bottom: 1px solid var(--line); } +.wk-now { position: absolute; left: 0; right: 0; height: 0; border-top: 2px solid var(--coral); z-index: 4; pointer-events: none; } +.wk-now::before { content: ""; position: absolute; left: -3px; top: -4px; width: 7px; height: 7px; border-radius: 50%; background: var(--coral); } +.ev { position: absolute; left: 3px; right: 3px; border-radius: 9px; padding: 5px 8px; font-size: 11.5px; overflow: hidden; cursor: pointer; z-index: 3; border-left: 3px solid var(--evc); background: var(--evbg); color: var(--evtx); transition: transform 0.1s, box-shadow 0.12s; } +.ev:hover { transform: translateY(-1px); box-shadow: 0 6px 14px -5px rgba(30,26,22,0.4); z-index: 6; } +.ev .et { font-weight: 700; line-height: 1.22; letter-spacing: -0.01em; } +.ev .etm { font-size: 10px; opacity: 0.82; margin-top: 1px; font-variant-numeric: tabular-nums; } +.ev.soon { box-shadow: 0 0 0 1.5px var(--evc); } +.ev.short { padding: 3px 8px; } +.ev.short .etm { display: inline; margin: 0 0 0 5px; } +.ev.short .et { display: inline; } + +/* ====================== 월 뷰 ====================== */ +.mo-head { display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid var(--line); } +.mo-wd { text-align: center; padding: 10px; font-size: 11.5px; font-weight: 700; color: var(--muted); } +.mo-wd.wknd { color: var(--faint); } +.mo-grid { display: grid; grid-template-columns: repeat(7, 1fr); grid-auto-rows: minmax(108px, 1fr); } +.mo-cell { border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 7px 7px 6px; min-width: 0; display: flex; flex-direction: column; gap: 3px; overflow: hidden; } +.mo-cell:nth-child(7n) { border-right: none; } +.mo-cell.out { background: color-mix(in oklab, var(--ink) 2%, transparent); } +.mo-dn { font-size: 12.5px; font-weight: 600; color: var(--ink-2); font-variant-numeric: tabular-nums; width: 24px; height: 24px; display: grid; place-items: center; border-radius: 50%; } +.mo-cell.out .mo-dn { color: var(--faint); } +.mo-cell.today .mo-dn { background: var(--coral); color: #fff; } +.mo-ev { display: flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 500; color: var(--ink-2); padding: 2px 6px; border-radius: 6px; background: var(--evbg); cursor: pointer; min-width: 0; } +.mo-ev .pdot { width: 6px; height: 6px; border-radius: 50%; background: var(--evc); flex-shrink: 0; } +.mo-ev .mt { font-variant-numeric: tabular-nums; color: var(--muted); flex-shrink: 0; font-size: 10px; } +.mo-ev .mn { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.mo-more { font-size: 10.5px; color: var(--muted); padding: 1px 6px; cursor: pointer; } +.mo-more:hover { color: var(--ink); } + +/* ====================== 일 뷰 ====================== */ +.day-wrap { display: grid; grid-template-columns: 1.55fr 1fr; gap: 16px; align-items: start; } +.day-grid { display: grid; grid-template-columns: 56px 1fr; max-height: calc(100vh - 252px); overflow-y: auto; scrollbar-width: thin; } +.day-grid::-webkit-scrollbar { width: 8px; } +.day-grid::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 99px; } +.day-col { position: relative; } +.agenda { padding: 20px; } +.ag-head { margin-bottom: 16px; } +.ag-title { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; } +.ag-sub { font-size: 12.5px; color: var(--muted); margin-top: 3px; } +.ag-item { display: flex; gap: 13px; padding: 13px 0; border-top: 1px solid var(--line); cursor: pointer; } +.ag-item:first-of-type { border-top: none; } +.ag-time { width: 46px; flex-shrink: 0; font-size: 12.5px; font-weight: 600; color: var(--ink-2); font-variant-numeric: tabular-nums; padding-top: 1px; } +.ag-time small { display: block; font-size: 11px; color: var(--faint); font-weight: 500; } +.ag-body { flex: 1; min-width: 0; position: relative; padding-left: 14px; } +.ag-body::before { content: ""; position: absolute; left: 0; top: 3px; bottom: 3px; width: 3px; border-radius: 3px; background: var(--evc); } +.ag-t { font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; } +.ag-m { font-size: 12px; color: var(--muted); margin-top: 3px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } +.ag-m > span { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; } +.ag-m .ic { width: 13px; height: 13px; } +.ag-note { font-size: 12px; color: var(--ink-2); margin-top: 7px; background: var(--glass-2); border: 1px solid var(--glass-brd); border-radius: 9px; padding: 8px 10px; line-height: 1.4; } +.ag-soon { font-size: 10px; font-weight: 700; color: #fff; background: var(--coral); padding: 1px 7px; border-radius: 999px; } +.ag-people { display: flex; align-items: center; margin-top: 8px; } +.ag-people .pp { width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center; font-size: 10px; font-weight: 700; color: #fff; border: 2px solid var(--card); margin-left: -7px; background: var(--violet); } +.ag-people .pp:first-child { margin-left: 0; } +.ag-people .pl { font-size: 11.5px; color: var(--muted); margin-left: 9px; } +.ag-empty { font-size: 13px; color: var(--faint); text-align: center; padding: 30px 10px; } + +/* AI 한 줄 브리핑 */ +.ai-strip { display: flex; align-items: flex-start; gap: 11px; background: var(--glass); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); border: 1px solid var(--glass-brd); border-radius: var(--radius-sm); box-shadow: var(--shadow-sm); padding: 13px 15px; margin-bottom: 16px; } +.ai-strip .ai-ic { width: 30px; height: 30px; border-radius: 9px; flex-shrink: 0; display: grid; place-items: center; background: color-mix(in oklab, var(--coral) 16%, transparent); color: var(--coral); } +.ai-strip .ai-ic .ic { width: 16px; height: 16px; } +.ai-strip p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--ink-2); } +.ai-strip p b { color: var(--ink); font-weight: 700; border-bottom: 2px solid color-mix(in oklab, var(--coral) 55%, transparent); } + +/* 상세 팝오버 */ +.dp-backdrop { position: fixed; inset: 0; z-index: 60; } +.epop { position: fixed; z-index: 61; width: 300px; background: var(--card); border: 1px solid var(--line); border-radius: 16px; box-shadow: 0 24px 60px -20px rgba(26,22,18,0.45); padding: 18px; animation: pop 0.16s ease; } +@keyframes pop { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: none; } } +.epop-bar { height: 4px; border-radius: 99px; background: var(--evc); margin-bottom: 13px; } +.epop-title { font-size: 16px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.3; margin-bottom: 12px; } +.epop-row { display: flex; align-items: center; gap: 9px; font-size: 13px; color: var(--ink-2); padding: 5px 0; } +.epop-row .ic { width: 15px; height: 15px; color: var(--muted); flex-shrink: 0; } +.epop-cat { display: inline-flex; align-items: center; gap: 6px; } +.epop-cat .pdot { width: 8px; height: 8px; border-radius: 50%; background: var(--evc); } +.epop-note { font-size: 12.5px; color: var(--ink-2); line-height: 1.45; margin-top: 10px; padding-top: 11px; border-top: 1px solid var(--line); } +.epop-acts { display: flex; gap: 8px; margin-top: 14px; } +.epop-acts button { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 9px; border-radius: 10px; font-size: 13px; font-weight: 600; transition: background 0.13s; } +.epop-acts .ic { width: 15px; height: 15px; } +.epop-acts .edit { background: var(--card-2); color: var(--ink); } +.epop-acts .edit:hover { background: var(--card-3); } +.epop-acts .del { background: color-mix(in oklab, var(--coral) 12%, transparent); color: var(--coral); flex: 0 0 auto; padding: 9px 13px; } + +/* ====================== 집중 블록 (Focus of the Day) ====================== */ +.ch-focus { + display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; + padding: 8px 14px; border-radius: 999px; font-size: 13px; font-weight: 600; + color: var(--ink-2); background: var(--glass); border: 1px solid var(--glass-brd); + -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); + box-shadow: var(--shadow-sm); transition: color 0.13s, background 0.13s; +} +.ch-focus .ic { width: 15px; height: 15px; color: var(--muted); transition: color 0.13s; } +.ch-focus:hover { color: var(--ink); } +.ch-focus.on { background: var(--violet); color: #fff; border-color: transparent; } +.ch-focus.on .ic { color: #fff; } + +.fblock { + position: absolute; left: 3px; right: 3px; z-index: 2; + border-radius: 9px; padding: 5px 8px; overflow: hidden; + border: 1.5px dashed color-mix(in oklab, var(--violet) 52%, transparent); + background: color-mix(in oklab, var(--violet) 9%, var(--card)); + color: color-mix(in oklab, var(--violet) 72%, var(--ink)); +} +.fblock.deep { + border-color: color-mix(in oklab, var(--coral) 54%, transparent); + background: color-mix(in oklab, var(--coral) 10%, var(--card)); + color: color-mix(in oklab, var(--coral) 72%, var(--ink)); +} +.fblock .fb-k { display: flex; align-items: center; gap: 4px; font-size: 9.5px; font-weight: 700; letter-spacing: 0.02em; opacity: 0.92; } +.fblock .fb-k .ic { width: 11px; height: 11px; } +.fblock .fbt { font-size: 11.5px; font-weight: 700; line-height: 1.2; margin-top: 2px; letter-spacing: -0.01em; } +.fblock.short .fbt { display: inline; margin: 0 0 0 5px; } +.fblock.short .fb-k { display: inline-flex; } + +/* 일 뷰 사이드 (집중 카드 + 어젠다) */ +.day-side { display: flex; flex-direction: column; gap: 16px; min-width: 0; } +.focus-card { padding: 18px; } +.fc-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; } +.fc-ic { width: 32px; height: 32px; border-radius: 9px; flex-shrink: 0; display: grid; place-items: center; background: var(--violet); color: #fff; } +.fc-ic .ic { width: 16px; height: 16px; } +.fc-h b { display: block; font-size: 14px; font-weight: 700; letter-spacing: -0.01em; } +.fc-h span { font-size: 11.5px; color: var(--muted); } +.fc-note { font-size: 12.5px; color: var(--ink-2); line-height: 1.5; margin: 0 0 8px; } +.fc-item { display: flex; gap: 11px; padding: 10px 0; border-top: 1px solid var(--line); } +.fci-time { font-family: var(--font-mono); font-size: 12px; color: var(--ink-2); width: 42px; flex-shrink: 0; padding-top: 1px; } +.fci-body { flex: 1; min-width: 0; } +.fci-tag { font-size: 9.5px; font-weight: 700; letter-spacing: 0.02em; padding: 2px 8px; border-radius: 999px; color: var(--ink-2); background: var(--card-2); } +.fci-tag.deep { color: #fff; background: var(--coral); } +.fci-title { font-size: 13px; font-weight: 600; letter-spacing: -0.01em; margin-top: 6px; line-height: 1.3; } +.fc-item.deep .fci-title { font-weight: 700; } + +/* 어젠다 액션 아이템 */ +.ag-actions { margin-top: 9px; padding: 10px 12px; background: var(--glass-2); border: 1px solid var(--glass-brd); border-radius: 9px; } +.aga-head { display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 700; color: var(--violet); margin-bottom: 7px; } +.aga-head .ic { width: 12px; height: 12px; } +.aga-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; } +.aga-check { width: 16px; height: 16px; border-radius: 5px; border: 2px solid var(--line-2); display: grid; place-items: center; color: transparent; flex-shrink: 0; } +.aga-check .ic { width: 10px; height: 10px; } +.aga-text { flex: 1; min-width: 0; font-size: 12px; font-weight: 500; line-height: 1.3; } +.aga-who { font-size: 10.5px; color: var(--muted); flex-shrink: 0; } + +/* 팝오버 액션 아이템 */ +.epop-actions { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); } +.epa-head { display: flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 700; color: var(--violet); margin-bottom: 9px; } +.epa-head .ic { width: 13px; height: 13px; } +.epa-head .n { margin-left: auto; font-size: 11px; font-weight: 700; color: var(--muted); background: var(--card-2); border-radius: 999px; padding: 1px 7px; } +.epa-row { display: flex; align-items: center; gap: 8px; padding: 5px 0; } +.epa-check { width: 17px; height: 17px; border-radius: 5px; border: 2px solid var(--line-2); display: grid; place-items: center; color: transparent; flex-shrink: 0; } +.epa-check .ic { width: 11px; height: 11px; } +.epa-text { flex: 1; min-width: 0; font-size: 12.5px; font-weight: 500; line-height: 1.3; } +.epa-who { font-size: 10.5px; color: var(--muted); flex-shrink: 0; } +.epa-add { display: inline-flex; align-items: center; justify-content: center; gap: 6px; width: 100%; margin-top: 10px; padding: 9px; border-radius: 10px; font-size: 12.5px; font-weight: 700; background: var(--violet); color: #fff; transition: filter 0.13s; } +.epa-add .ic { width: 14px; height: 14px; } +.epa-add:hover { filter: brightness(1.08); } + +.entered .twork > * { animation: rise 0.45s cubic-bezier(0.2,0.7,0.2,1) both; } +.entered .cmain { animation-delay: 0.05s; } +@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } + +/* ====================== 회의 도우미 드로어 ====================== */ +.mdrawer { + position: fixed; top: 0; right: 0; bottom: 0; z-index: 65; + width: min(440px, 94vw); display: flex; flex-direction: column; + background: var(--card); border-left: 1px solid var(--line); + box-shadow: -28px 0 70px -30px rgba(26, 22, 18, 0.4); + animation: md-in 0.32s cubic-bezier(0.2, 0.7, 0.2, 1) both; +} +@keyframes md-in { from { opacity: 0; transform: translateX(36px); } to { opacity: 1; transform: none; } } + +.md-head { display: flex; align-items: flex-start; gap: 12px; padding: 20px 20px 14px; } +.md-ic { + width: 38px; height: 38px; border-radius: 12px; flex-shrink: 0; display: grid; place-items: center; + background: color-mix(in oklab, var(--evc) 16%, transparent); color: var(--evc); +} +.md-ic .ic { width: 19px; height: 19px; } +.md-ht { flex: 1; min-width: 0; line-height: 1.3; } +.md-k { display: block; font-size: 10.5px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; color: var(--evc); margin-bottom: 2px; } +.md-ht b { display: block; font-size: 17px; font-weight: 700; letter-spacing: -0.02em; } +.md-sub { display: block; font-size: 12px; color: var(--muted); margin-top: 3px; } +.md-x { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; color: var(--muted); flex-shrink: 0; } +.md-x:hover { background: var(--card-2); color: var(--ink); } +.md-x .ic { width: 16px; height: 16px; } + +.md-bar { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; padding: 0 20px 14px; border-bottom: 1px solid var(--line); } +.md-phase { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 700; padding: 4px 10px; border-radius: 999px; } +.md-phase .ic { width: 12px; height: 12px; } +.md-phase.done { background: color-mix(in oklab, var(--green) 15%, transparent); color: var(--green); } +.md-phase.up { background: color-mix(in oklab, var(--blue) 13%, transparent); color: var(--blue); } +.md-phase.live { background: color-mix(in oklab, var(--coral) 14%, transparent); color: var(--coral); } +.md-pulse { width: 7px; height: 7px; border-radius: 50%; background: var(--coral); animation: md-pulse 1.4s ease infinite; } +@media (prefers-reduced-motion: reduce) { .md-pulse { animation: none; } } +@keyframes md-pulse { 0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--coral) 45%, transparent); } 55% { box-shadow: 0 0 0 5px transparent; } } +.md-meta { font-size: 11.5px; color: var(--muted); } + +.md-body { flex: 1; overflow-y: auto; padding: 6px 20px 24px; } +.md-sec { padding: 15px 0 4px; border-bottom: 1px solid var(--line); } +.md-sec:last-child { border-bottom: none; } +.md-sh { display: flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 700; margin-bottom: 9px; } +.md-sh .ic { width: 14px; height: 14px; color: var(--evc); } +.md-hint { margin-left: auto; font-size: 11px; font-weight: 600; color: var(--muted); } + +.md-note { margin: 0 0 10px; font-size: 13px; line-height: 1.55; color: var(--ink-2); } +.md-points { margin: 0 0 10px; padding-left: 19px; display: flex; flex-direction: column; gap: 6px; } +.md-points li { font-size: 13px; line-height: 1.45; color: var(--ink-2); } +.md-points li::marker { font-weight: 700; color: var(--evc); } +.md-sum { margin: 0 0 10px; padding-left: 17px; display: flex; flex-direction: column; gap: 6px; } +.md-sum li { font-size: 13px; line-height: 1.5; color: var(--ink-2); } +.md-sum li::marker { color: var(--evc); } + +.md-chk { display: flex; align-items: center; gap: 8px; padding: 5px 0; font-size: 12.5px; font-weight: 500; margin-bottom: 2px; } +.md-cbx { width: 17px; height: 17px; border-radius: 5px; border: 2px solid var(--line-2); display: grid; place-items: center; color: transparent; flex-shrink: 0; } +.md-cbx .ic { width: 11px; height: 11px; } +.md-chk.done .md-cbx { background: var(--green); border-color: var(--green); color: #fff; } +.md-chk.done { color: var(--muted); } +.md-who { margin-left: auto; font-size: 11px; color: var(--muted); flex-shrink: 0; } +.md-tag { font-size: 10px; font-weight: 800; padding: 2px 7px; border-radius: 999px; flex-shrink: 0; } +.md-tag.warn { background: color-mix(in oklab, var(--coral) 15%, transparent); color: var(--coral); } +.md-chk .md-tag.warn { margin-left: 6px; } + +.md-insight { margin: 0 0 8px; padding: 9px 12px; border-radius: 10px; background: var(--card-2); font-size: 12.5px; line-height: 1.5; color: var(--ink-2); } +.md-insight b { color: var(--ink); } +.md-docs { display: flex; flex-wrap: wrap; gap: 7px; padding: 14px 0 4px; } +.md-doc { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; padding: 6px 11px; border-radius: 999px; border: 1px solid var(--line-2); color: var(--ink-2); cursor: pointer; } +.md-doc:hover { border-color: var(--evc); color: var(--evc); } +.md-doc .ic { width: 13px; height: 13px; } + +.md-sig { display: flex; align-items: center; gap: 9px; padding: 6px 0; font-size: 12.5px; } +.md-sig-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } +.md-sig-k { font-weight: 700; flex-shrink: 0; min-width: 58px; } +.md-sig-v { color: var(--ink-2); } + +.md-decision { display: flex; align-items: center; gap: 8px; font-size: 12.5px; font-weight: 600; padding: 8px 11px; border-radius: 10px; margin-bottom: 8px; background: color-mix(in oklab, var(--green) 10%, transparent); color: color-mix(in oklab, var(--green) 70%, var(--ink)); } +.md-decision .ic { width: 13px; height: 13px; color: var(--green); flex-shrink: 0; } + +.md-act { display: flex; align-items: center; gap: 8px; padding: 7px 0; border-bottom: 1px dashed var(--line); } +.md-act:last-of-type { border-bottom: none; } +.md-act-check { width: 17px; height: 17px; border-radius: 5px; border: 2px solid var(--line-2); display: grid; place-items: center; color: transparent; flex-shrink: 0; } +.md-act-check .ic { width: 11px; height: 11px; } +.md-act.added .md-act-check { background: var(--violet); border-color: var(--violet); color: #fff; } +.md-act-text { flex: 1; min-width: 0; font-size: 12.5px; font-weight: 500; line-height: 1.35; } +.md-act-who { font-size: 11px; color: var(--muted); flex-shrink: 0; } +.md-act-add { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 700; padding: 4px 9px; border-radius: 999px; border: 1px solid var(--line-2); color: var(--ink-2); flex-shrink: 0; transition: all 0.13s; } +.md-act-add .ic { width: 11px; height: 11px; } +.md-act-add:hover { border-color: var(--violet); color: var(--violet); } +.md-act-done { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 700; color: var(--green); flex-shrink: 0; } +.md-act-done .ic { width: 11px; height: 11px; } +.md-addall { display: inline-flex; align-items: center; justify-content: center; gap: 7px; width: 100%; margin: 10px 0 8px; padding: 10px; border-radius: 11px; font-size: 12.5px; font-weight: 700; background: var(--fill); color: var(--on-fill); transition: filter 0.13s; } +.md-addall .ic { width: 14px; height: 14px; } +.md-addall:hover { filter: brightness(1.15); } +.md-allok { display: flex; align-items: center; justify-content: center; gap: 7px; margin: 10px 0 8px; padding: 9px; border-radius: 11px; font-size: 12px; font-weight: 700; background: color-mix(in oklab, var(--green) 11%, transparent); color: var(--green); } +.md-allok .ic { width: 13px; height: 13px; } + +/* 회의 도우미 진입점 (어젠다·팝오버) */ +.meet-open { display: inline-flex; align-items: center; justify-content: center; gap: 6px; width: 100%; margin-top: 9px; padding: 8px; border-radius: 10px; font-size: 12px; font-weight: 700; border: 1px solid color-mix(in oklab, var(--evc) 38%, transparent); color: var(--evtx); background: color-mix(in oklab, var(--evc) 7%, transparent); transition: all 0.13s; } +.meet-open .ic { width: 13px; height: 13px; } +.meet-open:hover { background: color-mix(in oklab, var(--evc) 14%, transparent); } + +@media (max-width: 1080px) { .subnav { display: none; } .day-wrap { grid-template-columns: 1fr; } } +@media (max-width: 760px) { + .tpage { padding: 0 14px 22px; } + .topbar { margin: 0 -14px; padding: 0 16px; gap: 14px; } + .mainnav { display: none; } + .ch-spacer { display: none; } + .view-seg { order: 5; width: 100%; justify-content: center; margin-top: 8px; } + .chead { flex-wrap: wrap; } +} diff --git a/design-reference/assets/cal.jsx b/design-reference/assets/cal.jsx new file mode 100644 index 0000000..696a249 --- /dev/null +++ b/design-reference/assets/cal.jsx @@ -0,0 +1,446 @@ +/* 아리 — 일정(Calendar) · 주/월/일 뷰 */ +const { useState, useEffect, useRef } = React; +const C = window.CalData; +const CAL = Object.fromEntries(C.cals.map((c) => [c.id, c])); + +const P = { + spark: "M12 3l1.9 5.2L19 10l-5.1 1.8L12 17l-1.9-5.2L5 10l5.1-1.8z|M19 4v3|M5 17v3", + grid: "M3 3h7v7H3z|M14 3h7v7h-7z|M14 14h7v7h-7z|M3 14h7v7H3z", + inbox: "M22 12h-6l-2 3h-4l-2-3H2|M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z", + route: "M6 19a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M18 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M9 16h6a3 3 0 0 0 3-3V11", + cal: "M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", + check: "M9 11l3 3L22 4|M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11", + tick: "M20 6 9 17l-5-5", + mail: "M22 7 13.03 12.7a1.94 1.94 0 0 1-2.06 0L2 7|M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", + brain: "M12 5a3 3 0 1 0-5.9.8A3 3 0 0 0 4 9.5 3 3 0 0 0 6 14a3 3 0 0 0 6 1zM12 5a3 3 0 1 1 5.9.8A3 3 0 0 1 20 9.5 3 3 0 0 1 18 14a3 3 0 0 1-6 1z|M12 5v14", + heart: "M19 14c1.5-1.5 3-3.4 3-5.5A4.5 4.5 0 0 0 12 5.5 4.5 4.5 0 0 0 2 8.5c0 2.1 1.5 4 3 5.5l7 7z", + search: "M21 21l-4.34-4.34|M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z", + bell: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9|M10.3 21a1.94 1.94 0 0 0 3.4 0", + sun: "M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10z|M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4", + moon: "M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z", + plus: "M12 5v14M5 12h14", + x: "M18 6 6 18M6 6l12 12", + prev: "M15 18l-6-6 6-6", + next: "M9 18l6-6-6-6", + clock: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z|M12 7v5l3 2", + pin: "M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0z|M12 10a3 3 0 1 0 0-6 3 3 0 0 0 0 6z", + users: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2|M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z|M23 21v-2a4 4 0 0 0-3-3.87|M16 3.13a4 4 0 0 1 0 7.75", + edit: "M12 20h9|M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4z", + trash: "M3 6h18|M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2", + wand: "M15 4V2M15 16v-2M8 9h2M20 9h2M17.8 11.8 19 13M17.8 6.2 19 5M3 21l9-9M12.2 6.2 11 5", + zap: "M13 2 3 14h9l-1 8 10-12h-9z", + send: "M22 2 11 13|M22 2 15 22l-4-9-9-4z", + target: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z|M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z|M12 12h.01", +}; +function I({ d, w }) { + return ( + + {P[d].split("|").map((s, i) => )} + + ); +} + +const MAIN = [ + { id: "dash", label: "대시보드", icon: "grid", href: "대시보드.html" }, + { id: "sbox", label: "인박스", icon: "inbox", href: "인박스.html" }, + { id: "appr", label: "결재함", icon: "spark", badge: "3", href: "결재함.html" }, + { id: "auto", label: "자동화", icon: "zap", href: "자동화.html" }, + { id: "journey", label: "여정", icon: "route", href: "여정.html" }, + { id: "cal", label: "일정", icon: "cal", href: "일정.html" }, + { id: "task", label: "작업", icon: "check", badge: "4", href: "작업.html" }, + { id: "mail", label: "메일", icon: "mail", href: "메일.html" }, + { id: "noti", label: "알림", icon: "bell", badge: "6", href: "알림.html" }, + { id: "research", label: "리서치", icon: "brain", href: "리서치.html" }, + { id: "trip", label: "여행", icon: "send", href: "여행.html" }, + { id: "life", label: "라이프", icon: "heart", href: "라이프.html" }, + { id: "wrap", label: "하루 마감", icon: "moon", href: "하루 마감.html" }, +]; +const VIEWS = [{ id: "week", label: "주" }, { id: "month", label: "월" }, { id: "day", label: "일" }]; +const HOURS = []; +for (let h = 7; h <= 22; h++) HOURS.push(h); +const HSTART = 7, PXH = 52; + +const toMin = (t) => { const [h, m] = t.split(":").map(Number); return h * 60 + m; }; +const fmt = (t) => { const [h, m] = t.split(":").map(Number); const ap = h < 12 ? "오전" : "오후"; const hh = h % 12 === 0 ? 12 : h % 12; return `${ap} ${hh}:${String(m).padStart(2, "0")}`; }; +const topOf = (t) => ((toMin(t) - HSTART * 60) / 60) * PXH; +const load = (k, fb) => { try { const v = localStorage.getItem("ariC." + k); return v === null ? fb : JSON.parse(v); } catch (e) { return fb; } }; +const save = (k, v) => { try { localStorage.setItem("ariC." + k, JSON.stringify(v)); } catch (e) {} }; + +/* 회의 도우미 진입 버튼 레이블 */ +const meetLabel = (m) => + m.oneOnOne ? "1:1 어시스턴트 브리핑" + : m.phase === "upcoming" ? "사전 브리핑 보기" + : m.phase === "live" ? "실시간 기록 보기" + : "회의 노트 · 액션 보기"; + +/* ---------- 미니 달력 ---------- */ +function MiniCal({ sel, onSel, eventsByDay }) { + const first = new Date(2026, 5, 1), off = first.getDay(); + const cells = []; + for (let i = 0; i < 42; i++) { + const d = new Date(2026, 5, 1 - off + i); + cells.push({ d, inMonth: d.getMonth() === 5, n: d.getDate() }); + } + return ( +
+
+ 2026년 6월 + +
+
+ {C.weekdays.map((w) =>
{w}
)} + {cells.map((c, i) => ( + + ))} +
+
+ ); +} + +/* ---------- 이벤트 블록 (주/일) ---------- */ +function EvBlock({ ev, onClick }) { + const top = topOf(ev.start), h = ((toMin(ev.end) - toMin(ev.start)) / 60) * PXH; + const short = h < 38; + return ( +
{ e.stopPropagation(); onClick(ev, e); }}> +
{ev.title}
+
{ev.start}–{ev.end}
+
+ ); +} + +/* ---------- 집중 블록 (아리가 빈 시간에 배치한 작업) ---------- */ +function FocusBlock({ b }) { + const top = topOf(b.start), h = ((toMin(b.end) - toMin(b.start)) / 60) * PXH; + const short = h < 40; + return ( +
+
{b.tag}
+
{b.title}
+
+ ); +} + +/* ---------- 주 뷰 ---------- */ +function WeekView({ events, focusBlocks, onOpen }) { + const nowTop = topOf("11:10"); // 오늘 6/8 가상 현재시각 + return ( +
+
+
+ {C.week.map((day, i) => ( +
+
{C.weekdays[i]}
+
{day}
+
+ ))} +
+
+
+ {HOURS.map((h) =>
{h}:00
)} +
+ {C.week.map((day, i) => { + const dayEvents = events.filter((e) => e.day === day); + const dayFocus = focusBlocks.filter((b) => b.day === day); + return ( +
+ {HOURS.map((h) =>
)} + {day === C.today &&
} + {dayFocus.map((b) => )} + {dayEvents.map((ev) => )} +
+ ); + })} +
+
+ ); +} + +/* ---------- 월 뷰 ---------- */ +function MonthView({ events, onOpen, onSelDay }) { + const first = new Date(2026, 5, 1), off = first.getDay(); + const byDay = {}; + events.forEach((e) => { (byDay[e.day] = byDay[e.day] || []).push(e); }); + Object.values(byDay).forEach((a) => a.sort((x, y) => toMin(x.start) - toMin(y.start))); + const cells = []; + for (let i = 0; i < 42; i++) { + const d = new Date(2026, 5, 1 - off + i); + cells.push({ d, inMonth: d.getMonth() === 5, n: d.getDate() }); + } + return ( +
+
+ {C.weekdays.map((w, i) =>
{w}
)} +
+
+ {cells.map((c, i) => { + const evs = c.inMonth ? (byDay[c.n] || []) : []; + return ( +
c.inMonth && onSelDay(c.n)}> +
{c.n}
+ {evs.slice(0, 3).map((ev) => ( +
{ e.stopPropagation(); onOpen(ev, e); }}> + {ev.start}{ev.title} +
+ ))} + {evs.length > 3 &&
+{evs.length - 3}개 더
} +
+ ); + })} +
+
+ ); +} + +/* ---------- 일 뷰 ---------- */ +function DayView({ events, focusBlocks, day, onOpen, onMeet }) { + const dayEvents = events.filter((e) => e.day === day).sort((a, b) => toMin(a.start) - toMin(b.start)); + const dayFocus = focusBlocks.filter((b) => b.day === day).sort((a, b) => toMin(a.start) - toMin(b.start)); + const idx = C.week.indexOf(day); + const wd = idx >= 0 ? C.weekdays[idx] : C.weekdays[new Date(2026, 5, day).getDay()]; + const nowTop = topOf("11:10"); + return ( +
+
+
+
{HOURS.map((h) =>
{h}:00
)}
+
+ {HOURS.map((h) =>
)} + {day === C.today &&
} + {dayFocus.map((b) => )} + {dayEvents.map((ev) => )} +
+
+
+
+ {dayFocus.length > 0 && ( +
+
+
+
오늘의 집중 모드빈 시간에 맞춰 자동 배치했어요
+
+

오전 미팅 사이엔 가벼운 일을, 오후 빈 블록엔 딥 워크를 넣었어요.

+ {dayFocus.map((b) => ( +
+
{b.start}
+
+
{b.tag}
+
{b.title}
+
+
+ ))} +
+ )} +
+
+
6월 {day}일 {wd}요일
+
일정 {dayEvents.length}개{day === C.today ? " · 오늘" : ""}
+
+ {dayEvents.length === 0 &&
이 날은 일정이 없어요.
} + {dayEvents.map((ev) => ( +
onOpen(ev, e)}> +
{ev.start}{ev.end}
+
+
{ev.title} {ev.soon && 곧}
+
+ {CAL[ev.cal].name} + {ev.loc && <>· {ev.loc}} +
+ {ev.people && ( +
+ {ev.people.slice(0, 3).map((p, i) =>
{p[0]}
)} + {ev.people.join(", ")} +
+ )} + {ev.note &&
{ev.note}
} + {ev.actions && ( +
+
내가 맡기로 한 일
+ {ev.actions.map((a, i) => ( +
{a.text}{a.when}
+ ))} +
+ )} + {C.meets[ev.id] && ( + + )} +
+
+ ))} +
+
+
+ ); +} + +/* ---------- 이벤트 팝오버 ---------- */ +function EvPopover({ ev, pos, onClose, onMeet }) { + const cat = CAL[ev.cal]; + const style = { tint: { "--evc": `var(--${cat.tone})` } }; + let left = pos.x, top = pos.y; + if (left + 300 > window.innerWidth - 12) left = window.innerWidth - 312; + if (top + 280 > window.innerHeight - 12) top = window.innerHeight - 292; + return ( + <> +
+
+
+
{ev.title}
+
{fmt(ev.start)} – {fmt(ev.end)}
+
{cat.name}
+ {ev.loc &&
{ev.loc}
} + {ev.people &&
{ev.people.join(", ")}
} + {ev.note &&
{ev.note}
} + {ev.actions && ( +
+
내가 맡기로 한 일{ev.actions.length}
+ {ev.actions.map((a, i) => ( +
+ + {a.text} + {a.when} +
+ ))} + +
+ )} + {C.meets[ev.id] && ( + + )} +
+ + +
+
+ + ); +} + +/* ---------- 앱 ---------- */ +function App() { + const [theme, setTheme] = useState(() => load("theme", "light")); + const [view, setView] = useState(() => load("view", "week")); + const [selDay, setSelDay] = useState(() => load("selDay", C.today)); + const [calOn, setCalOn] = useState(() => load("calOn", Object.fromEntries(C.cals.map((c) => [c.id, true])))); + const [focusOn, setFocusOn] = useState(() => load("focusOn", true)); + const [pop, setPop] = useState(null); + const [meet, setMeet] = useState(null); + const shellRef = useRef(null); + + useEffect(() => { document.documentElement.setAttribute("data-theme", theme); save("theme", theme); }, [theme]); + useEffect(() => save("view", view), [view]); + useEffect(() => save("selDay", selDay), [selDay]); + useEffect(() => save("calOn", calOn), [calOn]); + useEffect(() => save("focusOn", focusOn), [focusOn]); + useEffect(() => { const el = shellRef.current; const id = requestAnimationFrame(() => el && el.classList.add("entered")); return () => cancelAnimationFrame(id); }, []); + + const events = C.events.filter((e) => calOn[e.cal]); + const focusBlocks = focusOn ? C.focusBlocks : []; + const eventsByDay = {}; + events.forEach((e) => { eventsByDay[e.day] = (eventsByDay[e.day] || 0) + 1; }); + const countByCal = {}; + C.cals.forEach((c) => { countByCal[c.id] = C.events.filter((e) => e.cal === c.id).length; }); + + const openEv = (ev, e) => { setPop({ ev, pos: { x: e.clientX + 8, y: e.clientY + 8 } }); }; + const openMeet = (ev) => { setPop(null); setMeet(ev); }; + const toggleCal = (id) => setCalOn((s) => ({ ...s, [id]: !s[id] })); + const goToday = () => { setSelDay(C.today); }; + + const title = view === "month" ? "2026년 6월" + : view === "day" ? `6월 ${selDay}일` + : "6월 7일 – 13일"; + + return ( +
+
+
+
+
아리AI LIFE OS
+
+ +
+ + + +
지
+
+
+ +
+ + +
+
+ + +

{title}

+ + + +
+ {VIEWS.map((v) => ( + + ))} +
+
+ +
+
+

오늘 일정 중 14:00 분기 전략 미팅이 가장 중요해요. 미팅 사이 빈 시간엔 가벼운 일을, 오후 15:10 빈 블록엔 ‘분기 리포트’ 딥 워크를 자동으로 배치해뒀어요. 16:00 ‘치과 예약’은 1:1과 가까워 15분 앞당기길 추천드려요.

+
+ + {view === "week" && } + {view === "month" && { setSelDay(n); setView("day"); }} />} + {view === "day" && } +
+
+ + {pop && setPop(null)} onMeet={openMeet} />} + {meet && C.meets[meet.id] && ( + setMeet(null)} /> + )} +
+ ); +} + +ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/design-reference/assets/dash-v2.jsx b/design-reference/assets/dash-v2.jsx new file mode 100644 index 0000000..11cc4d4 --- /dev/null +++ b/design-reference/assets/dash-v2.jsx @@ -0,0 +1,499 @@ +/* 아리 — 대시보드 v2 (Rond 클린 화이트 + 아리 결재함) + v2: '제안만 하는 AI' → '미리 해두고 승인/되돌리기만 받는 AI' + - ApprovalCard (approve.jsx) · 자율성 수준은 Tweaks로 조절 */ +const { useState, useEffect, useRef } = React; +const D = window.AriData; + +/* ---------- 아이콘 ---------- */ +const P = { + spark: "M12 3l1.9 5.2L19 10l-5.1 1.8L12 17l-1.9-5.2L5 10l5.1-1.8z|M19 4v3|M5 17v3", + grid: "M3 3h7v7H3z|M14 3h7v7h-7z|M14 14h7v7h-7z|M3 14h7v7H3z", + route: "M6 19a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M18 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M9 16h6a3 3 0 0 0 3-3V11", + cal: "M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", + check: "M9 11l3 3L22 4|M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11", + tick: "M20 6 9 17l-5-5", + mail: "M22 7 13.03 12.7a1.94 1.94 0 0 1-2.06 0L2 7|M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", + target: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z|M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z|M12 12h.01", + heart: "M19 14c1.5-1.5 3-3.4 3-5.5A4.5 4.5 0 0 0 12 5.5 4.5 4.5 0 0 0 2 8.5c0 2.1 1.5 4 3 5.5l7 7z", + wallet: "M19 7V5a2 2 0 0 0-2-2H5a2 2 0 0 0 0 4h15a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5|M16 12h.01", + brain: "M12 5a3 3 0 1 0-5.9.8A3 3 0 0 0 4 9.5 3 3 0 0 0 6 14a3 3 0 0 0 6 1zM12 5a3 3 0 1 1 5.9.8A3 3 0 0 1 20 9.5 3 3 0 0 1 18 14a3 3 0 0 1-6 1z|M12 5v14", + zap: "M13 2 3 14h9l-1 8 10-12h-9z", + settings: "M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z", + more: "M5 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0zM11 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0zM17 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0z", + search: "M21 21l-4.34-4.34|M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z", + bell: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9|M10.3 21a1.94 1.94 0 0 0 3.4 0", + back: "M19 12H5|M12 19l-7-7 7-7", + send: "M22 2 11 13|M22 2 15 22l-4-9-9-4z", + sun: "M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10z|M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4", + moon: "M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z", + video: "M23 7l-7 5 7 5zM1 5h15a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H1a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2z", + swap: "M16 3h5v5|M21 3l-7 7|M8 21H3v-5|M3 21l7-7", + up: "M12 19V5|M5 12l7-7 7 7", + foot: "M4 16c0-2 .5-4 2-4s2 1 2 3 .5 4-1 4-3-1-3-3z|M8 7c0-2 .5-3 1.5-3S11 5 11 7s0 3-1.5 3S8 9 8 7z|M14 16c0-2-.5-4-2-4s-2 1-2 3-.5 4 1 4 3-1 3-3z|M16 7c0-2-.5-3-1.5-3S13 5 13 7s0 3 1.5 3S16 9 16 7z", + drop: "M12 2.7S5 10 5 14a7 7 0 0 0 14 0c0-4-7-11.3-7-11.3z", + zzz: "M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z|M16 4h4l-4 4h4", + arrow: "M5 12h14|M13 6l6 6-6 6", + pin: "M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0z|M12 10a3 3 0 1 0 0-6 3 3 0 0 0 0 6z", + inbox: "M22 12h-6l-2 3h-4l-2-3H2|M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z", + mic: "M12 2a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z|M19 10v1a7 7 0 0 1-14 0v-1|M12 18v4M8 22h8", + image: "M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z|M8.5 11a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z|M21 15l-5-5L5 21", + pen: "M12 20h9|M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z", + layers: "M12 2 2 7l10 5 10-5z|M2 17l10 5 10-5M2 12l10 5 10-5", + users: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2|M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z|M23 21v-2a4 4 0 0 0-3-3.87|M16 3.13a4 4 0 0 1 0 7.75", + clock: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z|M12 7v5l3 2", + branch: "M6 3v12|M18 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M15 12H9a3 3 0 0 0-3 3", + play: "M6 3l15 9-15 9z", + flag: "M4 22V4M4 4s1.5-1 5-1 5 2 8 2 4-1 4-1v10s-1.5 1-4 1-5-2-8-2-5 1-5 1", + rain: "M16 13v6M8 13v6M12 15v6|M20 16.6A5 5 0 0 0 18 7h-1.3A8 8 0 1 0 4 14.9", + plus: "M12 5v14M5 12h14", + scaffold: "M3 9h18M9 21V9|M4 4h16a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1z", +}; +function I({ d, w }) { + return ( + + {P[d].split("|").map((s, i) => )} + + ); +} + +const TONE = { blue: "var(--blue)", violet: "var(--violet)", coral: "var(--coral)", green: "var(--green)" }; + +const MAIN = [ + { id: "dash", label: "대시보드", icon: "grid", href: "대시보드.html" }, + { id: "sbox", label: "인박스", icon: "inbox", href: "인박스.html" }, + { id: "appr", label: "결재함", icon: "spark", badge: "3", href: "결재함.html" }, + { id: "auto", label: "자동화", icon: "zap", href: "자동화.html" }, + { id: "journey", label: "여정", icon: "route", href: "여정.html" }, + { id: "cal", label: "일정", icon: "cal", href: "일정.html" }, + { id: "task", label: "작업", icon: "check", badge: "4", href: "작업.html" }, + { id: "mail", label: "메일", icon: "mail", href: "메일.html" }, + { id: "noti", label: "알림", icon: "bell", badge: "6", href: "알림.html" }, + { id: "research", label: "리서치", icon: "brain", href: "리서치.html" }, + { id: "trip", label: "여행", icon: "send", href: "여행.html" }, + { id: "life", label: "라이프", icon: "heart", href: "라이프.html" }, + { id: "wrap", label: "하루 마감", icon: "moon", href: "하루 마감.html" }, +]; +const CURRENT = "dash"; +const RAIL = [ + { icon: "grid", label: "오늘 개요" }, + { icon: "cal", label: "일정" }, + { icon: "check", label: "할 일" }, + { icon: "mail", label: "메일" }, + { icon: "target", label: "목표" }, + { icon: "heart", label: "건강" }, + { icon: "wallet", label: "지출" }, + { icon: "zap", label: "자동화" }, + { icon: "brain", label: "리서치" }, +]; +const CHIPS = [ + { icon: "cal", text: "내일 오후 비워줘" }, + { icon: "mail", text: "중요 메일만 요약" }, + { icon: "video", text: "14시 미팅 준비" }, +]; + +const PRIO = { 높음: "high", 보통: "mid", 낮음: "low" }; +const AVCOL = ["var(--coral)", "var(--blue)", "var(--violet)"]; + +/* 요약 카드용 데이터 — 실행은 전용 페이지(결재함·스마트 인박스)에서 */ +const AP = window.AriApprovals; +const SB = window.AriSmartInbox; +const apPending = AP.items.filter((i) => i.risk === "high"); + +function App() { + const [theme, setTheme] = useState("light"); + const [tasks, setTasks] = useState(D.tasks); + const shellRef = useRef(null); + + useEffect(() => { document.documentElement.setAttribute("data-theme", theme); }, [theme]); + useEffect(() => { + const el = shellRef.current; + const id = requestAnimationFrame(() => el && el.classList.add("entered")); + return () => cancelAnimationFrame(id); + }, []); + + const toggle = (id) => setTasks((t) => t.map((x) => (x.id === id ? { ...x, done: !x.done } : x))); + const openCount = tasks.filter((t) => !t.done).length; + + return ( +
+ {/* ===== 상단바 — 메인 메뉴 ===== */} +
+
+
+
아리AI LIFE OS
+
+ +
+ + + +
{D.user.initial}
+
+
+ + {/* ===== 페이지 헤더 ===== */} +
+
+
+ {D.today} + + {D.weather.cond} +
+

좋은 아침이에요, {D.user.name}님 ☀

+
+
+ + +
+
+ + {/* ===== 작업 영역 ===== */} +
+ {/* 보드 — 베n토 그리드 */} +
+ {/* 히어로 — 브리핑 + 명령 */} +
+ 아리 브리핑 +

+

+ {CHIPS.map((c, i) => ( + + ))} +
+
+ + + +
+
+ + {/* 결재함 요약 — 실행은 전용 페이지에서 */} +
+
+
+

아리 결재함

오늘 {AP.savedToday} 아껴드렸어요
+ 대기 {apPending.length}건 +
+
+ {apPending.map((it) => ( +
+ + {it.title} + {it.time} +
+ ))} +
+ 결재함에서 승인하기 +
+ + {/* 인박스 요약 — 캡처·분류는 전용 페이지에서 */} +
+
+
+

스마트 인박스

적으면 아리가 제자리로
+ 오늘 {SB.todayRouted}건 +
+
+ {SB.items.slice(0, 3).map((c) => ( +
+ + {c.raw} + {c.route.typeLabel} +
+ ))} +
+ 새로 적기 · 인박스 열기 +
+ + {/* 오늘의 집중 모드 — 캘린더 빈 블록에 맞춰 큐레이션 */} +
+
+
+

오늘의 집중

Focus of the Day
+ 4블록 +
+

+

+ {D.focus.blocks.map((b, i) => ( +
+
{b.time}
+
+
+ {b.tag} + {b.len} +
+
{b.title}
+
{b.gap}
+
+
+ ))} +
+
+ + {/* 오늘 일정 */} +
+
+
+

오늘 일정

4개 · 다음까지 2시간
+ +
+
+ {D.schedule.map((e, i) => ( +
+
{e.time}
+
+
{e.title}
+
{e.tag}{e.dur}
+
+ {e.soon && 곧} +
+ ))} +
치과 예약은 16:00로 옮겨뒀어요 — 결재함에서 되돌릴 수 있어요.
+
+
+ + {/* 할 일 */} +
+
+
+

할 일

+ {openCount}개 남음 +
+
+ {tasks.map((t, idx) => { + const focus = !t.done && idx === 0; + return ( +
toggle(t.id)}> +
+
+
{t.title}
+
{t.project}
+
+ {focus + ? 지금 + : !t.done && {t.prio}} +
+ ); + })} +
+
+ + {/* 미팅 준비 */} +
+
+
+

미팅 준비

+
+
{D.meeting.title} · {D.meeting.when}
+
+ {D.meeting.people.slice(0, 3).map((p, i) => ( +
{p[0]}
+ ))} + {D.meeting.people.join(", ")} +
+
    + {D.meeting.brief.map((b, i) => ( +
  • {b}
  • + ))} +
+
+
내가 맡기로 한 일 · {D.meeting.recap}
+ {D.meeting.actions.map((a, i) => ( +
+ + {a.text} + {a.when} +
+ ))} +
+ +
+ + {/* 위임 현황 — 매니지먼트 & 위임 */} +
+
+
+

위임 현황

대기 중인 작업
+
+
+
+

오늘 일정이 꽉 차 ‘{D.delegation.suggest.task}’를 직접 하기 빠듯해요. ‘{D.delegation.suggest.proj}’를 함께 하는 {D.delegation.suggest.who}님께 넘기고 진행은 제가 추적할게요.

+
+
+ {D.delegation.waiting.map((w, i) => ( +
+
{w.initial}
+
+
{w.task}
+
{w.who}님 · {w.note}
+
+ {w.state} +
+ ))} +
+
+ + {/* 이어가기 & 우선순위 — 개인 프로젝트 스마트 어시스턴트 */} +
+
+
+

이어서 하기

맥락이 끊기지 않게
+
+
+
+ {D.resume.project} + {D.resume.ago} +
+

{D.resume.context}

+
다음{D.resume.next}
+ +
+
+
+
+
{D.envNudge.task}
+
{D.envNudge.reason}
+
+ 최우선 +
+
+ + {/* 이번 주 집중도 */} +
+
+
+

이번 주 집중도

딥 워크 시간
+ +
+
+ 27.4시간 + +12% +
+
+ + + + + + + + + + + +
+ {["월","화","수","목","금","토","일"].map((d) => {d})} +
+
+
+ + {/* 목표 */} +
+
+
+

목표

+
+
+ {D.goals.map((g, i) => ( +
+
+ {g.title} + {g.pct}% +
+
+
{g.sub}
+
+ ))} +
+
+ + {/* 메일 요약 */} +
+
+
+

메일 요약

+ {D.emailCount}통 +
+
+ {D.emails.map((m, i) => ( +
+ +
+
+ {m.from} + {m.task && 할 일} + {m.time} +
+
{m.subj}
+
{m.summary}
+
+
+ ))} +
+
+ + {/* 건강 */} +
+
+
+

건강 & 웰니스

+
+
+ {D.health.rings.map((r, i) => { + const ico = { footprints: "foot", moonZz: "zzz", droplet: "drop" }[r.icon]; + return ( +
+
+ +
+
{r.val}
+
{r.label}
+
+ ); + })} +
+
{D.health.nudge}
+
+ + {/* 지출 */} +
+
+
+

이번 달 지출

+
+
{D.finance.spentPct}%예산 사용
+
{D.finance.delta}
+
+
+ ₩{D.finance.spent}₩{D.finance.budget} +
+
+ {D.finance.subs.map((s, i) => ( +
+ {s.name} + {s.date} + ₩{s.amt} +
+ ))} +
+
+
+
+ +
+ ); +} + +ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/design-reference/assets/dash.css b/design-reference/assets/dash.css new file mode 100644 index 0000000..8037112 --- /dev/null +++ b/design-reference/assets/dash.css @@ -0,0 +1,800 @@ +/* =========================================================== + 아리 — 대시보드 · 'Rond' 클린 화이트 테마 + 레퍼런스 팔레트 충실 분석: + - 배경: 상단 옅은 웜 크림 → 하단 쿨 라이트 그레이 그라데이션 + - 카드: 순백 + 큰 라운드 + 아주 옅은 중립 그림자 + - 잉크: 거의 검정 차콜 / 단일 라임그린 CTA / 선택=차콜 채움 + - 컬러 액센트는 점·바에만 절제해서 사용 + 상단바 = 메인 메뉴 · 좌측 아이콘 레일 = 서브 메뉴 + =========================================================== */ + +* { box-sizing: border-box; } +html, body { margin: 0; padding: 0; } + +:root { + --bg-top: #f6efe7; + --bg-mid: #eef0f1; + --bg-bot: #e9ebed; + --card: #ffffff; + --card-2: #f4f5f6; + --card-3: #eef0f1; + --ink: #211f1c; + --ink-2: #514d47; + --muted: #8d8a85; + --faint: #b6b3ad; + --line: rgba(33, 31, 28, 0.07); + --line-2: rgba(33, 31, 28, 0.13); + --fill: #29241f; + --fill-soft: #36302a; + --on-fill: #f4efe6; + --lime: #c2f24a; + --lime-hi: #cdf85e; + --lime-ink: #233006; + --blue: #4f72e0; + --coral: #df7256; + --green: #4e9b66; + --violet: #8b6fd4; + --amber: #e0a23c; + --radius: 22px; + --radius-sm: 14px; + --shadow: 0 10px 30px -14px rgba(30, 26, 22, 0.22), 0 2px 6px -2px rgba(30, 26, 22, 0.06); + --shadow-sm: 0 4px 14px -8px rgba(30, 26, 22, 0.18); + /* 글래스 */ + --glass: rgba(255, 255, 255, 0.4); + --glass-2: rgba(255, 255, 255, 0.26); + --glass-brd: rgba(255, 255, 255, 0.7); + --glass-ink: rgba(26, 22, 18, 0.46); + --blur: blur(26px) saturate(190%); + --glass-hi: inset 0 1px 0 rgba(255, 255, 255, 0.75); + --font-disp: "Onest", "Pretendard", system-ui, sans-serif; + --font-ui: "Pretendard", "Onest", system-ui, sans-serif; + --font-mono: "DM Mono", ui-monospace, monospace; +} + +[data-theme="dark"] { + --bg-top: #201d1a; + --bg-mid: #1a1816; + --bg-bot: #161413; + --card: #2c2925; + --card-2: #353029; + --card-3: #3b352d; + --ink: #f3eee6; + --ink-2: #c3bdb4; + --muted: #948e85; + --faint: #6f6a62; + --line: rgba(244, 239, 230, 0.09); + --line-2: rgba(244, 239, 230, 0.16); + --fill: #f4efe6; + --fill-soft: #e7e0d4; + --on-fill: #211f1c; + --shadow: 0 14px 36px -16px rgba(0, 0, 0, 0.7), 0 2px 8px -3px rgba(0, 0, 0, 0.4); + --shadow-sm: 0 6px 16px -10px rgba(0, 0, 0, 0.6); + --glass: rgba(48, 43, 38, 0.4); + --glass-2: rgba(70, 63, 55, 0.28); + --glass-brd: rgba(255, 255, 255, 0.1); + --glass-ink: rgba(16, 13, 11, 0.42); + --glass-hi: inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +body { + font-family: var(--font-ui); + color: var(--ink); + background: linear-gradient(178deg, var(--bg-top) 0%, var(--bg-mid) 20%, var(--bg-bot) 100%); + background-attachment: fixed; + min-height: 100vh; + -webkit-font-smoothing: antialiased; + letter-spacing: -0.011em; + word-break: keep-all; +} +button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; } +input { font-family: inherit; } +:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; border-radius: 6px; } + +.ic { width: 1em; height: 1em; flex-shrink: 0; display: block; } +.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; } + +.dash { + max-width: 1620px; + margin: 0 auto; + padding: 0 26px 40px; +} + +/* ====================== 상단바 — 메인 메뉴 ====================== */ +.topbar { + position: sticky; + top: 0; + z-index: 40; + display: flex; + align-items: center; + gap: 24px; + height: 70px; + margin: 0 -26px; + padding: 0 30px; + -webkit-backdrop-filter: blur(14px) saturate(150%); + backdrop-filter: blur(14px) saturate(150%); +} +.brand { display: flex; align-items: center; gap: 11px; flex-shrink: 0; } +.brand-mark { + width: 40px; height: 40px; + border-radius: 12px; + background: var(--fill); + color: var(--coral); + display: grid; place-items: center; + position: relative; +} +.brand-mark .ic { width: 21px; height: 21px; } +.brand-mark::after { + content: ""; position: absolute; + top: -2px; right: -2px; + width: 9px; height: 9px; + border-radius: 50%; + background: var(--coral); + border: 2px solid var(--bg-top); +} +.brand-tag { line-height: 1.15; } +.brand-tag b { display: block; font-size: 15px; font-weight: 700; letter-spacing: -0.02em; } +.brand-tag span { + font-size: 9.5px; font-weight: 700; letter-spacing: 0.08em; + color: var(--muted); text-transform: uppercase; white-space: nowrap; +} +.brand-tag b { white-space: nowrap; } + +.mainnav { + display: flex; align-items: center; gap: 2px; + min-width: 0; overflow-x: auto; scrollbar-width: none; +} +.mainnav::-webkit-scrollbar { display: none; } +.mainnav a { + text-decoration: none; cursor: pointer; + display: inline-flex; align-items: center; gap: 8px; + font-size: 14px; font-weight: 500; + color: var(--ink-2); + padding: 9px 16px; + border-radius: 999px; + white-space: nowrap; + transition: background 0.16s, color 0.16s, box-shadow 0.16s; +} +.mainnav a .ic { width: 16px; height: 16px; color: var(--muted); transition: color 0.16s; } +.mainnav a:hover { color: var(--ink); } +.mainnav a:hover .ic { color: var(--ink-2); } +.mainnav a.active { + background: var(--glass); + -webkit-backdrop-filter: var(--blur); + backdrop-filter: var(--blur); + color: var(--ink); + font-weight: 600; + box-shadow: var(--shadow-sm); + border: 1px solid var(--glass-brd); +} +.mainnav a.active .ic { color: var(--ink); } +.mainnav a .badge { + font-size: 11px; font-weight: 700; + background: var(--coral); color: #fff; + border-radius: 999px; padding: 0 6px; min-width: 18px; text-align: center; +} + +.top-actions { margin-left: auto; display: flex; align-items: center; gap: 9px; flex-shrink: 0; } +.t-btn { + width: 40px; height: 40px; + border-radius: 50%; + display: grid; place-items: center; + color: var(--ink-2); + background: var(--glass); + -webkit-backdrop-filter: var(--blur); + backdrop-filter: var(--blur); + border: 1px solid var(--glass-brd); + box-shadow: var(--shadow-sm); + position: relative; + transition: transform 0.12s, color 0.16s; +} +.t-btn .ic { width: 18px; height: 18px; } +.t-btn:hover { color: var(--ink); transform: translateY(-1px); } +.t-btn.dot::after { + content: ""; position: absolute; top: 9px; right: 10px; + width: 7px; height: 7px; border-radius: 50%; + background: var(--coral); border: 2px solid var(--card); +} +.t-ava { + width: 40px; height: 40px; border-radius: 50%; + background: linear-gradient(135deg, var(--coral), var(--violet)); + color: #fff; display: grid; place-items: center; + font-weight: 700; font-size: 15px; + box-shadow: var(--shadow-sm); + cursor: pointer; +} + +/* ====================== 페이지 헤더 ====================== */ +.pagehead { + display: flex; + align-items: flex-end; + gap: 18px; + padding: 14px 4px 22px; +} +.ph-back { + width: 46px; height: 46px; border-radius: 50%; + display: grid; place-items: center; + background: var(--glass); border: 1px solid var(--glass-brd); + -webkit-backdrop-filter: var(--blur); + backdrop-filter: var(--blur); + color: var(--ink); box-shadow: var(--shadow-sm); + margin-bottom: 4px; + transition: transform 0.12s; +} +.ph-back .ic { width: 19px; height: 19px; } +.ph-back:hover { transform: translateX(-2px); } +.ph-eyebrow { + font-size: 13.5px; color: var(--muted); font-weight: 500; + margin-bottom: 7px; display: flex; align-items: center; gap: 9px; +} +.ph-eyebrow .wx { display: inline-flex; align-items: center; gap: 5px; color: var(--ink-2); } +.ph-eyebrow .wx .ic { width: 15px; height: 15px; color: var(--amber); } +.ph-eyebrow .sep { width: 4px; height: 4px; border-radius: 50%; background: var(--faint); } +.ph-title { + font-family: var(--font-disp); + font-size: clamp(30px, 3.4vw, 46px); + font-weight: 700; + letter-spacing: -0.03em; + line-height: 1; + margin: 0; +} +.ph-title em { font-style: normal; color: var(--muted); } +.ph-search { + margin-left: auto; + display: flex; align-items: center; gap: 10px; + width: 250px; padding: 12px 18px; + border-radius: 999px; + background: var(--glass); border: 1px solid var(--glass-brd); + -webkit-backdrop-filter: var(--blur); + backdrop-filter: var(--blur); + box-shadow: var(--shadow-sm); + margin-bottom: 4px; + transition: box-shadow 0.18s, border-color 0.18s; +} +.ph-search:focus-within { border-color: var(--line-2); box-shadow: 0 0 0 4px rgba(79,114,224,0.12); } +.ph-search .ic { width: 17px; height: 17px; color: var(--muted); } +.ph-search input { + flex: 1; min-width: 0; border: none; background: none; outline: none; + font-size: 14px; color: var(--ink); +} +.ph-search input::placeholder { color: var(--faint); } + +/* ====================== 작업 영역 (레일 + 보드) ====================== */ +.work { display: flex; gap: 18px; align-items: flex-start; } + +.rail { + position: sticky; top: 86px; + display: flex; flex-direction: column; align-items: center; gap: 6px; + flex-shrink: 0; +} +.rail button { + width: 44px; height: 44px; + border-radius: 13px; + display: grid; place-items: center; + color: var(--muted); + position: relative; + transition: background 0.15s, color 0.15s; +} +.rail button .ic { width: 19px; height: 19px; } +.rail button:hover { background: var(--glass); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); color: var(--ink-2); box-shadow: var(--shadow-sm); border: 1px solid var(--glass-brd); } +.rail button.active { background: var(--fill); color: var(--on-fill); box-shadow: var(--shadow); } +.rail button .tip { + position: absolute; left: 50%; bottom: calc(100% + 8px); transform: translateX(-50%) translateY(4px) scale(0.96); + background: var(--fill); color: var(--on-fill); + font-size: 12px; font-weight: 600; white-space: nowrap; + padding: 5px 10px; border-radius: 8px; + opacity: 0; pointer-events: none; transition: opacity 0.14s, transform 0.14s; + z-index: 60; + box-shadow: var(--shadow-sm); +} +.rail button .tip::after { + content: ""; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); + border: 5px solid transparent; border-top-color: var(--fill); +} +.rail button:hover .tip { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); } +.rail .sp { height: 10px; } +.rail button .rdot { + position: absolute; top: 7px; right: 7px; + width: 7px; height: 7px; border-radius: 50%; + background: var(--coral); border: 2px solid var(--bg-top); +} +.rail button.active .rdot { border-color: var(--fill); } + +/* ====================== 보드 (베n토 그리드) ====================== */ +.board { + flex: 1; min-width: 0; + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 16px; +} +.card { + background: var(--glass); + -webkit-backdrop-filter: var(--blur); + backdrop-filter: var(--blur); + border: 1px solid var(--glass-brd); + border-radius: var(--radius); + box-shadow: var(--shadow), var(--glass-hi); + padding: 20px; + display: flex; flex-direction: column; + min-width: 0; +} +.card.sp2 { grid-column: span 2; } +.card.r2 { grid-row: span 2; } + +.ch { + display: flex; align-items: center; gap: 10px; margin-bottom: 16px; +} +.ch .ico { + width: 34px; height: 34px; border-radius: 10px; + display: grid; place-items: center; + background: var(--glass-2); color: var(--ink); + border: 1px solid var(--glass-brd); + flex-shrink: 0; +} +.ch .ico .ic { width: 17px; height: 17px; } +.ch .htext { min-width: 0; } +.ch h3 { font-size: 15.5px; font-weight: 700; letter-spacing: -0.02em; margin: 0; white-space: nowrap; } +.ch .sub { font-size: 12px; color: var(--muted); margin-top: 1px; } +.ch .tool { + margin-left: auto; width: 30px; height: 30px; border-radius: 9px; + display: grid; place-items: center; color: var(--muted); + transition: background 0.15s, color 0.15s; +} +.ch .tool .ic { width: 16px; height: 16px; } +.ch .tool:hover { background: var(--card-2); color: var(--ink); } +.ch .count { + margin-left: auto; + font-size: 12px; font-weight: 700; color: var(--ink-2); + background: var(--glass-2); border: 1px solid var(--glass-brd); + border-radius: 999px; padding: 3px 10px; +} + +/* ---------- 히어로: 브리핑 + 명령 ---------- */ +.hero { + background: + radial-gradient(95% 105% at 0% 0%, rgba(231, 128, 82, 0.22), transparent 56%), + radial-gradient(130% 130% at 100% 0%, rgba(255,255,255,0.42), transparent 50%), + var(--glass); + -webkit-backdrop-filter: var(--blur); + backdrop-filter: var(--blur); + border: 1px solid var(--glass-brd); + box-shadow: var(--shadow), var(--glass-hi); + color: var(--ink); +} +.hero .ch h3 { color: var(--ink); } +.hero-spark { + display: inline-flex; align-items: center; gap: 7px; + font-size: 12px; font-weight: 700; letter-spacing: 0.02em; + color: #fff; + background: var(--coral); + border: 1px solid transparent; + padding: 5px 12px; border-radius: 999px; + align-self: flex-start; + box-shadow: 0 5px 14px -5px var(--coral); +} +.hero-spark .ic { width: 14px; height: 14px; } +.hero-spark { white-space: nowrap; } +.hero p.brief { + font-size: clamp(18px, 1.7vw, 22px); + line-height: 1.5; + letter-spacing: -0.02em; + margin: 16px 0 18px; + color: var(--ink); + max-width: 40ch; +} +.hero p.brief b { + color: var(--ink); font-weight: 700; + border-bottom: 2px solid color-mix(in oklab, var(--coral) 60%, transparent); + padding-bottom: 1px; +} +.cmd { + display: flex; align-items: center; gap: 10px; + background: var(--glass-2); + border: 1px solid var(--glass-brd); + border-radius: 15px; + padding: 7px 7px 7px 16px; + margin-top: auto; +} +.cmd .ic.lead { width: 18px; height: 18px; color: var(--ink-2); } +.cmd > svg:first-child { color: var(--ink-2); } +.cmd input { + flex: 1; min-width: 0; border: none; background: none; outline: none; + color: var(--ink); font-size: 14.5px; +} +.cmd input::placeholder { color: var(--faint); } +.cmd .send { + width: 38px; height: 38px; border-radius: 11px; + background: var(--fill); color: var(--on-fill); + display: grid; place-items: center; flex-shrink: 0; + transition: filter 0.14s; +} +.cmd .send .ic { width: 18px; height: 18px; } +.cmd .send:hover { filter: brightness(1.05); } +.chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 12px; } +.chip { + display: inline-flex; align-items: center; gap: 7px; + font-size: 12.5px; font-weight: 500; + color: var(--ink-2); + background: var(--glass-2); + border: 1px solid var(--glass-brd); + padding: 7px 12px; border-radius: 999px; + transition: background 0.14s; +} +.chip .ic { width: 14px; height: 14px; color: var(--muted); } +.chip:hover { background: var(--card-2); } + +/* ---------- 오늘 일정 ---------- */ +.sched { display: flex; flex-direction: column; } +.ev { + display: flex; gap: 13px; padding: 13px 0; + border-top: 1px solid var(--line); +} +.ev:first-of-type { border-top: none; padding-top: 4px; } +.ev-time { + font-family: var(--font-mono); font-size: 13px; font-weight: 500; + color: var(--ink-2); width: 42px; flex-shrink: 0; padding-top: 1px; +} +.ev-body { flex: 1; min-width: 0; position: relative; padding-left: 15px; } +.ev-body::before { + content: ""; position: absolute; left: 0; top: 4px; bottom: 4px; + width: 3px; border-radius: 3px; background: var(--accent, var(--blue)); +} +.ev-title { font-size: 14px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; } +.ev-meta { font-size: 12px; color: var(--muted); margin-top: 3px; display: flex; align-items: center; gap: 7px; } +.ev-meta .dur { color: var(--faint); } +.ev .soon { + font-size: 10.5px; font-weight: 700; letter-spacing: 0.02em; + color: #fff; background: var(--coral); + padding: 1px 7px; border-radius: 999px; align-self: flex-start; margin-top: 1px; + flex-shrink: 0; +} +.sched-note { + margin-top: 12px; display: flex; gap: 9px; align-items: flex-start; + background: var(--glass-2); border: 1px solid var(--glass-brd); border-radius: var(--radius-sm); + padding: 11px 13px; font-size: 12.5px; color: var(--ink-2); line-height: 1.4; +} +.sched-note .ic { width: 15px; height: 15px; color: var(--coral); flex-shrink: 0; margin-top: 1px; } + +/* ---------- 할 일 ---------- */ +.tasks { display: flex; flex-direction: column; gap: 8px; } +.task { + display: flex; align-items: center; gap: 11px; + padding: 12px 13px; + border-radius: var(--radius-sm); + border: 1px solid var(--glass-brd); + background: var(--glass-2); + cursor: pointer; + transition: background 0.14s, border-color 0.14s; +} +.task:hover { background: var(--card-2); } +.task .box { + width: 21px; height: 21px; border-radius: 7px; + border: 2px solid var(--line-2); + display: grid; place-items: center; flex-shrink: 0; + color: transparent; transition: all 0.14s; +} +.task .box .ic { width: 13px; height: 13px; } +.task.done .box { background: var(--green); border-color: var(--green); color: #fff; } +.task-body { flex: 1; min-width: 0; } +.task-title { font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; } +.task.done .task-title { color: var(--faint); text-decoration: line-through; } +.task-meta { font-size: 11.5px; color: var(--muted); margin-top: 2px; } +.task .prio { + font-size: 10.5px; font-weight: 700; flex-shrink: 0; + padding: 2px 8px; border-radius: 999px; +} +.prio.high { color: var(--coral); background: color-mix(in oklab, var(--coral) 14%, transparent); } +.prio.mid { color: var(--amber); background: color-mix(in oklab, var(--amber) 16%, transparent); } +.prio.low { color: var(--muted); background: var(--card-2); } + +/* 포커스 작업 — 따뜻한 코럴 액센트 (라이트 글래스) */ +.task.focus { + background: + linear-gradient(90deg, color-mix(in oklab, var(--coral) 13%, transparent), transparent 58%), + var(--glass); + border-color: color-mix(in oklab, var(--coral) 38%, var(--glass-brd)); + box-shadow: var(--shadow-sm), inset 3px 0 0 0 var(--coral); +} +.task.focus .task-title { color: var(--ink); font-weight: 700; } +.task.focus .task-meta { color: var(--muted); } +.task.focus .box { border-color: var(--coral); background: color-mix(in oklab, var(--coral) 14%, transparent); } +.task.focus .prio.high { color: var(--coral); background: color-mix(in oklab, var(--coral) 14%, transparent); } +.task.focus .focus-tag { + font-size: 10px; font-weight: 700; letter-spacing: 0.04em; + color: #fff; background: var(--coral); + padding: 1px 7px; border-radius: 999px; +} + +/* ---------- 미팅 준비 ---------- */ +.mtg-when { font-size: 12.5px; color: var(--muted); margin-bottom: 12px; } +.mtg-people { display: flex; align-items: center; margin-bottom: 14px; } +.mtg-people .av { + width: 30px; height: 30px; border-radius: 50%; + display: grid; place-items: center; + font-size: 11px; font-weight: 700; color: #fff; + border: 2px solid var(--card); margin-left: -8px; +} +.mtg-people .av:first-child { margin-left: 0; } +.mtg-people .more { font-size: 12px; color: var(--muted); margin-left: 10px; } +.mtg-brief { display: flex; flex-direction: column; gap: 9px; margin-bottom: 16px; } +.mtg-brief li { + display: flex; gap: 9px; align-items: flex-start; + font-size: 13px; color: var(--ink-2); line-height: 1.4; list-style: none; +} +.mtg-brief li .dot { + width: 6px; height: 6px; border-radius: 50%; background: var(--ink-2); + margin-top: 6px; flex-shrink: 0; +} +.mtg-cta { + margin-top: auto; + display: flex; align-items: center; justify-content: center; gap: 8px; + padding: 12px; border-radius: 13px; + background: var(--fill); color: var(--on-fill); + font-size: 13.5px; font-weight: 600; + transition: filter 0.14s; +} +.mtg-cta { white-space: nowrap; } +.mtg-cta:hover { filter: brightness(1.1); } +.mtg-cta .ic { width: 16px; height: 16px; } + +/* ---------- 집중도 차트 ---------- */ +.focus-stat { display: flex; align-items: baseline; gap: 8px; margin-bottom: 4px; flex-wrap: nowrap; white-space: nowrap; } +.focus-stat .u { min-width: 0; white-space: nowrap; } +.focus-stat b { font-size: 28px; font-weight: 700; letter-spacing: -0.02em; font-family: var(--font-disp); } +.focus-stat .u { font-size: 13px; color: var(--muted); } +.focus-stat .delta { margin-left: auto; font-size: 12px; font-weight: 600; color: var(--green); display: inline-flex; align-items: center; gap: 3px; } +.focus-stat .delta .ic { width: 13px; height: 13px; } +.chart { margin-top: 10px; flex: 1; min-height: 0; display: flex; flex-direction: column; } +.chart svg { width: 100%; height: 100%; min-height: 120px; display: block; overflow: visible; } +.chart-x { display: flex; justify-content: space-between; margin-top: 8px; } +.chart-x span { font-size: 10.5px; color: var(--faint); font-family: var(--font-mono); } + +/* ---------- 목표 ---------- */ +.goals { display: flex; flex-direction: column; gap: 15px; } +.goal-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; margin-bottom: 7px; } +.goal-title { font-size: 13px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; flex: 1; min-width: 0; } +.goal-pct { flex-shrink: 0; } +.goal-pct { font-size: 13px; font-weight: 700; font-family: var(--font-mono); } +.goal-bar { height: 7px; border-radius: 999px; background: var(--card-2); overflow: hidden; } +.goal-bar i { display: block; height: 100%; border-radius: 999px; } +.goal-sub { font-size: 11.5px; color: var(--muted); margin-top: 5px; } + +/* ---------- 메일 요약 ---------- */ +.mails { display: flex; flex-direction: column; } +.mail-row { + display: flex; gap: 11px; padding: 12px 0; + border-top: 1px solid var(--line); cursor: pointer; +} +.mail-row:first-of-type { border-top: none; padding-top: 2px; } +.mail-dot { width: 8px; height: 8px; border-radius: 50%; margin-top: 6px; flex-shrink: 0; } +.mail-body { flex: 1; min-width: 0; } +.mail-from { display: flex; align-items: center; gap: 7px; } +.mail-from { min-width: 0; } +.mail-from b { font-size: 13px; font-weight: 700; letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 1 1 auto; } +.mail-from .t { font-size: 11px; color: var(--faint); white-space: nowrap; flex-shrink: 0; } +.mail-task { flex-shrink: 0; } +.mail-subj { font-size: 13px; color: var(--ink-2); font-weight: 500; margin-top: 2px; line-height: 1.3; } +.mail-sum { font-size: 11.5px; color: var(--muted); margin-top: 2px; line-height: 1.3; } +.mail-task { + font-size: 10px; font-weight: 700; color: var(--blue); white-space: nowrap; + background: color-mix(in oklab, var(--blue) 12%, transparent); + padding: 1px 6px; border-radius: 999px; margin-left: 6px; +} + +/* ---------- 건강 ---------- */ +.rings { display: flex; justify-content: space-around; gap: 8px; margin-bottom: 14px; } +.ring { display: flex; flex-direction: column; align-items: center; gap: 8px; } +.ring .dial { + width: 58px; height: 58px; border-radius: 50%; + display: grid; place-items: center; + position: relative; +} +.ring .dial::before { + content: ""; position: absolute; inset: 6px; border-radius: 50%; background: var(--card); +} +.ring .dial .ic { width: 17px; height: 17px; position: relative; z-index: 1; } +.ring .rv { font-size: 13px; font-weight: 700; font-family: var(--font-mono); } +.ring .rl { font-size: 11px; color: var(--muted); } +.health-nudge { + margin-top: auto; + display: flex; gap: 9px; align-items: flex-start; + background: var(--glass-2); border: 1px solid var(--glass-brd); border-radius: var(--radius-sm); + padding: 11px 13px; font-size: 12.5px; color: var(--ink-2); line-height: 1.4; +} +.health-nudge .ic { width: 15px; height: 15px; color: var(--violet); flex-shrink: 0; margin-top: 1px; } + +/* ---------- 지출 ---------- */ +.fin-big { display: flex; align-items: baseline; gap: 8px; } +.fin-big b { font-size: 30px; font-weight: 700; font-family: var(--font-disp); letter-spacing: -0.02em; } +.fin-big .u { font-size: 13px; color: var(--muted); white-space: nowrap; } +.fin-big { white-space: nowrap; } +.fin-delta { font-size: 12px; color: var(--green); font-weight: 600; margin-top: 2px; } +.fin-bar { height: 9px; border-radius: 999px; background: var(--card-2); overflow: hidden; margin: 13px 0 6px; } +.fin-bar i { display: block; height: 100%; border-radius: 999px; background: var(--fill); } +.fin-range { display: flex; justify-content: space-between; font-size: 11px; color: var(--muted); font-family: var(--font-mono); } +.fin-subs { margin-top: 14px; padding-top: 13px; border-top: 1px solid var(--line); display: flex; flex-direction: column; gap: 10px; } +.fin-sub { display: flex; align-items: center; gap: 9px; font-size: 12.5px; } +.fin-sub .nm { font-weight: 600; } +.fin-sub .dt { color: var(--muted); } +.fin-sub .amt { margin-left: auto; font-family: var(--font-mono); font-weight: 500; color: var(--ink-2); } + +/* ---------- 마법의 인박스 (Magic Inbox) ---------- */ +.mbox-modes { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-bottom: 14px; } +.mbox-modes .mode { + display: inline-flex; align-items: center; gap: 6px; + font-size: 12.5px; font-weight: 600; color: var(--ink-2); + background: var(--glass-2); border: 1px solid var(--glass-brd); + padding: 7px 12px; border-radius: 999px; transition: background 0.14s, color 0.14s; +} +.mbox-modes .mode .ic { width: 14px; height: 14px; color: var(--muted); } +.mbox-modes .mode:hover { background: var(--card-2); color: var(--ink); } +.mbox-modes .mode-hint { font-size: 11.5px; color: var(--faint); flex: 1; min-width: 120px; text-align: right; } +.mbox-list { display: flex; flex-direction: column; } +.cap { + display: flex; gap: 12px; align-items: flex-start; + padding: 13px 0; border-top: 1px solid var(--line); +} +.cap:first-child { border-top: none; padding-top: 2px; } +.cap-k { + width: 32px; height: 32px; border-radius: 9px; flex-shrink: 0; + display: grid; place-items: center; + background: var(--glass-2); border: 1px solid var(--glass-brd); color: var(--ink-2); +} +.cap-k .ic { width: 15px; height: 15px; } +.cap-k.voice { color: var(--violet); } +.cap-k.image { color: var(--blue); } +.cap-k.text { color: var(--ink-2); } +.cap-body { flex: 1; min-width: 0; } +.cap-raw { font-size: 13.5px; font-weight: 600; line-height: 1.4; letter-spacing: -0.01em; } +.cap-route { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; } +.cap-route .r-arrow { color: var(--coral); display: inline-flex; } +.cap-route .r-arrow .ic { width: 14px; height: 14px; } +.r-chip { + display: inline-flex; align-items: center; gap: 5px; + font-size: 11px; font-weight: 600; color: var(--ink-2); + background: var(--glass-2); border: 1px solid var(--glass-brd); + padding: 3px 9px; border-radius: 999px; +} +.r-chip .ic { width: 12px; height: 12px; color: var(--muted); } +.r-chip .pdot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; } +.r-chip.proj { color: var(--ink); font-weight: 700; } +.cap-time { font-size: 11px; color: var(--faint); white-space: nowrap; flex-shrink: 0; padding-top: 2px; } + +/* ---------- 오늘의 집중 (Focus of the Day) ---------- */ +.foc-note { font-size: 13px; color: var(--ink-2); line-height: 1.5; margin: 0 0 14px; } +.foc-note b { color: var(--ink); font-weight: 700; } +.foc-track { display: flex; flex-direction: column; gap: 10px; position: relative; } +.foc-block { display: flex; gap: 12px; align-items: stretch; } +.fb-time { + font-family: var(--font-mono); font-size: 12px; font-weight: 500; + color: var(--ink-2); width: 42px; flex-shrink: 0; padding-top: 12px; +} +.fb-card { + flex: 1; min-width: 0; + border-radius: var(--radius-sm); + border: 1px solid var(--glass-brd); + background: var(--glass-2); + padding: 11px 13px; + position: relative; +} +.foc-block.deep .fb-card { + background: linear-gradient(100deg, color-mix(in oklab, var(--coral) 14%, transparent), transparent 70%), var(--glass); + border-color: color-mix(in oklab, var(--coral) 34%, var(--glass-brd)); + box-shadow: inset 3px 0 0 0 var(--coral); +} +.fb-top { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; } +.fb-tag { + font-size: 10px; font-weight: 700; letter-spacing: 0.02em; + padding: 2px 8px; border-radius: 999px; + color: var(--ink-2); background: var(--card-2); +} +.fb-tag.deep { color: #fff; background: var(--coral); } +.fb-len { font-size: 11px; color: var(--faint); font-family: var(--font-mono); margin-left: auto; } +.fb-title { font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; } +.foc-block.deep .fb-title { font-weight: 700; } +.fb-gap { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--muted); margin-top: 5px; } +.fb-gap .ic { width: 12px; height: 12px; } + +/* ---------- 미팅 액션 아이템 ---------- */ +.mtg-actions { + margin-bottom: 14px; padding: 12px 13px; + background: var(--glass-2); border: 1px solid var(--glass-brd); border-radius: var(--radius-sm); +} +.ma-head { display: flex; align-items: center; gap: 7px; font-size: 11.5px; font-weight: 700; color: var(--ink-2); margin-bottom: 10px; } +.ma-head .ic { width: 13px; height: 13px; color: var(--coral); } +.ma-row { display: flex; align-items: center; gap: 9px; padding: 6px 0; } +.ma-check { + width: 17px; height: 17px; border-radius: 6px; flex-shrink: 0; + border: 2px solid var(--line-2); display: grid; place-items: center; color: transparent; +} +.ma-check .ic { width: 11px; height: 11px; } +.ma-text { font-size: 12.5px; font-weight: 500; line-height: 1.3; flex: 1; min-width: 0; } +.ma-meta { font-size: 10.5px; color: var(--muted); white-space: nowrap; flex-shrink: 0; } + +/* ---------- 위임 현황 (Delegation) ---------- */ +.dg-suggest { + display: flex; gap: 10px; align-items: flex-start; + background: linear-gradient(100deg, color-mix(in oklab, var(--blue) 12%, transparent), transparent 70%), var(--glass-2); + border: 1px solid color-mix(in oklab, var(--blue) 26%, var(--glass-brd)); + border-radius: var(--radius-sm); padding: 12px 13px; margin-bottom: 14px; +} +.dg-s-ic { width: 26px; height: 26px; border-radius: 8px; flex-shrink: 0; display: grid; place-items: center; background: var(--blue); color: #fff; } +.dg-s-ic .ic { width: 14px; height: 14px; } +.dg-suggest p { font-size: 12.5px; color: var(--ink-2); line-height: 1.5; margin: 2px 0 0; } +.dg-suggest p b { color: var(--ink); font-weight: 700; } +.dg-list { display: flex; flex-direction: column; gap: 9px; } +.dg-row { + display: flex; align-items: center; gap: 11px; padding: 10px 12px; + border-radius: var(--radius-sm); border: 1px solid var(--glass-brd); background: var(--glass-2); +} +.dg-row.review { border-color: color-mix(in oklab, var(--green) 36%, var(--glass-brd)); background: linear-gradient(100deg, color-mix(in oklab, var(--green) 10%, transparent), transparent 70%), var(--glass-2); } +.dg-av { width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0; display: grid; place-items: center; font-size: 11px; font-weight: 700; color: #fff; } +.dg-body { flex: 1; min-width: 0; } +.dg-task { font-size: 13px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; } +.dg-sub { font-size: 11px; color: var(--muted); margin-top: 2px; } +.dg-state { + font-size: 10.5px; font-weight: 700; flex-shrink: 0; + padding: 3px 9px; border-radius: 999px; color: var(--ink-2); background: var(--card-2); +} +.dg-state.review { color: #fff; background: var(--green); } + +/* ---------- 이어서 하기 + 환경 우선순위 ---------- */ +.rs-card { + border-radius: var(--radius-sm); border: 1px solid var(--glass-brd); background: var(--glass-2); + padding: 14px; margin-bottom: 12px; +} +.rs-top { display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px; } +.rs-proj { font-size: 13px; font-weight: 700; letter-spacing: -0.01em; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.rs-ago { font-size: 10.5px; color: var(--faint); flex-shrink: 0; white-space: nowrap; } +.rs-ctx { font-size: 12.5px; color: var(--ink-2); line-height: 1.5; margin: 0 0 10px; } +.rs-next { + font-size: 12.5px; font-weight: 600; color: var(--ink); line-height: 1.35; + display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; +} +.rs-next-lb { + font-size: 9.5px; font-weight: 700; letter-spacing: 0.05em; color: var(--muted); + background: var(--card-2); padding: 2px 7px; border-radius: 999px; flex-shrink: 0; +} +.rs-cta { + display: inline-flex; align-items: center; gap: 7px; width: 100%; justify-content: center; + padding: 10px; border-radius: 11px; background: var(--fill); color: var(--on-fill); + font-size: 12.5px; font-weight: 600; transition: filter 0.14s; +} +.rs-cta .ic { width: 14px; height: 14px; } +.rs-cta:hover { filter: brightness(1.1); } +.env-nudge { + display: flex; align-items: center; gap: 11px; padding: 12px 13px; + border-radius: var(--radius-sm); + background: linear-gradient(100deg, color-mix(in oklab, var(--coral) 13%, transparent), transparent 70%), var(--glass-2); + border: 1px solid color-mix(in oklab, var(--coral) 30%, var(--glass-brd)); +} +.env-nudge .en-ic { width: 30px; height: 30px; border-radius: 9px; flex-shrink: 0; display: grid; place-items: center; background: color-mix(in oklab, var(--coral) 16%, transparent); color: var(--coral); } +.env-nudge .en-ic .ic { width: 16px; height: 16px; } +.en-body { flex: 1; min-width: 0; } +.en-task { font-size: 12.5px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; } +.en-reason { font-size: 11px; color: var(--muted); margin-top: 2px; line-height: 1.3; } +.en-flag { + display: inline-flex; align-items: center; gap: 4px; flex-shrink: 0; + font-size: 10px; font-weight: 700; color: #fff; background: var(--coral); + padding: 3px 8px; border-radius: 999px; +} +.en-flag .ic { width: 11px; height: 11px; } + +.entered .card { animation: cardin 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) both; } +@keyframes cardin { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } } + +/* ====================== 반응형 ====================== */ +@media (max-width: 1200px) { + .board { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .card.sp2 { grid-column: span 2; } + .card.r2 { grid-row: span 1; } +} +@media (max-width: 720px) { + .dash { padding: 0 14px 30px; } + .topbar { margin: 0 -14px; padding: 0 16px; gap: 14px; } + .mainnav { display: none; } + .board { grid-template-columns: 1fr; } + .card.sp2 { grid-column: span 1; } + .work { gap: 10px; } + .rail { flex-direction: row; position: static; overflow-x: auto; width: 100%; } + .ph-search { width: 150px; } +} diff --git a/design-reference/assets/data.js b/design-reference/assets/data.js new file mode 100644 index 0000000..6fbbfbb --- /dev/null +++ b/design-reference/assets/data.js @@ -0,0 +1,145 @@ +/* 아리 — 목업 데이터 (한국어) */ +window.AriData = { + user: { name: "지우", initial: "지" }, + today: "6월 7일 일요일", + + weather: { temp: 24, cond: "맑음 · 한낮 28°", icon: "cloudSun" }, + commute: "출근 23분 · 평소보다 4분 빠름", + sleep: "어젯밤 7시간 12분 · 평소만큼 푹 잤어요", + + briefingNote: + "오늘은 오후 미팅이 핵심이에요. 오전을 비워 분기 리포트에 집중하시면 좋겠어요. 14시 전엔 비가 그칠 예정이라 우산은 안 챙기셔도 돼요.", + + schedule: [ + { time: "09:30", title: "팀 데일리 스탠드업", tag: "프로덕트", dur: "15분", tone: "blue" }, + { time: "11:00", title: "디자인 리뷰 — 온보딩 플로우", tag: "디자인", dur: "45분", tone: "violet" }, + { time: "14:00", title: "분기 전략 미팅", tag: "경영진", dur: "60분", tone: "coral", soon: true }, + { time: "16:30", title: "1:1 — 민서님", tag: "팀", dur: "30분", tone: "green" }, + ], + conflict: "15:00 ‘치과 예약’이 미팅과 겹쳐요. 16:00로 옮길까요?", + + tasks: [ + { id: "t1", title: "분기 리포트 초안 마무리", project: "경영", prio: "높음", done: false }, + { id: "t2", title: "온보딩 와이어프레임 피드백", project: "디자인", prio: "높음", done: false }, + { id: "t3", title: "신규 입사자 환영 메일 회신", project: "팀", prio: "보통", done: true }, + { id: "t4", title: "구독 결제 카드 갱신", project: "개인", prio: "보통", done: false }, + { id: "t5", title: "독서모임 책 2장 읽기", project: "성장", prio: "낮음", done: false }, + ], + + goals: [ + { title: "분기 OKR — 사용자 리텐션", pct: 68, sub: "12개 중 8개 달성", c: "var(--blue)" }, + { title: "주 4회 운동", pct: 75, sub: "이번 주 3/4회", c: "var(--coral)" }, + { title: "‘딥 워크’ 책 완독", pct: 40, sub: "320쪽 중 128쪽", c: "var(--violet)" }, + ], + + emails: [ + { from: "현우 (디자인팀)", subj: "온보딩 시안 v3 공유드려요", summary: "3개 화면 수정 완료, 피드백 요청", tone: "violet", task: true, time: "8분 전" }, + { from: "Asana", subj: "‘분기 리포트’ 마감 임박", summary: "내일 18시 마감 · 담당: 나", tone: "coral", task: false, time: "1시간 전" }, + { from: "민서 (팀)", subj: "1:1 안건 미리 정리했어요", summary: "성장 로드맵, 업무 분장 2건", tone: "green", task: false, time: "3시간 전" }, + ], + emailCount: 14, + + health: { + rings: [ + { label: "걸음", val: "7.2천", pct: 72, icon: "footprints", c: "var(--coral)" }, + { label: "수면", val: "7.2h", pct: 90, icon: "moonZz", c: "var(--violet)" }, + { label: "수분", val: "5/8잔", pct: 62, icon: "droplet", c: "var(--blue)" }, + ], + nudge: "오후 3시예요 — 물 한 잔, 5분 스트레칭 어때요?", + }, + + finance: { + spentPct: 64, + spent: "1,280,000", + budget: "2,000,000", + delta: "지난달 대비 8% 적게 썼어요", + subs: [ + { name: "Spotify", date: "6월 9일", amt: "10,900" }, + { name: "ChatGPT Plus", date: "6월 12일", amt: "29,000" }, + ], + }, + + meeting: { + title: "분기 전략 미팅", + when: "오늘 14:00 · 60분", + people: ["대표님", "재무팀장", "나 외 3명"], + brief: [ + "지난 회의 액션아이템 5건 중 4건 완료", + "리텐션 KPI 전분기 대비 +6%p 상승", + "예산 재배분 안건 — 의사결정 필요", + ], + // 커뮤니케이션 기반 Task 자동 생성 — 지난 미팅 녹취에서 내 액션 아이템 추출 + recap: "지난주 분기 미팅 녹취", + actions: [ + { text: "UI 컴포넌트 리뷰 마치기", when: "이번 주 금" }, + { text: "리텐션 후속 분석 공유", when: "수" }, + ], + }, + + // 1. 마법의 인박스 — 멀티모달 캡처를 아리가 알아서 분류 + magicInbox: { + routed: 3, + captures: [ + { + kind: "text", time: "방금", + raw: "다음 주 화요일까지 인조잔디 업체 3곳 견적 받아 비교하기", + to: { proj: "집 고치기 · 마당", tone: "green", due: "다음 주 화", status: "할 일" }, + }, + { + kind: "voice", time: "2분 전", + raw: "음성 메모 0:14 — 수영장 차광막 부품 알아보기", + to: { proj: "집 관리 · 수영장", tone: "blue", due: "아이디어", status: "아이디어" }, + }, + { + kind: "image", time: "12분 전", + raw: "캡처 사진 — 차광막 클립 부품", + to: { proj: "집 관리 · 수영장", tone: "blue", due: "구매 후보", status: "아이디어" }, + }, + ], + }, + + // 6. 오늘의 집중 모드 — 캘린더 빈 블록에 맞춰 큐레이션 + focus: { + note: "오전엔 미팅이 촘촘해 가벼운 일을, 14–17시 빈 블록엔 딥 워크를 배치했어요.", + blocks: [ + { time: "09:00", len: "20분", type: "light", title: "현우님 온보딩 시안 회신", tag: "메일", gap: "스탠드업 전" }, + { time: "10:05", len: "15분", type: "light", title: "PR #482 빠른 리뷰", tag: "개발", gap: "미팅 사이" }, + { time: "14:30", len: "2시간 30분", type: "deep", title: "분기 리포트 초안 마무리", tag: "딥 워크", gap: "오후 빈 블록" }, + { time: "17:10", len: "20분", type: "light", title: "독서모임 책 2장 읽기", tag: "성장", gap: "퇴근 전" }, + ], + }, + + // 4. 매니지먼트 & 위임 — 아리는 동료의 업무량을 모른다. + // 내 일정 과부하 + 내 프로젝트 공동작업자 정보로만 제안하고, 넘긴 일을 추적한다. + delegation: { + suggest: { task: "데이터 전처리", proj: "사용자 리서치", who: "민서" }, + waiting: [ + { task: "사용자 인터뷰 녹취 정리", who: "민서", initial: "민", c: "var(--green)", note: "마감 내일", state: "진행 중", review: false }, + { task: "경쟁사 UI 벤치마크", who: "현우", initial: "현", c: "var(--violet)", note: "방금 완료", state: "리뷰 대기", review: true }, + ], + }, + + // 5. 개인 프로젝트 스마트 어시스턴트 + resume: { + project: "아리 AI 비서 개발", + ago: "3일 전 중단", + context: "Docker 컨테이너 세팅 문서를 읽다 멈추셨어요.", + next: "docker-compose 로컬 DB 연결", + }, + envNudge: { + task: "수영장 수질 체크 & 화학약품 투입", + reason: "어제 애리조나 폭우 — 오늘 최우선으로 올렸어요", + }, + + automations: [ + { when: "퇴근 위치 감지", then: "내일 일정 브리핑 발송", on: true, icon: "pin" }, + { when: "‘긴급’ 라벨 메일", then: "즉시 푸시 알림", on: true, icon: "mail" }, + { when: "밤 11시", then: "수면 모드 · 조명 끄기", on: false, icon: "moon" }, + ], + + knowledge: [ + { title: "AI 에이전트 설계 패턴", src: "웹 아티클", tag: "리서치", icon: "file" }, + { title: "분기 회고 메모", src: "내 노트", tag: "업무", icon: "pen" }, + { title: "‘몰입’ 독서 하이라이트 12개", src: "북마크", tag: "성장", icon: "bookmark" }, + ], +}; diff --git a/design-reference/assets/journey-clean-data.js b/design-reference/assets/journey-clean-data.js new file mode 100644 index 0000000..b01840b --- /dev/null +++ b/design-reference/assets/journey-clean-data.js @@ -0,0 +1,77 @@ +/* 아리 — '오늘의 여정' 데이터 (클린 화이트 테마) + 하루를 4단계 흐름으로 보고, 작업↔사람↔미팅을 의미 있는 링크로 연결한다. + 링크는 DOM 위치를 측정해 실시간 베지어 곡선으로 그린다. */ +(function () { + /* 클린 화이트 팔레트 */ + const C = { + blue: "#4f72e0", coral: "#df7256", green: "#4e9b66", + violet: "#8b6fd4", amber: "#e0a23c", ink: "#29241f", + }; + + /* ---------- 사람 ---------- */ + const people = [ + { id: "jiwoo", name: "지우", ini: "지", c: C.blue, me: true, tasks: 4, role: "나 · PM" }, + { id: "hyunwoo", name: "현우", ini: "현", c: C.violet, tasks: 2, role: "디자인" }, + { id: "minseo", name: "민서", ini: "민", c: C.green, tasks: 2, role: "팀 운영" }, + { id: "jaeho", name: "재호", ini: "재", c: C.coral, tasks: 2, role: "데이터" }, + { id: "sua", name: "수아", ini: "수", c: C.ink, tasks: 3, role: "리서치" }, + ]; + const P = Object.fromEntries(people.map((p) => [p.id, p])); + + /* ---------- 4단계 ---------- */ + const stages = [ + { id: "s1", title: "아침 준비", time: "06:40 – 09:00", c: C.blue, n: "2" }, + { id: "s2", title: "오전 집중", time: "09:00 – 13:00", c: C.coral, n: "4" }, + { id: "s3", title: "오후 협업", time: "13:00 – 17:00", c: C.violet, n: "4" }, + { id: "s4", title: "마무리 & 내일", time: "17:00 – 23:00", c: C.ink, n: "4" }, + ]; + + /* ---------- 단계별 카드 ---------- */ + const cards = { + s1: [ + { node: "m-sleep", kind: "routine", ic: "moon", title: "수면 7시간 12분", meta: "평소만큼 푹 잔 밤", tag: "컨디션 좋음", tool: "tick", toolC: "ok" }, + { node: "m-brief", kind: "routine", ic: "sun", title: "아침 브리핑 확인", meta: "맑음 24° · 출근 23분", tag: "4분 빠름", tool: "cal" }, + ], + s2: [ + { node: "t-report", kind: "task", title: "분기 리포트 초안 마무리", meta: "경영 전략 · 우선", who: "jiwoo", done: false }, + { node: "t-wire", kind: "task", title: "온보딩 와이어프레임 피드백", meta: "온보딩 · 우선", who: "hyunwoo", done: false }, + { node: "t-interview", kind: "task", title: "사용자 인터뷰 5건 정리", meta: "온보딩 · 리서치", who: "sua", done: false }, + { node: "t-welcome", kind: "task", title: "신규 입사자 환영 메일", meta: "팀 운영", who: "minseo", done: true }, + ], + s3: [ + { node: "k-stand", kind: "meet", time: "13:00", title: "팀 데일리 스탠드업", meta: "프로덕트 · 15분", who: "minseo" }, + { node: "k-design", kind: "meet", time: "14:00", title: "디자인 리뷰", meta: "온보딩 · 45분", who: "hyunwoo" }, + { node: "k-strat", kind: "meet", time: "15:00", title: "분기 전략 미팅", meta: "경영진 · 60분", who: "jaeho", soon: true }, + { node: "k-11", kind: "meet", time: "16:30", title: "1:1 — 민서님", meta: "팀 · 30분", who: "minseo" }, + ], + s4: [ + { node: "w-focus", kind: "tile", hi: true, ic: "target", tt: "지금 집중", ts: "분기 리포트 마무리" }, + { node: "w-retro", kind: "tile", ic: "send", tt: "회고 배포", ts: "팀 채널 공유" }, + { node: "w-health", kind: "tile", ic: "heart", tt: "운동 30분", ts: "주 3/4회" }, + { node: "w-next", kind: "tile", ic: "cal", tt: "내일 준비", ts: "일정 4건" }, + ], + }; + + /* ---------- 의미 있는 흐름 링크 (from → to, 둘 다 node) ---------- */ + const links = [ + { from: "m-brief", to: "t-report", c: C.blue }, + { from: "t-report", to: "k-strat", c: C.coral }, + { from: "t-wire", to: "k-design", c: C.violet }, + { from: "t-interview", to: "k-11", c: C.green, dash: true }, + { from: "t-welcome", to: "k-stand", c: C.green, dash: true }, + { from: "k-strat", to: "w-focus", c: C.ink }, + { from: "k-design", to: "w-retro", c: C.violet, dash: true }, + { from: "k-11", to: "w-next", c: C.green, dash: true }, + ]; + + /* ---------- 하단 작업 테이블 ---------- */ + const rows = [ + { star: true, node: "t-report", nm: "분기 리포트 초안", st: "prog", stt: "진행중", date: "09:00", who: "jiwoo" }, + { node: "t-wire", nm: "온보딩 와이어프레임 피드백", st: "review", stt: "검토", date: "11:00", who: "hyunwoo" }, + { node: "t-interview", nm: "사용자 인터뷰 5건 정리", st: "prog", stt: "진행중", date: "13:30", who: "sua" }, + { star: true, node: "t-welcome", nm: "신규 입사자 환영 메일", st: "done", stt: "완료", date: "08:30", who: "minseo" }, + { node: "t-card", nm: "구독 결제 카드 갱신", st: "sched", stt: "예정", date: "18:00", who: "jiwoo" }, + ]; + + window.AriJourney = { C, people, P, stages, cards, links, rows }; +})(); diff --git a/design-reference/assets/journey-clean.css b/design-reference/assets/journey-clean.css new file mode 100644 index 0000000..744c91d --- /dev/null +++ b/design-reference/assets/journey-clean.css @@ -0,0 +1,236 @@ +/* =========================================================== + 아리 — '오늘의 여정' (클린 화이트) · dash.css 위에 로드 + 하루를 4단계 흐름으로 보여주고 작업·사람·미팅을 베지어로 연결. + 토큰은 모두 dash.css(:root)에서 상속 — 같은 글래스/잉크/라운드 언어. + =========================================================== */ + +/* ===== 여정 페이지 컨테이너 ===== */ +.jx { max-width: 1620px; margin: 0 auto; padding: 0 26px 40px; } + +/* ===== 큰 글래스 패널 ===== */ +.jx-panel { + background: var(--glass); + -webkit-backdrop-filter: var(--blur); + backdrop-filter: var(--blur); + border: 1px solid var(--glass-brd); + border-radius: var(--radius); + box-shadow: var(--shadow), var(--glass-hi); + padding: 22px; + position: relative; + overflow: hidden; +} + +/* ----- 패널 헤더 (라벨 + 사람 + 액션) ----- */ +.jx-top { display: flex; align-items: center; gap: 16px; margin-bottom: 20px; flex-wrap: wrap; } +.jx-label { font-size: 16px; font-weight: 700; letter-spacing: -0.02em; } +.jx-label span { color: var(--muted); font-weight: 500; } + +.jx-stack { display: flex; align-items: flex-end; margin-left: 6px; } +.jx-stack .av { + width: 42px; height: 42px; border-radius: 50%; margin-left: -10px; + border: 2.5px solid var(--card); position: relative; padding: 0; + display: grid; place-items: center; color: #fff; font-weight: 700; font-size: 13px; + box-shadow: var(--shadow-sm); cursor: pointer; transition: transform 0.14s; +} +.jx-stack .av:first-child { margin-left: 0; } +.jx-stack .av:hover, .jx-stack .av.on { transform: translateY(-3px); z-index: 6; } +.jx-stack .av .cnt { + position: absolute; bottom: -4px; left: 50%; transform: translateX(-50%); + font-size: 10px; font-weight: 700; color: #fff; + border-radius: 999px; padding: 0 5px; min-width: 17px; text-align: center; + border: 2px solid var(--card); +} +.jx-stack .av .cnt.me { background: var(--blue); } +.jx-stack .av .cnt.tm { background: var(--coral); } +.jx-stack .more { + width: 42px; height: 42px; border-radius: 50%; margin-left: -10px; + border: 2.5px solid var(--card); background: var(--card-2); color: var(--ink-2); + display: grid; place-items: center; font-size: 13px; font-weight: 600; box-shadow: var(--shadow-sm); +} + +.jx-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; } +.jx-act { + width: 42px; height: 42px; border-radius: 50%; + display: grid; place-items: center; color: var(--ink-2); + background: var(--glass-2); border: 1px solid var(--glass-brd); + box-shadow: var(--shadow-sm); transition: transform 0.12s, color 0.16s; +} +.jx-act .ic { width: 18px; height: 18px; } +.jx-act:hover { transform: translateY(-1px); color: var(--ink); } +.jx-act.fill { background: var(--fill); color: var(--on-fill); border-color: transparent; } + +/* ===== 보드 그리드 + 커넥터 ===== */ +.jx-board { position: relative; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px; } +.jx-svg { position: absolute; inset: 0; z-index: 1; pointer-events: none; overflow: visible; } +.jx-line { stroke-width: 2; opacity: 0.5; transition: opacity 0.22s, stroke-width 0.22s; } +.jx-dot { opacity: 0.65; transition: opacity 0.22s, r 0.2s; } +.jx-link path[stroke-dasharray]:not([stroke-dasharray="none"]) { animation: jxdash 1.1s linear infinite; } +@keyframes jxdash { to { stroke-dashoffset: -16; } } +.jx-link.on .jx-line { opacity: 1; stroke-width: 2.8; } +.jx-link.on .jx-dot { opacity: 1; r: 4.4; } +.jx-link.off .jx-line { opacity: 0.1; } +.jx-link.off .jx-dot { opacity: 0.1; } +@media (prefers-reduced-motion: reduce) { .jx-link path { animation: none !important; } } + +/* ----- 컬럼 ----- */ +.jx-col { position: relative; z-index: 2; display: flex; flex-direction: column; gap: 12px; } +.jx-chead { display: flex; align-items: center; gap: 9px; padding: 0 4px 2px; } +.jx-chead .dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; } +.jx-chead .t { font-size: 13px; font-weight: 700; letter-spacing: -0.01em; } +.jx-chead .n { margin-left: auto; font-size: 11.5px; color: var(--muted); font-family: var(--font-mono); } +.jx-cbody { + flex: 1; display: flex; flex-direction: column; gap: 11px; + background: var(--glass-2); border: 1px solid var(--glass-brd); + border-radius: var(--radius); padding: 13px; +} + +/* ----- 흐름 카드 ----- */ +.fcard { + background: var(--card); border: 1px solid var(--line); + border-radius: var(--radius-sm); padding: 13px; box-shadow: var(--shadow-sm); + display: flex; flex-direction: column; gap: 10px; + transition: transform 0.16s, box-shadow 0.16s, opacity 0.2s, outline-color 0.16s; +} +.fcard:hover { transform: translateY(-2px); box-shadow: var(--shadow); } +.fcard-row { display: flex; align-items: center; gap: 11px; } +.fc-ic { + width: 34px; height: 34px; border-radius: 10px; flex-shrink: 0; + display: grid; place-items: center; background: var(--card-2); color: var(--ink); + border: 1px solid var(--line); +} +.fc-ic .ic { width: 17px; height: 17px; } +.fc-av { + width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0; + display: grid; place-items: center; color: #fff; font-weight: 700; font-size: 13px; +} +.fc-who { + width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0; + display: grid; place-items: center; color: #fff; font-weight: 700; font-size: 12px; + border: 2px solid var(--card); box-shadow: var(--shadow-sm); +} +.fc-main { flex: 1; min-width: 0; } +.fc-title { font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.3; } +.fc-meta { font-size: 11.5px; color: var(--muted); margin-top: 2px; } +.fc-time { font-family: var(--font-mono); font-size: 12px; font-weight: 500; color: var(--ink-2); flex-shrink: 0; } +.mini { + width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; + color: var(--muted); background: var(--card-2); flex-shrink: 0; transition: color 0.15s; +} +.mini .ic { width: 15px; height: 15px; } +.mini.ok { color: var(--green); background: color-mix(in oklab, var(--green) 14%, transparent); } +.mini.soon { + width: auto; padding: 0 9px; font-size: 11px; font-weight: 700; + color: #fff; background: var(--coral); +} +.fcard-foot { display: flex; align-items: center; gap: 8px; border-top: 1px solid var(--line); padding-top: 10px; } +.fcard-foot .tag { + font-size: 11px; font-weight: 600; color: var(--ink-2); + background: var(--card-2); border-radius: 999px; padding: 3px 9px; +} + +/* 체크 카드(할 일) */ +.fcard.task { cursor: default; } +.fcheck { + width: 22px; height: 22px; border-radius: 7px; flex-shrink: 0; cursor: pointer; + border: 2px solid var(--line-2); display: grid; place-items: center; color: transparent; + transition: all 0.14s; +} +.fcheck .ic { width: 13px; height: 13px; } +.fcard.task.done .fcheck { background: var(--green); border-color: var(--green); color: #fff; } +.fcard.task.done .fc-title { color: var(--faint); text-decoration: line-through; } + +/* 강조 / 흐림 */ +.fcard.lit, .tile.lit { transform: translateY(-2px); box-shadow: var(--shadow); outline: 1.5px solid var(--blue); outline-offset: 1px; } +.tile.hi.lit { outline-color: rgba(255,255,255,0.5); } +.fcard.mute, .tile.mute { opacity: 0.4; } + +/* ----- 마무리 타일 ----- */ +.tiles { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; } +.tile { + background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-sm); + box-shadow: var(--shadow-sm); padding: 13px; min-height: 92px; + display: flex; flex-direction: column; gap: 8px; + transition: transform 0.16s, box-shadow 0.16s, opacity 0.2s, outline-color 0.16s; +} +.tile:hover { transform: translateY(-2px); } +.tile .tic { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; background: var(--card-2); color: var(--ink-2); border: 1px solid var(--line); } +.tile .tic .ic { width: 16px; height: 16px; } +.tile .tt { font-size: 12.5px; font-weight: 600; line-height: 1.3; margin-top: auto; } +.tile .ts { font-size: 11px; color: var(--muted); } +.tile.hi { background: var(--fill); border-color: transparent; color: var(--on-fill); box-shadow: var(--shadow); } +.tile.hi .tic { background: rgba(255,255,255,0.12); color: var(--lime); border-color: transparent; } +.tile.hi .ts { color: color-mix(in oklab, var(--on-fill) 60%, transparent); } + +/* ----- 단계 라벨 ----- */ +.jx-labels { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 18px; margin-top: 18px; } +.jx-labels .cl { text-align: center; font-size: 13px; font-weight: 600; color: var(--ink-2); } +.jx-labels .cl span { display: block; font-size: 11px; color: var(--muted); font-weight: 500; margin-top: 2px; } + +/* ----- 흐름 힌트 ----- */ +.jx-hint { margin-top: 16px; display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 12.5px; color: var(--muted); font-weight: 500; } +.jx-hint .ic { width: 15px; height: 15px; color: var(--blue); } + +/* ===== 하단 2열 ===== */ +.jx-bottom { display: grid; grid-template-columns: 1.35fr 1fr; gap: 18px; margin-top: 18px; } +.jx-phead { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; } +.jx-phead .pt { font-size: 16px; font-weight: 700; letter-spacing: -0.02em; } +.jx-phead .ps { font-size: 12px; color: var(--muted); margin-top: 1px; } +.jx-phead .pa { margin-left: auto; display: flex; gap: 8px; } + +/* 작업 테이블 */ +.jtable { width: 100%; border-collapse: collapse; } +.jtable th { text-align: left; font-size: 11.5px; font-weight: 600; color: var(--muted); padding: 0 12px 12px; border-bottom: 1px solid var(--line); white-space: nowrap; } +.jtable th:last-child, .jtable td:last-child { text-align: right; } +.jtable td { padding: 13px 12px; border-bottom: 1px solid var(--line); font-size: 13px; vertical-align: middle; } +.jtable tr:last-child td { border-bottom: none; } +.jtable tbody tr { transition: background 0.14s; } +.jtable tbody tr:hover, .jtable tbody tr.lit { background: var(--card-2); } +.t-subj { display: flex; align-items: center; gap: 11px; } +.t-subj .star { color: var(--faint); width: 16px; height: 16px; flex-shrink: 0; } +.t-subj .star.on { color: var(--amber); } +.t-subj .nm { font-weight: 600; letter-spacing: -0.01em; } +.chip { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 600; padding: 4px 11px; border-radius: 999px; white-space: nowrap; } +.chip::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; } +.chip.done { color: var(--green); background: color-mix(in oklab, var(--green) 13%, transparent); } +.chip.sched { color: var(--blue); background: color-mix(in oklab, var(--blue) 12%, transparent); } +.chip.review { color: var(--violet); background: color-mix(in oklab, var(--violet) 14%, transparent); } +.chip.prog { color: var(--ink-2); background: var(--card-2); } +.t-date { color: var(--ink-2); font-family: var(--font-mono); font-size: 12px; white-space: nowrap; } +.t-user { display: flex; align-items: center; gap: 8px; justify-content: flex-end; } +.t-user .ua { width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-size: 11px; font-weight: 700; } +.t-user .un { font-size: 12.5px; color: var(--ink-2); } + +/* 반원 게이지 */ +.gauges { display: flex; gap: 14px; } +.gauge { + flex: 1; background: var(--card); border: 1px solid var(--line); + border-radius: var(--radius); box-shadow: var(--shadow-sm); padding: 18px 14px 16px; + display: flex; flex-direction: column; align-items: center; +} +.gauge svg { width: 100%; max-width: 160px; display: block; overflow: visible; } +.gauge .gval { font-family: var(--font-disp); font-size: 28px; font-weight: 700; letter-spacing: -0.03em; line-height: 1; } +.gauge .gunit { font-size: 12px; color: var(--muted); } +.gauge .glabel { font-size: 13px; font-weight: 600; color: var(--ink-2); margin-top: 12px; } +.gauge .gsub { font-size: 11.5px; color: var(--muted); margin-top: 2px; } +.gauge-foot { margin-top: 14px; display: flex; align-items: center; gap: 9px; background: var(--glass-2); border: 1px solid var(--glass-brd); border-radius: var(--radius-sm); padding: 11px 13px; font-size: 12.5px; color: var(--ink-2); line-height: 1.4; } +.gauge-foot .ic { width: 16px; height: 16px; color: var(--blue); flex-shrink: 0; } + +/* 진입 애니메이션 */ +.entered .jx-panel, .entered .jx-bottom .card { animation: jxin 0.5s cubic-bezier(0.2,0.7,0.2,1) both; } +.entered .jx-bottom .card:nth-child(2) { animation-delay: 0.06s; } +@keyframes jxin { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } } +@media (prefers-reduced-motion: reduce) { .entered .jx-panel, .entered .jx-bottom .card { animation: none; } } + +/* ===== 반응형 ===== */ +@media (max-width: 1200px) { + .jx-bottom { grid-template-columns: 1fr; } + .jx-board { grid-template-columns: repeat(2, minmax(0,1fr)); } + .jx-labels { grid-template-columns: repeat(2, minmax(0,1fr)); } +} +@media (max-width: 720px) { + .jx { padding: 0 14px 30px; } + .jx-board { grid-template-columns: 1fr; } + .jx-svg { display: none; } + .jx-labels { display: none; } + .gauges { flex-direction: column; } +} diff --git a/design-reference/assets/journey-clean.jsx b/design-reference/assets/journey-clean.jsx new file mode 100644 index 0000000..96ba20e --- /dev/null +++ b/design-reference/assets/journey-clean.jsx @@ -0,0 +1,369 @@ +/* 아리 — '오늘의 여정' (클린 화이트 테마) + 하루를 4단계 흐름으로 보여주고, 작업↔사람↔미팅을 실시간 베지어 커넥터로 + 연결한다. 카드에 마우스를 올리면 연결된 흐름이 강조된다. + 상단바·페이지헤더는 dash.css의 클린 화이트 언어를 그대로 사용. */ +const { useState, useEffect, useRef, useLayoutEffect, useCallback, useMemo } = React; +const J = window.AriJourney; + +/* ---------- 아이콘 ---------- */ +const PATHS = { + spark: "M12 3l1.9 5.2L19 10l-5.1 1.8L12 17l-1.9-5.2L5 10l5.1-1.8z|M19 4v3|M5 17v3", + grid: "M3 3h7v7H3z|M14 3h7v7h-7z|M14 14h7v7h-7z|M3 14h7v7H3z", + inbox: "M22 12h-6l-2 3h-4l-2-3H2|M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z", + route: "M6 19a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M18 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M9 16h6a3 3 0 0 0 3-3V11", + cal: "M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", + check: "M9 11l3 3L22 4|M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11", + tick: "M20 6 9 17l-5-5", + mail: "M22 7 13.03 12.7a1.94 1.94 0 0 1-2.06 0L2 7|M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", + target: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z|M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8z|M12 12h.01", + heart: "M19 14c1.5-1.5 3-3.4 3-5.5A4.5 4.5 0 0 0 12 5.5 4.5 4.5 0 0 0 2 8.5c0 2.1 1.5 4 3 5.5l7 7z", + brain: "M12 5a3 3 0 1 0-5.9.8A3 3 0 0 0 4 9.5 3 3 0 0 0 6 14a3 3 0 0 0 6 1zM12 5a3 3 0 1 1 5.9.8A3 3 0 0 1 20 9.5 3 3 0 0 1 18 14a3 3 0 0 1-6 1z|M12 5v14", + bell: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9|M10.3 21a1.94 1.94 0 0 0 3.4 0", + search: "M21 21l-4.34-4.34|M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z", + send: "M22 2 11 13|M22 2 15 22l-4-9-9-4z", + zap: "M13 2 3 14h9l-1 8 10-12h-9z", + sun: "M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10z|M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4", + moon: "M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z", + more: "M5 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0zM11 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0zM17 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0z", + plus: "M12 5v14|M5 12h14", + share: "M4 12v7a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7|M16 6l-4-4-4 4|M12 2v13", + star: "M12 2l2.9 6.3 6.9.8-5.1 4.7 1.4 6.8L12 18l-6 3.4 1.4-6.8L2.3 9.9l6.9-.8z", + drop: "M12 2.7S5 10 5 14a7 7 0 0 0 14 0c0-4-7-11.3-7-11.3z", + link: "M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1|M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1", +}; +function I({ d, w }) { + return ( + + {PATHS[d].split("|").map((s, i) => )} + + ); +} + +/* ---------- 상단 메뉴 (여정 추가) ---------- */ +const MAIN = [ + { id: "dash", label: "대시보드", icon: "grid", href: "대시보드.html" }, + { id: "sbox", label: "인박스", icon: "inbox", href: "인박스.html" }, + { id: "appr", label: "결재함", icon: "spark", badge: "3", href: "결재함.html" }, + { id: "auto", label: "자동화", icon: "zap", href: "자동화.html" }, + { id: "journey", label: "여정", icon: "route", href: "여정.html" }, + { id: "cal", label: "일정", icon: "cal", href: "일정.html" }, + { id: "task", label: "작업", icon: "check", badge: "4", href: "작업.html" }, + { id: "mail", label: "메일", icon: "mail", href: "메일.html" }, + { id: "noti", label: "알림", icon: "bell", badge: "6", href: "알림.html" }, + { id: "research", label: "리서치", icon: "brain", href: "리서치.html" }, + { id: "trip", label: "여행", icon: "send", href: "여행.html" }, + { id: "life", label: "라이프", icon: "heart", href: "라이프.html" }, + { id: "wrap", label: "하루 마감", icon: "moon", href: "하루 마감.html" }, +]; + +/* ---------- 반원 게이지 ---------- */ +const LEN = Math.PI * 64; +function Gauge({ pct, color, val, unit, label, sub }) { + return ( +
+ + + + +
+
{val}
+
{unit}
+
+
{label}
+
{sub}
+
+ ); +} + +/* ---------- 컬럼 ---------- */ +function Col({ stage, count, children }) { + return ( +
+
+ + {stage.title} + {count || stage.n} +
+
{children}
+
+ ); +} + +/* ==================================================================== + 흐름 보드 — 동적 커넥터 + ==================================================================== */ +function Board({ tasks, toggle, hover, setHover, neighbors }) { + const boardRef = useRef(null); + const nodes = useRef({}); + const [paths, setPaths] = useState([]); + const [size, setSize] = useState({ w: 0, h: 0 }); + const setNode = (id) => (el) => { if (el) nodes.current[id] = el; }; + + const compute = useCallback(() => { + const board = boardRef.current; + if (!board) return; + const br = board.getBoundingClientRect(); + const out = []; + for (const lk of J.links) { + const a = nodes.current[lk.from], b = nodes.current[lk.to]; + if (!a || !b) continue; + const ar = a.getBoundingClientRect(), bb = b.getBoundingClientRect(); + const x1 = ar.right - br.left, y1 = ar.top - br.top + ar.height / 2; + const x2 = bb.left - br.left, y2 = bb.top - br.top + bb.height / 2; + const dx = Math.max(36, (x2 - x1) * 0.5); + out.push({ + key: lk.from + ">" + lk.to, from: lk.from, to: lk.to, c: lk.c, dash: lk.dash, + d: `M${x1},${y1} C${x1 + dx},${y1} ${x2 - dx},${y2} ${x2},${y2}`, + x1, y1, x2, y2, + }); + } + setPaths(out); + setSize({ w: br.width, h: br.height }); + }, []); + + useLayoutEffect(() => { + compute(); + const raf = requestAnimationFrame(compute); + const onR = () => compute(); + window.addEventListener("resize", onR); + const ro = new ResizeObserver(compute); + if (boardRef.current) ro.observe(boardRef.current); + if (document.fonts && document.fonts.ready) document.fonts.ready.then(compute); + return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", onR); ro.disconnect(); }; + }, [compute, tasks]); + + const isLit = (node) => hover && (node === hover || (neighbors[hover] && neighbors[hover].has(node))); + const cls = (node, base) => base + (isLit(node) ? " lit" : "") + (hover && !isLit(node) ? " mute" : ""); + const pathState = (p) => (!hover ? "" : (p.from === hover || p.to === hover) ? " on" : " off"); + const on = (node) => ({ onMouseEnter: () => setHover(node), onMouseLeave: () => setHover(null) }); + + return ( +
+ + {paths.map((p) => ( + + + + + + ))} + + + {/* 1 — 아침 준비 */} + + {J.cards.s1.map((c) => ( +
+
+
+
{c.title}
{c.meta}
+
+
+
{c.tag}
+
+ ))} + + + {/* 2 — 오전 집중 (할 일) */} + !t.done).length}/4`}> + {tasks.map((t) => { + const pe = J.P[t.who]; + return ( +
+
+
{ e.stopPropagation(); toggle(t.node); }}>
+
{t.title}
{t.meta}
+
{pe.ini}
+
+
+ ); + })} + + + {/* 3 — 오후 협업 (미팅) */} + + {J.cards.s3.map((e) => { + const pe = J.P[e.who]; + return ( +
+
+
{pe.ini}
+
{e.title}
{e.meta}
+ {e.soon ? 곧 :
{e.time}
} +
+
+ ); + })} + + + {/* 4 — 마무리 & 내일 */} + +
+ {J.cards.s4.map((t) => ( +
+
+
{t.tt}
+
{t.ts}
+
+ ))} +
+ +
+ ); +} + +/* ==================================================================== + 앱 + ==================================================================== */ +function App() { + const [theme, setTheme] = useState("light"); + const [tasks, setTasks] = useState(J.cards.s2.map((c) => ({ ...c }))); + const [hover, setHover] = useState(null); + const ref = useRef(null); + + useEffect(() => { document.documentElement.setAttribute("data-theme", theme); }, [theme]); + useEffect(() => { + const el = ref.current; + const id = requestAnimationFrame(() => el && el.classList.add("entered")); + return () => cancelAnimationFrame(id); + }, []); + + const toggle = (node) => setTasks((t) => t.map((x) => (x.node === node ? { ...x, done: !x.done } : x))); + const openCount = tasks.filter((t) => !t.done).length; + + const neighbors = useMemo(() => { + const m = {}; + for (const l of J.links) { + (m[l.from] = m[l.from] || new Set()).add(l.to); + (m[l.to] = m[l.to] || new Set()).add(l.from); + } + return m; + }, []); + + return ( +
+ {/* ===== 상단바 ===== */} +
+
+
+
아리AI LIFE OS
+
+ +
+ + + +
지
+
+
+ + {/* ===== 페이지 헤더 ===== */} +
+
+
+ 6월 10일 화요일 + + 맑음 · 한낮 28° +
+

오늘의 여정

+
+
+ + +
+
+ + {/* ===== 흐름 패널 ===== */} +
+
+
오늘의 흐름 · 4단계 · 12개 항목 · 5명 협업
+
+ {J.people.map((p) => ( + + ))} +
+3
+
+
+ + + +
+
+ + + +
+ {J.stages.map((s) => ( +
+ {s.title}{s.time} +
+ ))} +
+ +
카드에 마우스를 올리면 연결된 작업 · 사람 · 미팅 흐름이 강조돼요
+
+ + {/* ===== 하단 2열 ===== */} +
+
+
+
오늘의 작업
5건 · 진행중 2 · 검토 1 · 완료 1 · 예정 1
+
+ + +
+
+ + + + {J.rows.map((r, i) => { + const pe = J.P[r.who]; + return ( + setHover(r.node)} onMouseLeave={() => setHover(null)}> + + + + + + ); + })} + +
작업상태시작담당
+
+ + {r.nm} +
+
{r.stt}{r.date}
{pe.ini}
{pe.name}
+
+ +
+
+
오늘의 지표
집중 · 활동
+
+
+
+ + +
+
오후 3시예요 — 물 한 잔, 5분 스트레칭 어때요?
+
+
+
+ ); +} + +ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/design-reference/assets/life-data.js b/design-reference/assets/life-data.js new file mode 100644 index 0000000..046297f --- /dev/null +++ b/design-reference/assets/life-data.js @@ -0,0 +1,132 @@ +/* 아리 — 라이프 케어 데이터 + 건강·웰니스 / 금융·소비 / 지식·학습(제2의 뇌) + 아리가 데이터를 읽고 분석·코칭·리마인더를 미리 준비해 둔다. */ +(function () { + /* ===================== 연결된 데이터 소스 ===================== */ + const sources = { + health: [ + { id: "apple", name: "Apple Health", kind: "건강 앱", tone: "coral", on: true, last: "방금 동기화" }, + { id: "watch", name: "Apple Watch", kind: "웨어러블", tone: "violet", on: true, last: "1분 전" }, + { id: "fit", name: "Google Fit", kind: "활동 추적", tone: "green", on: false, last: "연결 안 됨" }, + ], + finance: [ + { id: "woori", name: "우리카드", kind: "신용/체크", tone: "blue", on: true, last: "오늘 09:12" }, + { id: "toss", name: "토스", kind: "계좌 통합", tone: "coral", on: true, last: "오늘 08:40" }, + { id: "kakao", name: "카카오뱅크", kind: "입출금", tone: "amber", on: true, last: "어제" }, + { id: "kb", name: "KB증권", kind: "투자", tone: "green", on: false, last: "연결 안 됨" }, + ], + knowledge: [ + { id: "notion", name: "Notion", kind: "메모·문서", tone: "violet", on: true, last: "오늘" }, + { id: "web", name: "웹 클리퍼", kind: "아티클 스크랩", tone: "blue", on: true, last: "2시간 전" }, + { id: "readwise", name: "Readwise", kind: "하이라이트", tone: "coral", on: true, last: "어제" }, + ], + }; + + /* ===================== 건강 & 웰니스 ===================== */ + const health = { + rings: [ + { label: "걸음", val: "7,240", unit: "보", goal: "10,000보", pct: 72, icon: "steps", tone: "coral" }, + { label: "운동", val: "38", unit: "분", goal: "45분", pct: 84, icon: "flame", tone: "blue" }, + { label: "수면", val: "7h 12m", unit: "", goal: "7h 30m", pct: 90, icon: "moon", tone: "violet" }, + ], + vitals: [ + { label: "안정 심박", val: "62", unit: "bpm", trend: "down", note: "지난주 –3", tone: "coral" }, + { label: "평균 스트레스", val: "보통", unit: "", trend: "flat", note: "오후에 상승", tone: "amber" }, + { label: "주간 운동", val: "3 / 4", unit: "회", trend: "up", note: "목표 근접", tone: "blue" }, + ], + sleep: { + total: "7시간 12분", + score: 86, + stages: [ + { label: "깊은 수면", pct: 22, tone: "violet" }, + { label: "렘 수면", pct: 24, tone: "blue" }, + { label: "얕은 수면", pct: 48, tone: "coral" }, + { label: "깸", pct: 6, tone: "muted" }, + ], + week: [72, 65, 88, 60, 90, 78, 86], + note: "수요일과 어젯밤은 잘 주무셨어요. 하지만 평일 취침 시각이 들쭉날쭉해요 — 자정 전 취침을 목표로 잡아볼까요?", + }, + coach: + "이번 주 활동량은 지난주보다 12% 늘었어요. 다만 어제 물 섭취가 권장량의 절반에 그쳤고, 오후 3시 이후 앉아 있는 시간이 길었어요. 가벼운 산책과 수분 보충 리마인더를 오후에 넣어둘게요.", + habits: [ + { id: "water", title: "물 마시기", goal: "하루 8잔", done: 5, total: 8, streak: 4, icon: "water", tone: "blue", at: "오후 3:00 리마인더" }, + { id: "stretch", title: "스트레칭", goal: "오후 휴식 5분", done: 1, total: 2, streak: 9, icon: "stretch", tone: "coral", at: "오후 3:30 리마인더" }, + { id: "vitamin", title: "비타민 D", goal: "아침 1정", done: 1, total: 1, streak: 21, icon: "pill", tone: "amber", at: "오전 8:00 완료" }, + { id: "meditate", title: "명상", goal: "취침 전 10분", done: 0, total: 1, streak: 2, icon: "feather", tone: "violet", at: "오후 10:30 예정" }, + ], + }; + + /* ===================== 금융 & 소비 ===================== */ + const finance = { + budget: { spent: 1284000, limit: 2000000, pct: 64, deltaPct: -8, daysLeft: 23 }, + cats: [ + { name: "식비", amt: 412000, pct: 32, icon: "food", tone: "coral", over: false }, + { name: "쇼핑", amt: 298000, pct: 23, icon: "bag", tone: "violet", over: true }, + { name: "교통", amt: 164000, pct: 13, icon: "car", tone: "blue", over: false }, + { name: "카페·간식", amt: 138000, pct: 11, icon: "coffee", tone: "amber", over: false }, + { name: "구독", amt: 92000, pct: 7, icon: "repeat", tone: "green", over: false }, + { name: "문화·여가", amt: 180000, pct: 14, icon: "ticket", tone: "blue", over: false }, + ], + subs: [ + { name: "Netflix", date: "6월 9일", inDays: 2, amt: 17000, icon: "play", tone: "coral", note: "프리미엄" }, + { name: "Spotify", date: "6월 9일", inDays: 2, amt: 10900, icon: "music", tone: "green", note: "개인" }, + { name: "ChatGPT Plus", date: "6월 12일", inDays: 5, amt: 29000, icon: "spark", tone: "violet", note: "월 구독" }, + { name: "쿠팡 와우", date: "6월 15일", inDays: 8, amt: 7890, icon: "bag", tone: "blue", note: "멤버십" }, + { name: "유튜브 프리미엄", date: "6월 22일", inDays: 15, amt: 14900, icon: "play", tone: "coral", note: "가족 요금제" }, + ], + coach: + "이번 달은 지난달보다 8% 적게 쓰고 계세요. 다만 쇼핑이 예산의 23%로 평소보다 빠르게 늘고 있어요. 또 이틀 뒤 Netflix·Spotify 결제가 같은 날 빠져나가요 — 최근 두 달간 안 본 Netflix는 일시정지를 추천드려요.", + insights: [ + { kind: "alert", icon: "alert", title: "쇼핑 예산 초과 임박", body: "이번 달 쇼핑 29.8만 원, 한도의 99%예요. 남은 23일을 위해 한도를 올리거나 알림을 받을까요?" }, + { kind: "save", icon: "repeat", title: "잠자는 구독 발견", body: "Netflix를 최근 58일간 시청하지 않았어요. 일시정지 시 월 1.7만 원을 아껴요." }, + { kind: "info", icon: "trending", title: "이달 저축 페이스 양호", body: "현재 추세라면 이번 달 약 26만 원을 남길 수 있어요. 적금에 자동 이체할까요?" }, + ], + }; + + /* ===================== 지식 & 학습 (제2의 뇌) ===================== */ + const knowledge = { + stats: { items: 1248, thisWeek: 23, collections: 9 }, + suggested: ["AI 에이전트 설계에서 배운 것 요약해줘", "이번 주 저장한 글 핵심만", "리텐션 관련 메모 모아줘"], + collections: [ + { name: "프로덕트 전략", count: 84, tone: "blue" }, + { name: "AI · 에이전트", count: 132, tone: "violet" }, + { name: "디자인 시스템", count: 57, tone: "coral" }, + { name: "개인 성장", count: 41, tone: "green" }, + ], + items: [ + { + id: "k1", type: "article", title: "효과적인 AI 에이전트를 설계하는 6가지 패턴", + src: "anthropic.com", time: "2시간 전", tone: "blue", icon: "article", + tags: ["AI·에이전트", "리서치", "설계"], + excerpt: "도구 사용, 계획 수립, 반성(reflection)을 분리하면 에이전트의 신뢰성이 크게 올라간다. 핵심은 작은 단위로 검증 가능한 단계로 쪼개는 것.", + ai: "오케스트레이터-워커 패턴과 평가-최적화 루프가 우리 온보딩 자동화에 바로 적용 가능해 보여요.", + }, + { + id: "k2", type: "note", title: "분기 회고 — 리텐션 실험 메모", src: "내 노트", time: "어제", + tone: "coral", icon: "note", tags: ["프로덕트 전략", "업무"], + excerpt: "온보딩 3단계 축소 후 D7 리텐션 +6%p. 다음 분기엔 알림 타이밍 개인화 A/B 테스트 진행.", + ai: "‘분기 전략 미팅’ 안건과 연결돼요. 발표 슬라이드에 이 수치를 넣어드릴까요?", + }, + { + id: "k3", type: "idea", title: "아이디어 — 습관 리마인더를 위치 기반으로", src: "빠른 메모", time: "어제", + tone: "violet", icon: "idea", tags: ["아이디어", "개인 성장"], + excerpt: "헬스장 근처를 지나면 운동 리마인더, 집 도착 시 명상 알림. 라이프 케어 모듈과 자연스럽게 이어짐.", + ai: "자동화 규칙으로 만들 수 있어요. ‘위치 도착 → 리마인더’ 레시피를 제안해드릴게요.", + }, + { + id: "k4", type: "highlight", title: "‘딥 워크’ — 몰입의 4가지 규칙 하이라이트", src: "Readwise", time: "3일 전", + tone: "amber", icon: "highlight", tags: ["개인 성장", "독서"], + excerpt: "“집중은 근육과 같다. 산만함을 견디는 훈련을 반복할수록 더 깊이 몰입할 수 있다.”", + ai: "주 4회 ‘딥 워크’ 블록을 캘린더에 잡아드릴까요? 오전 시간이 비어 있어요.", + }, + { + id: "k5", type: "article", title: "개인 재무를 자동화하는 시스템 만들기", src: "newsletter", time: "5일 전", + tone: "green", icon: "article", tags: ["개인 성장", "금융"], + excerpt: "수입이 들어오면 자동으로 비율에 따라 분배(저축·투자·소비)하는 구조를 한 번 세팅해두면 의지력이 필요 없다.", + ai: "금융 모듈의 ‘저축 자동 이체’ 제안과 맞닿아 있어요. 함께 설정해볼까요?", + }, + ], + }; + + window.LifeData = { sources, health, finance, knowledge }; +})(); diff --git a/design-reference/assets/life.css b/design-reference/assets/life.css new file mode 100644 index 0000000..97618ce --- /dev/null +++ b/design-reference/assets/life.css @@ -0,0 +1,587 @@ +/* =========================================================== + 아리 — 라이프 케어(Life Care) · 클린 화이트 글래스 테마 + 상단바 = 메인 메뉴 (공유) · 좌측 = 모듈 + 연결 소스 · 본문 = 모듈별 코칭 + 아리가 건강·금융·지식 데이터를 읽고 분석/코칭/리마인더를 준비한다. + =========================================================== */ + +* { box-sizing: border-box; } +html, body { margin: 0; padding: 0; } + +:root { + --bg-top: #f6efe7; + --bg-mid: #eef0f1; + --bg-bot: #e9ebed; + --card: #ffffff; + --card-2: #f4f5f6; + --card-3: #eef0f1; + --ink: #211f1c; + --ink-2: #514d47; + --muted: #8d8a85; + --faint: #b6b3ad; + --line: rgba(33, 31, 28, 0.07); + --line-2: rgba(33, 31, 28, 0.13); + --fill: #29241f; + --fill-soft: #36302a; + --on-fill: #f4efe6; + --blue: #4f72e0; + --coral: #df7256; + --green: #4e9b66; + --violet: #8b6fd4; + --amber: #e0a23c; + --radius: 22px; + --radius-sm: 14px; + --shadow: 0 10px 30px -14px rgba(30, 26, 22, 0.22), 0 2px 6px -2px rgba(30, 26, 22, 0.06); + --shadow-sm: 0 4px 14px -8px rgba(30, 26, 22, 0.18); + --glass: rgba(255, 255, 255, 0.4); + --glass-2: rgba(255, 255, 255, 0.26); + --glass-brd: rgba(255, 255, 255, 0.7); + --glass-ink: rgba(26, 22, 18, 0.46); + --blur: blur(26px) saturate(190%); + --glass-hi: inset 0 1px 0 rgba(255, 255, 255, 0.75); + --font-disp: "Onest", "Pretendard", system-ui, sans-serif; + --font-ui: "Pretendard", "Onest", system-ui, sans-serif; + --font-mono: "DM Mono", ui-monospace, monospace; +} +[data-theme="dark"] { + --bg-top: #201d1a; + --bg-mid: #1a1816; + --bg-bot: #161413; + --card: #2c2925; + --card-2: #353029; + --card-3: #3b352d; + --ink: #f3eee6; + --ink-2: #c3bdb4; + --muted: #948e85; + --faint: #6f6a62; + --line: rgba(244, 239, 230, 0.09); + --line-2: rgba(244, 239, 230, 0.16); + --fill: #f4efe6; + --fill-soft: #e7e0d4; + --on-fill: #211f1c; + --shadow: 0 14px 36px -16px rgba(0, 0, 0, 0.7), 0 2px 8px -3px rgba(0, 0, 0, 0.4); + --shadow-sm: 0 6px 16px -10px rgba(0, 0, 0, 0.6); + --glass: rgba(48, 43, 38, 0.4); + --glass-2: rgba(70, 63, 55, 0.28); + --glass-brd: rgba(255, 255, 255, 0.1); + --glass-ink: rgba(16, 13, 11, 0.42); + --glass-hi: inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +body { + font-family: var(--font-ui); + color: var(--ink); + background: linear-gradient(178deg, var(--bg-top) 0%, var(--bg-mid) 20%, var(--bg-bot) 100%); + background-attachment: fixed; + min-height: 100vh; + -webkit-font-smoothing: antialiased; + letter-spacing: -0.011em; + word-break: keep-all; +} +button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; } +input, textarea { font-family: inherit; } +:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; border-radius: 6px; } +.ic { width: 1em; height: 1em; flex-shrink: 0; display: block; } +.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; } +.tnum { font-variant-numeric: tabular-nums; } + +.mpage { max-width: 1680px; margin: 0 auto; padding: 0 26px 26px; } + +/* ====================== 상단바 — 메인 메뉴 (공유) ====================== */ +.topbar { + position: sticky; top: 0; z-index: 40; + display: flex; align-items: center; gap: 24px; + height: 70px; margin: 0 -26px; padding: 0 30px; + -webkit-backdrop-filter: blur(14px) saturate(150%); + backdrop-filter: blur(14px) saturate(150%); +} +.brand { display: flex; align-items: center; gap: 11px; flex-shrink: 0; } +.brand-mark { + width: 40px; height: 40px; border-radius: 12px; + background: var(--fill); color: var(--coral); + display: grid; place-items: center; position: relative; +} +.brand-mark .ic { width: 21px; height: 21px; } +.brand-mark::after { + content: ""; position: absolute; top: -2px; right: -2px; + width: 9px; height: 9px; border-radius: 50%; + background: var(--coral); border: 2px solid var(--bg-top); +} +.brand-tag { line-height: 1.15; } +.brand-tag b { display: block; font-size: 15px; font-weight: 700; letter-spacing: -0.02em; white-space: nowrap; } +.brand-tag span { + font-size: 9.5px; font-weight: 700; letter-spacing: 0.08em; + color: var(--muted); text-transform: uppercase; white-space: nowrap; +} +.mainnav { display: flex; align-items: center; gap: 2px; min-width: 0; overflow-x: auto; scrollbar-width: none; } +.mainnav::-webkit-scrollbar { display: none; } +.mainnav a { + text-decoration: none; cursor: pointer; + display: inline-flex; align-items: center; gap: 8px; + font-size: 14px; font-weight: 500; color: var(--ink-2); + padding: 9px 16px; border-radius: 999px; white-space: nowrap; + transition: background 0.16s, color 0.16s, box-shadow 0.16s; +} +.mainnav a .ic { width: 16px; height: 16px; color: var(--muted); transition: color 0.16s; } +.mainnav a .badge { font-size: 11px; font-weight: 700; background: var(--coral); color: #fff; border-radius: 999px; padding: 0 6px; min-width: 18px; text-align: center; } +.mainnav a:hover { color: var(--ink); } +.mainnav a:hover .ic { color: var(--ink-2); } +.mainnav a.active { + background: var(--glass); + -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); + color: var(--ink); font-weight: 600; box-shadow: var(--shadow-sm); + border: 1px solid var(--glass-brd); +} +.mainnav a.active .ic { color: var(--coral); } +.top-actions { margin-left: auto; display: flex; align-items: center; gap: 9px; flex-shrink: 0; } +.t-btn { + width: 40px; height: 40px; border-radius: 50%; + display: grid; place-items: center; color: var(--ink-2); + background: var(--glass); + -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); + border: 1px solid var(--glass-brd); box-shadow: var(--shadow-sm); + position: relative; transition: transform 0.12s, color 0.16s; +} +.t-btn .ic { width: 18px; height: 18px; } +.t-btn:hover { color: var(--ink); transform: translateY(-1px); } +.t-btn.dot::after { + content: ""; position: absolute; top: 9px; right: 10px; + width: 7px; height: 7px; border-radius: 50%; + background: var(--coral); border: 2px solid var(--card); +} +.t-ava { + width: 40px; height: 40px; border-radius: 50%; + background: linear-gradient(135deg, var(--coral), var(--violet)); + color: #fff; display: grid; place-items: center; + font-weight: 700; font-size: 15px; box-shadow: var(--shadow-sm); cursor: pointer; +} + +/* ====================== 본문 레이아웃 ====================== */ +.lwork { + display: grid; + grid-template-columns: 256px minmax(0, 1fr); + gap: 16px; align-items: start; padding-top: 12px; +} + +/* -------- 좌측: 모듈 + 연결 소스 -------- */ +.subnav { + position: sticky; top: 92px; + background: var(--glass); + -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); + border: 1px solid var(--glass-brd); + border-radius: var(--radius); + box-shadow: var(--shadow), var(--glass-hi); + padding: 14px 12px 12px; + display: flex; flex-direction: column; +} +.sn-label { + font-size: 10.5px; font-weight: 700; letter-spacing: 0.08em; + text-transform: uppercase; color: var(--faint); + padding: 14px 10px 7px; display: flex; align-items: center; +} +.mod-row { + display: flex; align-items: center; gap: 11px; width: 100%; text-align: left; + padding: 10px 11px; border-radius: 12px; margin-bottom: 3px; white-space: nowrap; + color: var(--ink-2); transition: background 0.13s, color 0.13s; +} +.mod-row .m-ic { + width: 32px; height: 32px; border-radius: 9px; flex-shrink: 0; + display: grid; place-items: center; background: var(--card-2); color: var(--muted); + transition: background 0.13s, color 0.13s; +} +.mod-row .m-ic .ic { width: 17px; height: 17px; } +.mod-row .m-txt { min-width: 0; flex: 1; line-height: 1.25; } +.mod-row .m-txt b { display: block; font-size: 13.5px; font-weight: 600; color: var(--ink); } +.mod-row .m-txt span { font-size: 11px; color: var(--muted); } +.mod-row:hover { background: var(--glass-2); } +.mod-row.on { background: var(--fill); } +.mod-row.on .m-txt b, .mod-row.on .m-txt span { color: var(--on-fill); } +.mod-row.on .m-txt span { opacity: 0.7; } +.mod-row.on .m-ic { background: rgba(255,255,255,0.14); color: var(--on-fill); } +.mod-row.on.t-coral .m-ic { background: var(--coral); color: #fff; } +.mod-row.on.t-blue .m-ic { background: var(--blue); color: #fff; } +.mod-row.on.t-violet .m-ic { background: var(--violet); color: #fff; } +.mod-row.on.t-amber .m-ic { background: var(--amber); color: #fff; } + +.src-list { margin-top: 2px; display: flex; flex-direction: column; gap: 1px; } +.src-row { + display: flex; align-items: center; gap: 9px; width: 100%; text-align: left; + padding: 7px 10px; border-radius: 9px; white-space: nowrap; + transition: background 0.13s; +} +.src-row:hover { background: var(--glass-2); } +.src-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } +.src-dot.off { background: var(--faint) !important; } +.src-info { min-width: 0; flex: 1; line-height: 1.2; } +.src-info b { font-size: 12.5px; font-weight: 600; color: var(--ink); display: block; } +.src-info span { font-size: 10px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; } +.src-row .src-stat { font-size: 9.5px; font-weight: 700; color: var(--green); } +.src-row .src-stat.off { color: var(--faint); } +.src-add { + display: flex; align-items: center; gap: 9px; width: 100%; white-space: nowrap; + padding: 9px 10px; margin-top: 4px; border-radius: 10px; + font-size: 12.5px; font-weight: 500; color: var(--muted); + border: 1px dashed var(--line-2); transition: background 0.13s, color 0.13s, border-color 0.13s; +} +.src-add .ic { width: 15px; height: 15px; } +.src-add:hover { background: var(--glass-2); color: var(--ink); border-color: var(--muted); } + +.sn-foot { + margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); + display: flex; align-items: center; gap: 10px; +} +.sn-foot .av-lg { + width: 32px; height: 32px; border-radius: 50%; + background: linear-gradient(135deg, var(--coral), var(--violet)); + color: #fff; display: grid; place-items: center; font-weight: 700; font-size: 13px; flex-shrink: 0; +} +.sn-foot .txt { min-width: 0; flex: 1; } +.sn-foot .txt b { font-size: 13px; font-weight: 600; display: block; } +.sn-foot .txt span { font-size: 11.5px; color: var(--muted); } + +/* ====================== 본문 패널 ====================== */ +.lmain { min-width: 0; display: flex; flex-direction: column; gap: 16px; } + +/* 아리 코칭 히어로 */ +.coach-hero { + display: flex; align-items: flex-start; gap: 16px; + padding: 22px 24px; border-radius: var(--radius); + background: + radial-gradient(90% 130% at 0% 0%, color-mix(in oklab, var(--coral) 18%, transparent), transparent 56%), + var(--card); + border: 1px solid color-mix(in oklab, var(--coral) 22%, var(--line-2)); + box-shadow: var(--shadow); +} +.coach-hero .ch-ava { + width: 44px; height: 44px; border-radius: 13px; flex-shrink: 0; + display: grid; place-items: center; color: #fff; background: var(--coral); + box-shadow: 0 7px 18px -6px var(--coral); +} +.coach-hero .ch-ava .ic { width: 23px; height: 23px; } +.coach-hero .ch-main { flex: 1; min-width: 0; } +.coach-hero .ch-eyebrow { font-size: 12px; font-weight: 700; color: var(--coral); letter-spacing: 0.02em; } +.coach-hero h1 { font-family: var(--font-disp); font-size: clamp(20px, 2.4vw, 26px); font-weight: 700; letter-spacing: -0.02em; line-height: 1.25; margin: 5px 0 0; } +.coach-hero p { font-size: 14px; color: var(--ink-2); line-height: 1.6; margin: 10px 0 0; max-width: 70ch; } +.coach-hero p b { color: var(--ink); font-weight: 700; } + +/* 모듈 헤더 */ +.mod-head { display: flex; align-items: center; gap: 12px; padding: 2px 2px 0; } +.mod-head .mh-ic { width: 36px; height: 36px; border-radius: 11px; display: grid; place-items: center; flex-shrink: 0; } +.mod-head .mh-ic .ic { width: 19px; height: 19px; } +.mod-head .mh-txt { min-width: 0; flex: 1; } +.mod-head h2 { font-family: var(--font-disp); font-size: 20px; font-weight: 700; letter-spacing: -0.02em; margin: 0; } +.mod-head .mh-sub { font-size: 12.5px; color: var(--muted); margin-top: 1px; } +.mod-head .mh-act { display: flex; gap: 7px; flex-shrink: 0; } + +/* 그리드 */ +.lgrid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 16px; align-items: start; } +.col-4 { grid-column: span 4; } +.col-5 { grid-column: span 5; } +.col-6 { grid-column: span 6; } +.col-7 { grid-column: span 7; } +.col-8 { grid-column: span 8; } +.col-12 { grid-column: span 12; } +@media (max-width: 1180px) { + .col-4, .col-5, .col-6, .col-7, .col-8 { grid-column: span 12; } +} + +/* 카드 */ +.card { + background: var(--card); border: 1px solid var(--line); + border-radius: 18px; box-shadow: var(--shadow-sm); + padding: 18px 19px; +} +.card.glassy { + background: var(--glass); + -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); + border-color: var(--glass-brd); box-shadow: var(--shadow), var(--glass-hi); +} +.card-head { display: flex; align-items: center; gap: 9px; margin-bottom: 15px; } +.card-head .c-ic { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; flex-shrink: 0; } +.card-head .c-ic .ic { width: 16px; height: 16px; } +.card-head .c-h { flex: 1; min-width: 0; line-height: 1.2; } +.card-head .c-h b { font-size: 14px; font-weight: 700; display: block; } +.card-head .c-h span { font-size: 11.5px; color: var(--muted); } +.card-head .c-act { font-size: 11.5px; font-weight: 600; color: var(--muted); display: inline-flex; align-items: center; gap: 4px; padding: 5px 9px; border-radius: 8px; white-space: nowrap; transition: background 0.13s, color 0.13s; } +.card-head .c-act .ic { width: 13px; height: 13px; } +.card-head .c-act:hover { background: var(--card-2); color: var(--ink); } + +/* 토큰 색상 헬퍼 */ +.t-blue { color: var(--blue); } +.t-coral { color: var(--coral); } +.t-green { color: var(--green); } +.t-violet { color: var(--violet); } +.t-amber { color: var(--amber); } +.t-muted { color: var(--muted); } +.bg-blue { background: color-mix(in oklab, var(--blue) 13%, transparent); color: var(--blue); } +.bg-coral { background: color-mix(in oklab, var(--coral) 14%, transparent); color: var(--coral); } +.bg-green { background: color-mix(in oklab, var(--green) 14%, transparent); color: var(--green); } +.bg-violet { background: color-mix(in oklab, var(--violet) 14%, transparent); color: var(--violet); } +.bg-amber { background: color-mix(in oklab, var(--amber) 16%, transparent); color: var(--amber); } +.bg-muted { background: var(--card-3); color: var(--muted); } + +/* ---------- 건강: 액티비티 링 ---------- */ +.rings3 { display: flex; gap: 14px; } +.ring-card { flex: 1; text-align: center; } +.ring { + --p: 0; --c: var(--coral); + width: 92px; height: 92px; border-radius: 50%; margin: 0 auto 10px; + display: grid; place-items: center; position: relative; + background: + radial-gradient(closest-side, var(--card) 73%, transparent 74%), + conic-gradient(var(--c) calc(var(--p) * 1%), var(--card-3) 0); +} +.ring .r-in { display: flex; flex-direction: column; align-items: center; line-height: 1; } +.ring .r-in .ic { width: 17px; height: 17px; color: var(--c); margin-bottom: 3px; } +.ring .r-in b { font-size: 16px; font-weight: 800; font-family: var(--font-disp); font-variant-numeric: tabular-nums; } +.ring-card .rc-label { font-size: 12.5px; font-weight: 600; } +.ring-card .rc-goal { font-size: 10.5px; color: var(--muted); margin-top: 1px; } + +/* 바이탈 */ +.vitals { display: flex; flex-direction: column; gap: 1px; } +.vital-row { display: flex; align-items: center; gap: 11px; padding: 11px 2px; border-top: 1px solid var(--line); } +.vital-row:first-child { border-top: none; } +.vital-row .v-dot { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; flex-shrink: 0; } +.vital-row .v-dot .ic { width: 15px; height: 15px; } +.vital-row .v-main { flex: 1; min-width: 0; } +.vital-row .v-main b { font-size: 13px; font-weight: 600; } +.vital-row .v-main span { font-size: 11px; color: var(--muted); display: block; } +.vital-row .v-val { font-family: var(--font-disp); font-weight: 800; font-size: 17px; font-variant-numeric: tabular-nums; } +.vital-row .v-val small { font-size: 11px; font-weight: 600; color: var(--muted); margin-left: 2px; } + +/* 수면 */ +.sleep-top { display: flex; align-items: center; gap: 16px; margin-bottom: 16px; } +.sleep-score { + --p: 0; width: 74px; height: 74px; border-radius: 50%; flex-shrink: 0; + display: grid; place-items: center; + background: radial-gradient(closest-side, var(--card) 72%, transparent 73%), conic-gradient(var(--violet) calc(var(--p)*1%), var(--card-3) 0); +} +.sleep-score b { font-size: 19px; font-weight: 800; font-family: var(--font-disp); } +.sleep-score span { font-size: 9px; color: var(--muted); font-weight: 700; letter-spacing: 0.06em; } +.sleep-meta { min-width: 0; } +.sleep-meta .sm-total { font-family: var(--font-disp); font-size: 22px; font-weight: 800; letter-spacing: -0.02em; } +.sleep-meta .sm-label { font-size: 12px; color: var(--muted); } +.sleep-bar { display: flex; height: 11px; border-radius: 999px; overflow: hidden; margin-bottom: 9px; gap: 2px; } +.sleep-bar > span { display: block; height: 100%; border-radius: 3px; } +.sleep-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; } +.sleep-legend .lg { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ink-2); } +.sleep-legend .lg i { width: 9px; height: 9px; border-radius: 3px; } +.sleep-week { display: flex; align-items: flex-end; gap: 6px; height: 56px; margin-top: 16px; } +.sleep-week .sw { flex: 1; background: var(--card-3); border-radius: 5px 5px 3px 3px; position: relative; transition: background 0.13s; } +.sleep-week .sw.peak { background: color-mix(in oklab, var(--violet) 55%, transparent); } +.sleep-week-labels { display: flex; gap: 6px; margin-top: 5px; } +.sleep-week-labels span { flex: 1; text-align: center; font-size: 9.5px; color: var(--faint); } +.sleep-note { font-size: 12.5px; color: var(--ink-2); line-height: 1.5; margin-top: 14px; padding: 11px 13px; border-radius: 12px; background: color-mix(in oklab, var(--violet) 7%, var(--card-2)); border: 1px solid color-mix(in oklab, var(--violet) 18%, var(--line)); } + +/* 습관 */ +.habits { display: grid; grid-template-columns: repeat(2, 1fr); gap: 11px; } +@media (max-width: 1180px) { .habits { grid-template-columns: repeat(2, 1fr); } } +@media (max-width: 620px) { .habits { grid-template-columns: 1fr; } } +.habit { + padding: 14px 15px; border-radius: 14px; background: var(--card-2); border: 1px solid var(--line); + transition: border-color 0.14s, background 0.14s, transform 0.12s; +} +.habit.done { background: color-mix(in oklab, var(--green) 9%, var(--card-2)); border-color: color-mix(in oklab, var(--green) 28%, var(--line)); } +.habit-top { display: flex; align-items: center; gap: 10px; margin-bottom: 11px; } +.habit-top .h-ic { width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center; flex-shrink: 0; } +.habit-top .h-ic .ic { width: 17px; height: 17px; } +.habit-top .h-tx { flex: 1; min-width: 0; line-height: 1.25; } +.habit-top .h-tx b { font-size: 13.5px; font-weight: 700; display: block; } +.habit-top .h-tx span { font-size: 11px; color: var(--muted); } +.habit-streak { font-size: 10.5px; font-weight: 700; color: var(--amber); display: inline-flex; align-items: center; gap: 3px; flex-shrink: 0; } +.habit-streak .ic { width: 12px; height: 12px; } +.habit-prog { display: flex; align-items: center; gap: 9px; } +.habit-prog .hp-bar { flex: 1; height: 7px; border-radius: 999px; background: var(--card-3); overflow: hidden; } +.habit-prog .hp-bar > i { display: block; height: 100%; border-radius: 999px; transition: width 0.5s cubic-bezier(0.2,0.7,0.2,1); } +.habit-prog .hp-n { font-size: 11px; font-weight: 700; color: var(--ink-2); font-variant-numeric: tabular-nums; white-space: nowrap; } +.habit-foot { display: flex; align-items: center; gap: 8px; margin-top: 11px; } +.habit-at { font-size: 10.5px; color: var(--muted); display: inline-flex; align-items: center; gap: 4px; flex: 1; min-width: 0; } +.habit-at .ic { width: 12px; height: 12px; flex-shrink: 0; } +.habit-btn { + display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; flex-shrink: 0; + font-size: 11.5px; font-weight: 700; padding: 6px 11px; border-radius: 8px; + background: var(--fill); color: var(--on-fill); transition: filter 0.13s, transform 0.12s; +} +.habit-btn .ic { width: 12px; height: 12px; } +.habit-btn:hover { transform: translateY(-1px); filter: brightness(1.08); } +.habit-btn.done { background: color-mix(in oklab, var(--green) 16%, transparent); color: var(--green); pointer-events: none; } + +/* ---------- 금융 ---------- */ +.budget-top { display: flex; align-items: baseline; gap: 10px; margin-bottom: 5px; } +.budget-top .bt-spent { font-family: var(--font-disp); font-size: 30px; font-weight: 800; letter-spacing: -0.02em; } +.budget-top .bt-of { font-size: 13px; color: var(--muted); } +.budget-delta { font-size: 12px; font-weight: 600; display: inline-flex; align-items: center; gap: 4px; } +.budget-delta .ic { width: 13px; height: 13px; } +.budget-gauge { height: 12px; border-radius: 999px; background: var(--card-3); overflow: hidden; margin: 14px 0 8px; position: relative; } +.budget-gauge > i { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--green), var(--blue)); transition: width 0.6s cubic-bezier(0.2,0.7,0.2,1); } +.budget-foot { display: flex; justify-content: space-between; font-size: 11.5px; color: var(--muted); } + +/* 카테고리 */ +.cats { display: flex; flex-direction: column; gap: 1px; } +.cat-row { display: flex; align-items: center; gap: 11px; padding: 10px 2px; border-top: 1px solid var(--line); } +.cat-row:first-child { border-top: none; } +.cat-row .ca-ic { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; flex-shrink: 0; } +.cat-row .ca-ic .ic { width: 15px; height: 15px; } +.cat-row .ca-main { flex: 1; min-width: 0; } +.cat-row .ca-name { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; } +.cat-row .ca-name .over-tag { font-size: 9.5px; font-weight: 700; color: var(--coral); background: color-mix(in oklab, var(--coral) 13%, transparent); padding: 1px 6px; border-radius: 999px; } +.cat-row .ca-bar { height: 6px; border-radius: 999px; background: var(--card-3); overflow: hidden; margin-top: 6px; } +.cat-row .ca-bar > i { display: block; height: 100%; border-radius: 999px; } +.cat-row .ca-amt { font-family: var(--font-disp); font-weight: 700; font-size: 13.5px; font-variant-numeric: tabular-nums; flex-shrink: 0; text-align: right; } +.cat-row .ca-amt small { display: block; font-size: 10px; font-weight: 600; color: var(--muted); } + +/* 구독 */ +.subs { display: flex; flex-direction: column; gap: 8px; } +.sub-row { + display: flex; align-items: center; gap: 11px; padding: 11px 13px; + border-radius: 12px; background: var(--card-2); border: 1px solid var(--line); + transition: border-color 0.13s, transform 0.12s; +} +.sub-row.soon { border-color: color-mix(in oklab, var(--coral) 30%, var(--line)); background: color-mix(in oklab, var(--coral) 6%, var(--card-2)); } +.sub-row .su-ic { width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center; flex-shrink: 0; } +.sub-row .su-ic .ic { width: 17px; height: 17px; } +.sub-row .su-main { flex: 1; min-width: 0; line-height: 1.3; } +.sub-row .su-main b { font-size: 13.5px; font-weight: 700; } +.sub-row .su-main span { font-size: 11px; color: var(--muted); display: block; } +.sub-row .su-when { text-align: right; flex-shrink: 0; } +.sub-row .su-when .sw-date { font-size: 12px; font-weight: 600; color: var(--ink); } +.sub-row .su-when .sw-in { font-size: 10px; font-weight: 700; color: var(--coral); } +.sub-row .su-when .sw-in.far { color: var(--muted); } +.sub-row .su-amt { font-family: var(--font-disp); font-weight: 800; font-size: 14px; font-variant-numeric: tabular-nums; flex-shrink: 0; min-width: 64px; text-align: right; } + +/* 인사이트 */ +.insights { display: flex; flex-direction: column; gap: 9px; } +.insight { + display: flex; gap: 11px; padding: 13px 14px; border-radius: 13px; + background: var(--card-2); border: 1px solid var(--line); +} +.insight.alert { background: color-mix(in oklab, var(--coral) 7%, var(--card-2)); border-color: color-mix(in oklab, var(--coral) 24%, var(--line)); } +.insight .in-ic { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; flex-shrink: 0; } +.insight .in-ic .ic { width: 16px; height: 16px; } +.insight.alert .in-ic { background: var(--coral); color: #fff; } +.insight.save .in-ic { background: color-mix(in oklab, var(--green) 14%, transparent); color: var(--green); } +.insight.info .in-ic { background: color-mix(in oklab, var(--blue) 14%, transparent); color: var(--blue); } +.insight .in-main { flex: 1; min-width: 0; } +.insight .in-main b { font-size: 13px; font-weight: 700; display: block; } +.insight .in-main p { font-size: 12px; color: var(--ink-2); line-height: 1.5; margin: 4px 0 0; } +.insight .in-act { align-self: center; flex-shrink: 0; font-size: 11.5px; font-weight: 700; padding: 7px 12px; border-radius: 9px; background: var(--fill); color: var(--on-fill); transition: filter 0.13s, transform 0.12s; } +.insight .in-act:hover { transform: translateY(-1px); filter: brightness(1.08); } + +/* ---------- 지식 (제2의 뇌) ---------- */ +.kn-search { + display: flex; align-items: center; gap: 11px; + padding: 14px 18px; border-radius: 16px; + background: var(--card); border: 1.5px solid var(--line-2); + transition: box-shadow 0.16s, border-color 0.16s; +} +.kn-search:focus-within { border-color: color-mix(in oklab, var(--violet) 45%, var(--line)); box-shadow: 0 0 0 4px color-mix(in oklab, var(--violet) 14%, transparent); } +.kn-search .ks-spark { width: 20px; height: 20px; color: var(--violet); flex-shrink: 0; } +.kn-search input { flex: 1; min-width: 0; border: none; background: none; outline: none; font-size: 15px; color: var(--ink); } +.kn-search input::placeholder { color: var(--faint); } +.kn-search .ks-go { width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center; background: var(--violet); color: #fff; flex-shrink: 0; transition: filter 0.13s, transform 0.12s; } +.kn-search .ks-go:hover { transform: translateY(-1px); filter: brightness(1.07); } +.kn-search .ks-go .ic { width: 17px; height: 17px; } +.kn-suggest { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; } +.kn-chip { + display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 500; + color: var(--ink-2); background: var(--card-2); border: 1px solid var(--line); + border-radius: 999px; padding: 7px 13px; transition: background 0.13s, color 0.13s, border-color 0.13s; +} +.kn-chip .ic { width: 13px; height: 13px; color: var(--violet); } +.kn-chip:hover { background: color-mix(in oklab, var(--violet) 9%, var(--card-2)); border-color: color-mix(in oklab, var(--violet) 30%, var(--line)); color: var(--ink); } + +/* AI 답변 카드 */ +.kn-answer { + display: flex; gap: 13px; padding: 17px 18px; border-radius: 16px; + background: + radial-gradient(90% 120% at 0% 0%, color-mix(in oklab, var(--violet) 14%, transparent), transparent 55%), + var(--card); + border: 1px solid color-mix(in oklab, var(--violet) 26%, var(--line-2)); + box-shadow: var(--shadow-sm); animation: rise 0.35s cubic-bezier(0.2,0.7,0.2,1); +} +.kn-answer .ka-ava { width: 32px; height: 32px; border-radius: 10px; display: grid; place-items: center; background: var(--violet); color: #fff; flex-shrink: 0; box-shadow: 0 5px 14px -5px var(--violet); } +.kn-answer .ka-ava .ic { width: 17px; height: 17px; } +.kn-answer .ka-main { flex: 1; min-width: 0; } +.kn-answer .ka-q { font-size: 12px; color: var(--violet); font-weight: 700; margin-bottom: 5px; } +.kn-answer .ka-body { font-size: 13.5px; color: var(--ink); line-height: 1.6; } +.kn-answer .ka-body b { font-weight: 700; } +.kn-answer .ka-src { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; } +.kn-answer .ka-src .src-chip { font-size: 11px; color: var(--ink-2); background: var(--card-2); border: 1px solid var(--line); border-radius: 8px; padding: 4px 9px; display: inline-flex; align-items: center; gap: 5px; } +.kn-answer .ka-src .src-chip .ic { width: 11px; height: 11px; color: var(--muted); } + +/* 컬렉션 */ +.collections { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; } +.coll { + display: flex; align-items: center; gap: 10px; padding: 12px 13px; border-radius: 13px; + background: var(--card-2); border: 1px solid var(--line); text-align: left; + transition: border-color 0.13s, transform 0.12s, background 0.13s; +} +.coll:hover { transform: translateY(-1px); background: var(--card-3); } +.coll .co-ic { width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center; flex-shrink: 0; } +.coll .co-ic .ic { width: 16px; height: 16px; } +.coll .co-tx { min-width: 0; flex: 1; line-height: 1.25; } +.coll .co-tx b { font-size: 13px; font-weight: 600; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.coll .co-tx span { font-size: 11px; color: var(--muted); } + +/* 스크랩 카드 */ +.scraps { display: flex; flex-direction: column; gap: 11px; } +.scrap { + padding: 15px 17px; border-radius: 15px; background: var(--card); border: 1px solid var(--line); + box-shadow: var(--shadow-sm); transition: border-color 0.14s, transform 0.12s; +} +.scrap:hover { transform: translateY(-1px); border-color: var(--line-2); } +.scrap-top { display: flex; align-items: center; gap: 10px; margin-bottom: 9px; } +.scrap-top .sc-ic { width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center; flex-shrink: 0; } +.scrap-top .sc-ic .ic { width: 16px; height: 16px; } +.scrap-top .sc-h { flex: 1; min-width: 0; } +.scrap-top .sc-h b { font-size: 14.5px; font-weight: 700; line-height: 1.3; display: block; } +.scrap-top .sc-meta { font-size: 11px; color: var(--muted); margin-top: 2px; display: flex; align-items: center; gap: 6px; } +.scrap-top .sc-meta .dotsep { width: 3px; height: 3px; border-radius: 50%; background: var(--faint); } +.scrap-type { font-size: 9.5px; font-weight: 700; padding: 2px 8px; border-radius: 999px; flex-shrink: 0; } +.scrap-excerpt { font-size: 13px; color: var(--ink-2); line-height: 1.6; margin: 0 0 11px; } +.scrap-ai { display: flex; gap: 9px; padding: 11px 13px; border-radius: 12px; background: color-mix(in oklab, var(--violet) 7%, var(--card-2)); border: 1px solid color-mix(in oklab, var(--violet) 17%, var(--line)); margin-bottom: 11px; } +.scrap-ai .sa-ic { width: 22px; height: 22px; border-radius: 7px; display: grid; place-items: center; background: var(--violet); color: #fff; flex-shrink: 0; } +.scrap-ai .sa-ic .ic { width: 12px; height: 12px; } +.scrap-ai p { font-size: 12px; color: var(--ink-2); line-height: 1.5; margin: 0; } +.scrap-ai p b { color: var(--violet); font-weight: 700; } +.scrap-foot { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; } +.tag { font-size: 11px; font-weight: 600; color: var(--ink-2); background: var(--card-2); border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px; display: inline-flex; align-items: center; gap: 4px; } +.tag::before { content: "#"; color: var(--faint); font-weight: 700; } +.tag.auto { color: var(--violet); background: color-mix(in oklab, var(--violet) 8%, transparent); border-color: color-mix(in oklab, var(--violet) 22%, var(--line)); } +.scrap-foot .sf-spacer { flex: 1; } +.scrap-foot .sf-act { font-size: 11.5px; font-weight: 600; color: var(--muted); display: inline-flex; align-items: center; gap: 5px; padding: 5px 9px; border-radius: 8px; transition: background 0.13s, color 0.13s; } +.scrap-foot .sf-act .ic { width: 13px; height: 13px; } +.scrap-foot .sf-act:hover { background: var(--card-2); color: var(--ink); } + +/* 빈 상태 (검색) */ +.kn-empty { text-align: center; padding: 30px 20px; color: var(--muted); font-size: 13px; } + +/* 진입 애니메이션 */ +.entered .lwork > * { animation: rise 0.42s cubic-bezier(0.2,0.7,0.2,1) both; } +.entered .lmain { animation-delay: 0.05s; } +@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } + +/* 토스트 */ +.toast-wrap { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); z-index: 80; display: flex; flex-direction: column; gap: 8px; align-items: center; } +.toast { + display: flex; align-items: center; gap: 10px; + padding: 12px 18px; border-radius: 13px; + background: var(--fill); color: var(--on-fill); + font-size: 13px; font-weight: 600; box-shadow: var(--shadow); + animation: toastin 0.3s cubic-bezier(0.2,0.7,0.2,1); +} +.toast .t-dot { width: 8px; height: 8px; border-radius: 50%; } +@keyframes toastin { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } } + +/* 반응형 */ +@media (max-width: 1080px) { + .lwork { grid-template-columns: 1fr; } + .subnav { position: static; flex-direction: column; } +} +@media (max-width: 760px) { + .mpage { padding: 0 12px 20px; } + .topbar { margin: 0 -12px; padding: 0 14px; gap: 14px; } + .mainnav { display: none; } +} +@media (prefers-reduced-motion: reduce) { + * { animation: none !important; transition: none !important; } +} diff --git a/design-reference/assets/life.jsx b/design-reference/assets/life.jsx new file mode 100644 index 0000000..140523a --- /dev/null +++ b/design-reference/assets/life.jsx @@ -0,0 +1,675 @@ +/* 아리 — 라이프 케어(Life Care) + 건강·웰니스 / 금융·소비 / 지식·학습(제2의 뇌) + 아리가 각 영역의 데이터를 읽고 분석·코칭·리마인더를 미리 준비해 둔다. */ +const { useState, useEffect, useRef, useMemo } = React; +const L = window.LifeData; + +/* ---------------- 아이콘 ---------------- */ +const P = { + spark: "M12 3l1.9 4.6L18.5 9.5l-4.6 1.9L12 16l-1.9-4.6L5.5 9.5l4.6-1.9z|M19 13l.7 1.8 1.8.7-1.8.7-.7 1.8-.7-1.8-1.8-.7 1.8-.7z", + grid: "M3 3h7v7H3z|M14 3h7v7h-7z|M14 14h7v7h-7z|M3 14h7v7H3z", + inbox: "M22 12h-6l-2 3h-4l-2-3H2|M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z", + route: "M6 19a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M18 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M9 16h6a3 3 0 0 0 3-3V11", + cal: "M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", + check: "M9 11l3 3L22 4|M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11", + mail: "M22 7 13.03 12.7a1.94 1.94 0 0 1-2.06 0L2 7|M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", + brain: "M12 5a3 3 0 1 0-5.9.8A3 3 0 0 0 4 9.5 3 3 0 0 0 6 14a3 3 0 0 0 6 1zM12 5a3 3 0 1 1 5.9.8A3 3 0 0 1 20 9.5 3 3 0 0 1 18 14a3 3 0 0 1-6 1z|M12 5v14", + heart: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7z", + wallet: "M19 7V5a2 2 0 0 0-2-2H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4|M18 12a1 1 0 0 0 0 2h3v-2z", + search: "M21 21l-4.34-4.34|M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z", + bell: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9|M10.3 21a1.94 1.94 0 0 0 3.4 0", + sun: "M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10z|M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4", + moon: "M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z", + plus: "M12 5v14M5 12h14", + x: "M18 6 6 18M6 6l12 12", + chev: "M9 6l6 6-6 6", + arrow: "M5 12h14|M13 6l6 6-6 6", + more: "M5 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0zM11 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0zM17 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0z", + refresh: "M21 12a9 9 0 1 1-3-6.7|M21 3v5h-5", + tick: "M20 6 9 17l-5-5", + clock: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18z|M12 7v5l3 2", + zap: "M13 2 3 14h9l-1 8 10-12h-9z", + send: "M22 2 11 13|M22 2 15 22l-4-9-9-4z", + steps: "M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0zM20 20v-2.38c0-2.12 1.03-3.12 1-5.62-.03-2.72-1.49-6-4.5-6C14.63 6 14 7.8 14 9.5c0 3.11 2 5.66 2 8.68V20a2 2 0 1 0 4 0z", + flame: "M8.5 14.5A2.5 2.5 0 0 0 11 17a2.5 2.5 0 0 0 2.5-2.5c0-1.38-.5-2-1-3-1.07-2.14-.22-4.05 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.15.43-2.29 1-3a2.5 2.5 0 0 0 2.5 2.5z", + water: "M12 2.7S6 9.5 6 14a6 6 0 0 0 12 0c0-4.5-6-11.3-6-11.3z", + stretch: "M22 12h-4l-3 9L9 3l-3 9H2", + pill: "M10.5 20.5 3.5 13.5a5 5 0 0 1 7-7l7 7a5 5 0 0 1-7 7z|M8.5 8.5l7 7", + feather: "M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z|M16 8 2 22|M17.5 15H9", + food: "M3 2v7c0 1.1.9 2 2 2a2 2 0 0 0 2-2V2M5 2v20|M21 15V2a5 5 0 0 0-5 5v6c0 1.1.9 2 2 2h3zm0 0v7", + bag: "M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z|M3 6h18|M16 10a4 4 0 0 1-8 0", + car: "M5 17a2 2 0 1 0 4 0 2 2 0 0 0-4 0zM15 17a2 2 0 1 0 4 0 2 2 0 0 0-4 0z|M5 17H3v-4l2-5h11l3 5v4h-2M5 13h14", + coffee: "M17 8h1a4 4 0 1 1 0 8h-1|M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4z|M6 2v2M10 2v2M14 2v2", + repeat: "M17 2l4 4-4 4M3 11V9a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 13v2a4 4 0 0 1-4 4H3", + ticket: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2z|M13 5v2M13 17v2M13 11v2", + play: "M6 3l15 9-15 9z", + music: "M9 18V5l12-2v13|M6 18a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM18 16a3 3 0 1 0 0-6 3 3 0 0 0 0 6z", + alert: "M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h16.9a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z|M12 9v4M12 17h.01", + trending: "M22 7l-8.5 8.5-5-5L2 17|M16 7h6v6", + article: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z|M14 2v6h6|M9 13h6M9 17h5", + note: "M12 20h9|M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z", + idea: "M9 18h6|M10 22h4|M12 2a7 7 0 0 0-4 12.7c.6.5 1 1.3 1 2.1V17h6v-.2c0-.8.4-1.6 1-2.1A7 7 0 0 0 12 2z", + highlight: "M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z", + folder: "M3 7a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2V18a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z", + link: "M9 17H7A5 5 0 0 1 7 7h2M15 7h2a5 5 0 0 1 0 10h-2M8 12h8", + layers: "M12 2 2 7l10 5 10-5z|M2 17l10 5 10-5M2 12l10 5 10-5", +}; +function I({ d, w }) { + return ( + + {P[d].split("|").map((s, i) => )} + + ); +} + +const MAIN = [ + { id: "dash", label: "대시보드", icon: "grid", href: "대시보드.html" }, + { id: "sbox", label: "인박스", icon: "inbox", href: "인박스.html" }, + { id: "appr", label: "결재함", icon: "spark", badge: "3", href: "결재함.html" }, + { id: "auto", label: "자동화", icon: "zap", href: "자동화.html" }, + { id: "journey", label: "여정", icon: "route", href: "여정.html" }, + { id: "cal", label: "일정", icon: "cal", href: "일정.html" }, + { id: "task", label: "작업", icon: "check", badge: "4", href: "작업.html" }, + { id: "mail", label: "메일", icon: "mail", href: "메일.html" }, + { id: "noti", label: "알림", icon: "bell", badge: "6", href: "알림.html" }, + { id: "research", label: "리서치", icon: "brain", href: "리서치.html" }, + { id: "trip", label: "여행", icon: "send", href: "여행.html" }, + { id: "life", label: "라이프", icon: "heart", href: "라이프.html" }, + { id: "wrap", label: "하루 마감", icon: "moon", href: "하루 마감.html" }, +]; + +const MODULES = [ + { id: "today", label: "오늘의 라이프", sub: "통합 코칭", icon: "spark", tone: "coral" }, + { id: "health", label: "건강 & 웰니스", sub: "활동·수면·습관", icon: "heart", tone: "coral" }, + { id: "finance", label: "금융 & 소비", sub: "예산·구독·자산", icon: "wallet", tone: "blue" }, + { id: "knowledge", label: "지식 & 학습", sub: "제2의 뇌", icon: "brain", tone: "violet" }, +]; + +function load(k, fb) { try { const v = localStorage.getItem("ariL." + k); return v === null ? fb : JSON.parse(v); } catch (e) { return fb; } } +function save(k, v) { try { localStorage.setItem("ariL." + k, JSON.stringify(v)); } catch (e) {} } + +const won = (n) => "₩" + n.toLocaleString("ko-KR"); + +/* ===================== 액티비티 링 ===================== */ +function Ring({ r }) { + return ( +
+
+
+ + {r.val} +
+
+
{r.label}{r.unit && {r.unit}}
+
목표 {r.goal}
+
+ ); +} + +/* ===================== 건강 모듈 ===================== */ +function HealthModule({ habits, onHabit, toast }) { + const h = L.health; + return ( + <> +
+
+
+

건강 & 웰니스

+
Apple Health · Apple Watch 연동 · 오늘 {new Date().getHours()}시 기준
+
+
+ toast("건강 리포트를 생성하고 있어요", "coral")}>주간 리포트 +
+
+ +
+
+
+
+
아리의 건강 코칭
+

+

+
+
+ +
+
+
+
+
+
오늘의 활동목표까지 조금만 더
+
+
{h.rings.map((r) => )}
+
+
+
+
+
+
+
바이탈웨어러블 측정값
+
+
+ {h.vitals.map((v) => ( +
+
+
{v.label}{v.note}
+
{v.val}{v.unit && {v.unit}}
+
+ ))} +
+
+
+ +
+
+
+
+
수면 분석어젯밤 · 7일 추이
+
+
+
+ {h.sleep.score}점수 +
+
+
{h.sleep.total}
+
권장보다 18분 부족
+
+
+
+ {h.sleep.stages.map((s) => ( + + ))} +
+
+ {h.sleep.stages.map((s) => ( + {s.label} {s.pct}% + ))} +
+
+ {h.sleep.week.map((v, i) => ( +
= 86 ? " peak" : "")} style={{ height: v + "%" }} title={v + "점"} /> + ))} +
+
+ {["월", "화", "수", "목", "금", "토", "일"].map((d) => {d})} +
+
{h.sleep.note}
+
+
+ +
+
+
+
+
건강한 습관리마인더 자동 설정됨
+ 추가 +
+
+ {habits.map((hb) => { + const done = hb.done >= hb.total; + return ( +
+
+
+
{hb.title}{hb.goal}
+ {hb.streak}일 +
+
+
+ {hb.done}/{hb.total} +
+
+ {hb.at} + +
+
+ ); + })} +
+
+
+
+ + ); +} + +/* ===================== 금융 모듈 ===================== */ +function FinanceModule({ toast, paused, onPause }) { + const f = L.finance; + const b = f.budget; + return ( + <> +
+
+
+

금융 & 소비

+
우리카드 · 토스 · 카카오뱅크 연동 · 6월 예산
+
+
toast("이번 달 가계부를 정리하고 있어요", "blue")}>월간 정산
+
+ +
+
+
+
+
아리의 소비 코칭
+

+

+
+
+ +
+
+
+
+
+
이달 예산{b.daysLeft}일 남음
+
+
+ {won(b.spent)} + / {won(b.limit)} +
+ 지난달 대비 {Math.abs(b.deltaPct)}% 적게 썼어요 +
+
{b.pct}% 사용남은 예산 {won(b.limit - b.spent)}
+
+
+ {f.insights.map((ins, i) => ( +
+
+
{ins.title}

{ins.body}

+
+ ))} +
+
+
+
+ +
+
+
+
+
자동 분류된 지출아리가 거래내역을 카테고리로 정리했어요
+
+
+ {f.cats.map((c) => ( +
+
+
+
{c.name}{c.over && 예산 빠름}
+
+
+
{won(c.amt)}{c.pct}%
+
+ ))} +
+
+ +
+
+
+
구독 결제 예정결제일 전에 미리 알려드려요
+ 알림 켜짐 +
+
+ {f.subs.map((s) => { + const isPaused = paused[s.name]; + return ( +
+
+
{s.name}{s.note}{isPaused ? " · 일시정지됨" : ""}
+
+
{s.date}
+
3 ? " far" : "")}>{isPaused ? "정지" : s.inDays + "일 뒤"}
+
+
{won(s.amt)}
+ +
+ ); + })} +
+
+
+
+ + ); +} + +/* ===================== 지식 모듈 (제2의 뇌) ===================== */ +function KnowledgeModule({ toast }) { + const k = L.knowledge; + const [q, setQ] = useState(""); + const [answer, setAnswer] = useState(null); + const ask = (text) => { + if (!text.trim()) return; + setQ(text); + setAnswer({ + q: text, + body: "저장하신 자료를 종합하면 — 검증 가능한 작은 단계로 분해하고, 계획·실행·반성을 분리할 때 에이전트 신뢰성이 가장 크게 올라갑니다. ‘분기 회고’ 메모의 리텐션 +6%p 결과와도 연결돼요. 온보딩 자동화에 오케스트레이터-워커 패턴을 먼저 적용해보길 추천드려요.", + sources: ["AI 에이전트 설계 패턴", "분기 회고 메모", "재무 자동화 시스템"], + }); + }; + const filtered = useMemo(() => { + const s = q.trim().toLowerCase(); + if (!s || answer) return k.items; + return k.items.filter((it) => (it.title + it.excerpt + it.tags.join(" ")).toLowerCase().includes(s)); + }, [q, answer]); + + return ( + <> +
+
+
+

지식 & 학습 · 제2의 뇌

+
{k.stats.items.toLocaleString()}개 저장 · 이번 주 +{k.stats.thisWeek} · {k.stats.collections}개 컬렉션
+
+
toast("새 항목을 스크랩할 준비가 됐어요", "violet")}>스크랩
+
+ +
+
+ + { setQ(e.target.value); if (answer) setAnswer(null); }} + onKeyDown={(e) => e.key === "Enter" && ask(q)} + placeholder="저장한 모든 것에 자연어로 물어보세요…" + /> + +
+
+ {k.suggested.map((s, i) => ( + + ))} +
+
+ + {answer && ( +
+
+
+
"{answer.q}"
+
+
+ {answer.sources.map((s, i) => {s})} +
+
+
+ )} + +
+
+
+

{answer ? "관련 자료" : q.trim() ? "검색 결과" : "최근 스크랩"}

+
자동 태깅됨
+
+
+ {filtered.map((it) => ( +
+
+
+
+ {it.title} +
{it.src}{it.time}
+
+ {({ article: "아티클", note: "메모", idea: "아이디어", highlight: "하이라이트" })[it.type]} +
+

{it.excerpt}

+
+
+

아리 — " + it.ai }} /> +

+
+ {it.tags.map((t, i) => {t})} + + + +
+
+ ))} + {filtered.length === 0 &&
‘{q}’에 해당하는 자료가 없어요. 다르게 물어보거나 새로 스크랩해 보세요.
} +
+
+ +
+
+
+
+
컬렉션주제별 자동 묶음
+
+
+ {k.collections.map((c) => ( + + ))} +
+
+
+
+ + ); +} + +/* ===================== 오늘 (통합) ===================== */ +function TodayModule({ go, habits, toast }) { + const h = L.health, f = L.finance, k = L.knowledge; + const nextSub = f.subs[0]; + const pendingHabits = habits.filter((x) => x.done < x.total); + return ( + <> +
+
+
+
아리 라이프 코치 · 오늘 아침
+

오늘의 컨디션은 좋아요. 활동만 조금 더 챙기면 완벽해요 ☀️

+

+ 어젯밤 7시간 12분 푹 주무셨고 수면 점수는 86점이에요. 다만 어제 물 섭취가 절반에 그쳤으니 오후에 리마인더를 넣어뒀어요. + 이번 달 소비는 지난달보다 8% 적고 순항 중이지만, 이틀 뒤 Netflix·Spotify 결제가 함께 빠져나가요. + 저장하신 ‘AI 에이전트 설계’ 글은 오후 미팅과 연결돼 요약해뒀어요. +

+
+
+ +
+ {/* 건강 스냅샷 */} +
+
go("health")}> +
+
+
건강 & 웰니스활동·수면·습관
+ +
+
{h.rings.map((r) => )}
+
물 마시기 5/8잔 · 오후 3시 리마인더가 예정돼 있어요.
+
+
+ + {/* 금융 스냅샷 */} +
+
go("finance")}> +
+
+
금융 & 소비6월 예산
+ +
+
+ {won(f.budget.spent)} + / {won(f.budget.limit)} +
+ 지난달보다 8% 절약 +
+
+
+
{nextSub.name}다음 결제
+
{nextSub.date}
{nextSub.inDays}일 뒤
+
{won(nextSub.amt)}
+
+
+
+ + {/* 지식 스냅샷 */} +
+
go("knowledge")}> +
+
+
지식 & 학습제2의 뇌
+ +
+
{ e.stopPropagation(); go("knowledge"); }}> + + 저장한 것에 물어보기… +
+
+ {k.items.slice(0, 2).map((it) => ( +
+
+
+ {it.title} + {it.src} · {it.time} +
+
+ ))} +
+
+
+ + {/* 오늘의 리마인더 */} +
+
+
+
+
오늘의 리마인더아리가 챙기고 있는 것들
+
+
+ {pendingHabits.map((hb) => ( +
+
+
+
{hb.title}

{hb.at} · {hb.done}/{hb.total} 완료

+
+
+ ))} +
+
+
+
구독 결제 임박

{nextSub.date} {nextSub.name} {won(nextSub.amt)} 결제 예정

+
+
+
+
+
+
+ + ); +} + +/* ===================== 앱 ===================== */ +function App() { + const [theme, setTheme] = useState(() => load("theme", "light")); + const [mod, setMod] = useState(() => load("mod", "today")); + const [habits, setHabits] = useState(() => load("habits", L.health.habits)); + const [paused, setPaused] = useState(() => load("paused", {})); + const [toasts, setToasts] = useState([]); + const shellRef = useRef(null); + const tid = useRef(0); + + useEffect(() => { document.documentElement.setAttribute("data-theme", theme); save("theme", theme); }, [theme]); + useEffect(() => save("mod", mod), [mod]); + useEffect(() => save("habits", habits), [habits]); + useEffect(() => save("paused", paused), [paused]); + useEffect(() => { const el = shellRef.current; const id = requestAnimationFrame(() => el && el.classList.add("entered")); return () => cancelAnimationFrame(id); }, []); + + const toast = (msg, tone) => { + const id = ++tid.current; + setToasts((t) => [...t, { id, msg, tone }]); + setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 2500); + }; + const onHabit = (id) => { + setHabits((hs) => hs.map((h) => h.id === id && h.done < h.total ? { ...h, done: h.done + 1 } : h)); + toast("습관을 기록했어요", "green"); + }; + const onPause = (name) => { + setPaused((p) => ({ ...p, [name]: !p[name] })); + toast(paused[name] ? "구독을 다시 활성화했어요" : "구독을 일시정지했어요", paused[name] ? "green" : "coral"); + }; + + return ( +
+ {/* 상단바 */} +
+
+
+
아리AI LIFE OS
+
+ +
+ + + +
지
+
+
+ +
+ {/* 좌측: 모듈 + 연결 소스 */} + + + {/* 본문 */} +
+ {mod === "today" && } + {mod === "health" && } + {mod === "finance" && } + {mod === "knowledge" && } +
+
+ +
+ {toasts.map((t) => ( +
+ + {t.msg} +
+ ))} +
+
+ ); +} + +ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/design-reference/assets/mail-data.js b/design-reference/assets/mail-data.js new file mode 100644 index 0000000..83fcdbc --- /dev/null +++ b/design-reference/assets/mail-data.js @@ -0,0 +1,294 @@ +/* 아리 — 메일 데이터 (여러 계정 통합 · AI 분석 포함) */ +(function () { + /* ---------- 계정 (여러 어카운트 수입) ---------- */ + const accounts = [ + { id: "work", name: "회사", email: "jiwoo@lumi.co", tone: "coral", kind: "Lumi Workspace", unread: 3 }, + { id: "personal", name: "개인", email: "jiwoo.kim@gmail.com", tone: "blue", kind: "Google", unread: 2 }, + { id: "side", name: "사이드", email: "jiwoo@hey.com", tone: "violet", kind: "HEY", unread: 1 }, + ]; + const acc = Object.fromEntries(accounts.map((a) => [a.id, a])); + + /* ---------- 폴더 / 스마트뷰 ---------- */ + const folders = [ + { id: "inbox", name: "받은편지함", icon: "inbox" }, + { id: "starred", name: "별표", icon: "star" }, + { id: "sent", name: "보낸편지함", icon: "send" }, + { id: "drafts", name: "임시보관함", icon: "file" }, + { id: "archive", name: "보관함", icon: "archive" }, + ]; + + /* ---------- 사람 ---------- */ + const people = { + hyunwoo: { name: "현우", email: "hyunwoo@lumi.co", initial: "현", c: "var(--violet)", role: "프로덕트 디자이너" }, + daepyo: { name: "서지원 대표", email: "jiwon@lumi.co", initial: "서", c: "var(--coral)", role: "대표이사" }, + minseo: { name: "민서", email: "minseo@lumi.co", initial: "민", c: "var(--green)", role: "팀 리드" }, + sua: { name: "수아", email: "sua@lumi.co", initial: "수", c: "oklch(0.66 0.13 200)", role: "UX 리서처" }, + jaeho: { name: "재호", email: "jaeho@lumi.co", initial: "재", c: "var(--amber)", role: "데이터 분석" }, + bank: { name: "우리카드", email: "no-reply@wooricard.com", initial: "우", c: "oklch(0.6 0.12 250)", role: "카드 명세" }, + book: { name: "수요 독서모임", email: "club@bookwed.kr", initial: "독", c: "oklch(0.62 0.1 150)", role: "모임 운영" }, + stripe: { name: "Stripe", email: "receipts@stripe.com", initial: "S", c: "oklch(0.6 0.15 285)", role: "결제 영수증" }, + recruit: { name: "박서연 (지원자)", email: "seoyeon.p@gmail.com", initial: "박", c: "oklch(0.64 0.12 30)", role: "프로덕트 디자이너 지원" }, + me: { name: "지우", email: "jiwoo@lumi.co", initial: "지", c: "var(--blue)", me: true }, + }; + + const P = (s) => s.split("\n").map((t) => t.trim()).filter(Boolean); + + /* ---------- 메일 (최신순) ---------- + ai: 아리가 새 메일을 분석해 준비해 둔 작업 묶음 + summary : 한두 줄 요약 + priority : 높음 | 보통 | 낮음 + category : 분류 라벨 + tasks : [{ text, project, due, prio }] + events : [{ title, date, day, time, dur }] + replies : [{ tone, preview, body }] + file : { project, reason } 프로젝트 정리 제안 + */ + const emails = [ + { + id: "m1", account: "work", from: "hyunwoo", to: "나", + subject: "온보딩 시안 v3 — 금요일까지 피드백 부탁드려요", + time: "8분 전", date: "오늘 09:12", read: false, starred: false, hasAttach: true, + attachments: [{ name: "온보딩_v3.fig", size: "4.2MB" }, { name: "변경사항_요약.pdf", size: "320KB" }], + labels: ["디자인"], + preview: "지우님, 리뷰에서 나온 코멘트 반영해서 v3 정리했어요. 환영·권한요청·음성안내 3개 화면이 바뀌었고…", + body: P(`지우님, 안녕하세요. 현우입니다. + + 지난 디자인 리뷰에서 주신 코멘트 반영해서 온보딩 시안 v3 정리했습니다. 이번에 바뀐 건 크게 세 화면이에요. + + 1) 환영 화면 — 카피 톤을 조금 더 친근하게 바꿨고, 일러스트는 교체 요청 넣어둔 상태입니다. + 2) 권한 요청 화면 — 요청 순서를 재배치했어요. (알림 → 위치 → 연락처) + 3) 음성 안내 화면 — 신규 추가했습니다. 한 번 봐주시면 좋겠어요. + + 금요일(6/13)까지 피드백 주시면 다음 스프린트에 바로 반영하겠습니다. 피그마 링크랑 변경사항 요약본 첨부했어요. 감사합니다!`), + ai: { + summary: "온보딩 시안 v3 도착 — 3개 화면(환영·권한요청·음성안내) 수정. 금요일 6/13까지 피드백 요청.", + priority: "높음", category: "검토 요청", + tasks: [ + { text: "온보딩 시안 v3 피드백 정리", project: "온보딩 리디자인 · 와이어프레임", due: "6/13", prio: "높음" }, + { text: "음성 안내 화면 신규 검토", project: "온보딩 리디자인 · 음성 인터페이스", due: "6/12", prio: "보통" }, + ], + events: [], + replies: [ + { tone: "수락 · 일정 약속", preview: "금요일까지 피드백 드릴게요", body: "현우님, 잘 받았어요! 오늘 중으로 살펴보고 금요일 오전까지 화면별로 코멘트 정리해서 드릴게요. 음성 안내 화면은 특히 꼼꼼히 볼게요. 수고하셨어요 👏" }, + { tone: "질문 추가", preview: "권한 요청 순서 근거가 궁금해요", body: "현우님, 시안 잘 봤어요. 권한 요청 순서를 알림 → 위치 → 연락처로 두신 근거가 따로 있을까요? 리서치 결과랑 연결해서 보면 좋을 것 같아 여쭤봐요." }, + { tone: "간단 확인", preview: "확인했고 회신 예정", body: "확인했습니다, 현우님! 금요일까지 피드백 드릴게요." }, + ], + file: { project: "온보딩 리디자인", reason: "‘와이어프레임’ 작업과 직접 연결돼요" }, + }, + }, + { + id: "m2", account: "work", from: "daepyo", to: "나, 재무팀", + subject: "[중요] 오늘 14시 분기 전략 미팅 — 리포트 초안 공유 부탁", + time: "42분 전", date: "오늘 08:38", read: false, starred: true, hasAttach: false, + labels: ["경영진"], + preview: "지우님, 오늘 오후 미팅 전에 분기 리포트 초안 미리 돌려볼 수 있을까요? 특히 매출·예산 섹션 위주로…", + body: P(`지우님, + + 오늘 오후 2시 분기 전략 미팅 잘 부탁드려요. 미팅 전에 분기 리포트 초안을 미리 공유해 주시면 다같이 보고 들어갈 수 있을 것 같아요. + + 특히 매출 섹션과 예산 재배분 안건은 의사결정이 필요한 부분이라, 숫자가 확정된 버전으로 부탁드립니다. 리텐션 지표는 지난번 버전으로도 충분해요. + + 미팅은 60분, 대회의실입니다. 늦지 않게 시작하면 좋겠어요. 고생 많으십니다. + + 서지원 드림`), + ai: { + summary: "오늘 14시 분기 전략 미팅 전, 매출·예산 섹션 확정본 리포트 공유 요청. 의사결정 안건 포함.", + priority: "높음", category: "마감 임박", + tasks: [ + { text: "분기 리포트 매출 섹션 숫자 확정", project: "경영 전략 · 분기 리포트", due: "6/10", prio: "높음" }, + { text: "예산 재배분 안건 1p 요약", project: "경영 전략 · 예산 관리", due: "6/10", prio: "높음" }, + { text: "미팅 시작 전 리포트 메일로 공유", project: "경영 전략 · 분기 리포트", due: "6/10", prio: "높음" }, + ], + events: [ + { title: "분기 전략 미팅", date: "6/10", day: "오늘", time: "14:00", dur: "60분", place: "대회의실" }, + ], + replies: [ + { tone: "확정 회신", preview: "13시까지 확정본 공유할게요", body: "대표님, 확인했습니다. 매출·예산 섹션 숫자 확정해서 오후 1시까지 미팅 참석자분들께 공유드리겠습니다. 예산 재배분 안건은 1페이지 요약 따로 붙여둘게요." }, + { tone: "일부 지연 양해", preview: "예산 섹션만 미팅 중 공유", body: "대표님, 매출 섹션은 1시까지 확정해서 보내드리겠습니다. 예산 재배분 안건은 재무팀 최종 확인이 오전 중 마무리되는 대로, 늦어도 미팅 직전에 공유드릴게요." }, + ], + file: { project: "경영 전략", reason: "‘분기 리포트’ 마감 작업과 연결돼요" }, + }, + }, + { + id: "m3", account: "work", from: "minseo", to: "나", + subject: "오늘 1:1 안건 미리 정리해뒀어요", + time: "2시간 전", date: "오늘 07:50", read: false, starred: false, hasAttach: false, + labels: ["팀"], + preview: "지우님, 오후 1:1 때 이야기 나누고 싶은 거 두 가지 정리했어요. 1) 하반기 성장 로드맵 2) 업무 분장…", + body: P(`지우님, 좋은 아침이에요! + + 오늘 오후 1:1 때 이야기 나누고 싶은 거 미리 정리해봤어요. + + 1) 하반기 성장 로드맵 — 제 쪽 OKR이랑 어떻게 맞물리면 좋을지 같이 보면 좋겠어요. + 2) 업무 분장 — 리서치 보드 업데이트랑 회고 문서 배포, 둘 중 하나는 재호님께 넘기는 게 어떨까요? + + 편하게 오시면 됩니다. 이따 봬요 :)`), + ai: { + summary: "오늘 1:1(16:30) 안건 2건: 하반기 성장 로드맵, 업무 분장(재호님께 일부 위임 제안).", + priority: "보통", category: "준비 필요", + tasks: [ + { text: "1:1 전 하반기 로드맵 초안 훑어보기", project: "팀 운영", due: "6/10", prio: "보통" }, + ], + events: [ + { title: "1:1 — 민서님", date: "6/10", day: "오늘", time: "16:30", dur: "30분", place: "포커스룸 B" }, + ], + replies: [ + { tone: "준비해서 갈게요", preview: "로드맵 보고 갈게요", body: "민서님, 정리 고마워요! 1:1 전에 하반기 로드맵 초안 한 번 보고 갈게요. 업무 분장은 재호님 일정도 같이 보면서 정리하면 좋겠어요. 이따 봬요 :)" }, + ], + file: { project: "팀 운영", reason: "1:1·업무 분장 관련 항목이에요" }, + }, + }, + { + id: "m4", account: "personal", from: "bank", to: "나", + subject: "[우리카드] 6월 결제 예정 명세서 안내", + time: "5시간 전", date: "오늘 04:30", read: false, starred: false, hasAttach: false, + labels: ["금융"], + preview: "고객님의 6월 결제 예정 금액은 1,284,500원입니다. 결제일은 6월 25일이며, 등록된 카드…", + body: P(`안녕하세요, 우리카드입니다. + + 고객님의 6월 결제 예정 금액을 안내드립니다. + + · 결제 예정 금액: 1,284,500원 + · 결제 예정일: 2026년 6월 25일 + · 결제 계좌: 우리은행 (***-12) + + ※ 등록하신 결제 카드의 유효기간이 6월 말 만료될 예정입니다. 자동 결제 중단을 막으려면 카드 정보를 갱신해 주세요. + + 본 메일은 발신 전용입니다.`), + ai: { + summary: "6월 결제 예정 1,284,500원(6/25). 결제 카드 유효기간 6월 말 만료 → 갱신 필요.", + priority: "보통", category: "금융 · 액션 필요", + tasks: [ + { text: "구독 결제 카드 정보 갱신 (말일 전)", project: "개인", due: "6/28", prio: "보통" }, + ], + events: [ + { title: "카드 결제일 (1,284,500원)", date: "6/25", day: "수", time: "종일", dur: "", place: "" }, + ], + replies: [], + file: { project: "개인", reason: "결제·구독 관리 항목이에요" }, + }, + }, + { + id: "m5", account: "side", from: "book", to: "나", + subject: "이번 주 수요 독서모임 — 6/12 저녁 7시 ‘몰입’ 2부", + time: "어제", date: "6월 9일 18:20", read: false, starred: false, hasAttach: false, + labels: ["모임"], + preview: "이번 주는 ‘몰입(Deep Work)’ 2부를 다룹니다. 장소는 합정 모임공간, 저녁 7시예요. 발제는…", + body: P(`안녕하세요, 수요 독서모임입니다. + + 이번 주 모임 안내드려요. + + · 일시: 6월 12일(목) 저녁 7시 + · 장소: 합정 모임공간 ‘책상’ + · 범위: ‘몰입(Deep Work)’ 2부 (128~210쪽) + · 발제: 지우님 (2장 담당) + + 지우님이 이번 주 2장 발제 맡아주셨어요. 부담 갖지 마시고 인상 깊었던 부분 위주로 준비해오시면 됩니다. 곧 뵐게요!`), + ai: { + summary: "6/12(목) 19시 독서모임 ‘몰입’ 2부. 지우님이 2장 발제 담당.", + priority: "보통", category: "개인 일정", + tasks: [ + { text: "‘몰입’ 2장 발제 준비 (128~210쪽)", project: "개인 · 성장", due: "6/12", prio: "보통" }, + ], + events: [ + { title: "수요 독서모임 (2장 발제)", date: "6/12", day: "목", time: "19:00", dur: "120분", place: "합정 ‘책상’" }, + ], + replies: [ + { tone: "참석 확정", preview: "발제 준비해서 갈게요", body: "안녕하세요! 6/12 참석합니다. 2장 발제 준비해서 갈게요. 인상 깊었던 부분 위주로 정리해볼게요. 곧 뵐게요 :)" }, + ], + file: { project: "개인", reason: "독서·성장 관련 일정이에요" }, + }, + }, + { + id: "m6", account: "work", from: "recruit", to: "나", + subject: "프로덕트 디자이너 지원합니다 — 박서연 포트폴리오", + time: "어제", date: "6월 9일 14:05", read: true, starred: false, hasAttach: true, + attachments: [{ name: "박서연_포트폴리오.pdf", size: "12MB" }], + labels: ["채용"], + preview: "안녕하세요, 프로덕트 디자이너 직무에 지원하는 박서연입니다. 5년 차 디자이너로 모바일 앱…", + body: P(`안녕하세요, 채용 담당자님. + + 프로덕트 디자이너 직무에 지원하는 박서연입니다. 5년 차 디자이너로, 핀테크와 헬스케어 분야 모바일 앱 디자인을 주로 해왔습니다. + + 특히 온보딩 플로우 개선으로 전환율을 23% 높인 경험이 있어, Lumi의 온보딩 리디자인 방향성과 잘 맞을 것 같아 지원하게 되었습니다. + + 포트폴리오 첨부드립니다. 검토 후 연락 주시면 감사하겠습니다.`), + ai: { + summary: "프로덕트 디자이너 지원(5년 차, 온보딩 전환율 +23% 경험). 포트폴리오 첨부.", + priority: "보통", category: "채용", + tasks: [ + { text: "박서연 포트폴리오 검토 & 1차 의견", project: "팀 운영 · 채용 & 온보딩", due: "6/13", prio: "보통" }, + ], + events: [], + replies: [ + { tone: "검토 후 회신 약속", preview: "검토 후 다음 주 연락드릴게요", body: "박서연님, 지원해 주셔서 감사합니다. 포트폴리오 잘 받았습니다. 내부 검토 후 다음 주 초 연락드리겠습니다. 좋은 하루 보내세요!" }, + ], + file: { project: "팀 운영", reason: "‘채용 & 온보딩’ 작업과 연결돼요" }, + }, + }, + { + id: "m7", account: "personal", from: "stripe", to: "나", + subject: "Your receipt from ChatGPT Plus — $20.00", + time: "2일 전", date: "6월 8일 09:00", read: true, starred: false, hasAttach: false, + labels: ["영수증"], + preview: "Receipt #2451-0098 · ChatGPT Plus Subscription · $20.00 paid on Jun 8, 2026…", + body: P(`Receipt from OpenAI, LLC + + Amount paid: $20.00 + Date: June 8, 2026 + Subscription: ChatGPT Plus (monthly) + Payment method: Visa ···· 4242 + + Thank you for your business.`), + ai: { + summary: "ChatGPT Plus 월 구독 $20 결제 영수증. 별도 조치 불필요.", + priority: "낮음", category: "영수증", + tasks: [], + events: [], + replies: [], + file: { project: "개인", reason: "구독 영수증으로 보관하면 좋아요" }, + }, + }, + { + id: "m8", account: "work", from: "jaeho", to: "나, 수아", + subject: "리텐션 코호트 데이터 v2 — 이탈 사유 태깅 완료", + time: "2일 전", date: "6월 8일 17:42", read: true, starred: false, hasAttach: true, + attachments: [{ name: "retention_cohort_v2.xlsx", size: "880KB" }], + labels: ["데이터"], + preview: "지우님, 수아님. 이탈 사유 태깅까지 끝내서 코호트 데이터 v2 올려요. 주차별 잔존율이랑…", + body: P(`지우님, 수아님. + + 이탈 사유 태깅까지 마무리해서 리텐션 코호트 데이터 v2 공유드려요. 주차별 잔존율, 코호트별 이탈 사유 분포까지 정리돼 있습니다. + + 분기 리포트 리텐션 섹션에 바로 쓰실 수 있게 시각화용 표도 시트 2번에 넣어뒀어요. 추가로 필요한 분석 있으면 말씀 주세요.`), + ai: { + summary: "리텐션 코호트 v2 완료(이탈 사유 태깅 포함). 리포트용 시각화 표 포함.", + priority: "보통", category: "데이터 수신", + tasks: [ + { text: "코호트 v2 표 → 분기 리포트 리텐션 섹션 반영", project: "경영 전략 · 분기 리포트", due: "6/10", prio: "보통" }, + ], + events: [], + replies: [ + { tone: "감사 + 확인", preview: "리포트에 반영할게요", body: "재호님, 수고하셨어요! 시각화 표까지 챙겨주셔서 감사해요. 리텐션 섹션에 바로 반영할게요. 추가 분석 필요하면 말씀드릴게요." }, + ], + file: { project: "경영 전략", reason: "‘분기 리포트’ 데이터 소스예요" }, + }, + }, + ]; + + /* 보낸 편지함 샘플 (간단) */ + const sent = [ + { id: "s1", account: "work", to: "현우", toKey: "hyunwoo", subject: "온보딩 리뷰 일정 공유", time: "어제", date: "6월 9일 11:00", + preview: "현우님, 다음 리뷰는 목요일 오전으로 잡았어요…", read: true }, + { id: "s2", account: "work", to: "민서", toKey: "minseo", subject: "회고 문서 잘 봤어요", time: "3일 전", date: "6월 7일 16:20", + preview: "민서님, 회고 문서 정리 깔끔하네요. 액션 아이템만…", read: true }, + ]; + + /* 임시보관 샘플 */ + const drafts = [ + { id: "d1", account: "work", to: "재무팀", subject: "예산 재배분 안건 정리 (작성 중)", time: "1시간 전", + preview: "예산 재배분 관련해서 세 가지 시나리오를…", read: true, draft: true }, + ]; + + window.MailData = { accounts, acc, folders, people, emails, sent, drafts }; +})(); diff --git a/design-reference/assets/mail.css b/design-reference/assets/mail.css new file mode 100644 index 0000000..fb4ecc7 --- /dev/null +++ b/design-reference/assets/mail.css @@ -0,0 +1,722 @@ +/* =========================================================== + 아리 — 메일(Mail) · 클린 화이트 글래스 테마 + 상단바 = 메인 메뉴 (공유) + 좌측 사이드바 = 계정/폴더/라벨 · 가운데 = 메일 목록 · 우측 = 리딩 + 아리 분석 + AI 비서가 새 메일을 읽고 할 일·일정·답장·정리를 미리 준비해 둔다. + =========================================================== */ + +* { box-sizing: border-box; } +html, body { margin: 0; padding: 0; } + +:root { + --bg-top: #f6efe7; + --bg-mid: #eef0f1; + --bg-bot: #e9ebed; + --card: #ffffff; + --card-2: #f4f5f6; + --card-3: #eef0f1; + --ink: #211f1c; + --ink-2: #514d47; + --muted: #8d8a85; + --faint: #b6b3ad; + --line: rgba(33, 31, 28, 0.07); + --line-2: rgba(33, 31, 28, 0.13); + --fill: #29241f; + --fill-soft: #36302a; + --on-fill: #f4efe6; + --blue: #4f72e0; + --coral: #df7256; + --green: #4e9b66; + --violet: #8b6fd4; + --amber: #e0a23c; + --radius: 22px; + --radius-sm: 14px; + --shadow: 0 10px 30px -14px rgba(30, 26, 22, 0.22), 0 2px 6px -2px rgba(30, 26, 22, 0.06); + --shadow-sm: 0 4px 14px -8px rgba(30, 26, 22, 0.18); + --glass: rgba(255, 255, 255, 0.4); + --glass-2: rgba(255, 255, 255, 0.26); + --glass-brd: rgba(255, 255, 255, 0.7); + --glass-ink: rgba(26, 22, 18, 0.46); + --blur: blur(26px) saturate(190%); + --glass-hi: inset 0 1px 0 rgba(255, 255, 255, 0.75); + --font-disp: "Onest", "Pretendard", system-ui, sans-serif; + --font-ui: "Pretendard", "Onest", system-ui, sans-serif; + --font-mono: "DM Mono", ui-monospace, monospace; +} +[data-theme="dark"] { + --bg-top: #201d1a; + --bg-mid: #1a1816; + --bg-bot: #161413; + --card: #2c2925; + --card-2: #353029; + --card-3: #3b352d; + --ink: #f3eee6; + --ink-2: #c3bdb4; + --muted: #948e85; + --faint: #6f6a62; + --line: rgba(244, 239, 230, 0.09); + --line-2: rgba(244, 239, 230, 0.16); + --fill: #f4efe6; + --fill-soft: #e7e0d4; + --on-fill: #211f1c; + --shadow: 0 14px 36px -16px rgba(0, 0, 0, 0.7), 0 2px 8px -3px rgba(0, 0, 0, 0.4); + --shadow-sm: 0 6px 16px -10px rgba(0, 0, 0, 0.6); + --glass: rgba(48, 43, 38, 0.4); + --glass-2: rgba(70, 63, 55, 0.28); + --glass-brd: rgba(255, 255, 255, 0.1); + --glass-ink: rgba(16, 13, 11, 0.42); + --glass-hi: inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +body { + font-family: var(--font-ui); + color: var(--ink); + background: linear-gradient(178deg, var(--bg-top) 0%, var(--bg-mid) 20%, var(--bg-bot) 100%); + background-attachment: fixed; + min-height: 100vh; + -webkit-font-smoothing: antialiased; + letter-spacing: -0.011em; + word-break: keep-all; +} +button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; } +input, textarea { font-family: inherit; } +:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; border-radius: 6px; } +.ic { width: 1em; height: 1em; flex-shrink: 0; display: block; } +.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; } + +.mpage { max-width: 1680px; margin: 0 auto; padding: 0 26px 26px; } + +/* ====================== 상단바 — 메인 메뉴 (공유) ====================== */ +.topbar { + position: sticky; top: 0; z-index: 40; + display: flex; align-items: center; gap: 24px; + height: 70px; margin: 0 -26px; padding: 0 30px; + -webkit-backdrop-filter: blur(14px) saturate(150%); + backdrop-filter: blur(14px) saturate(150%); +} +.brand { display: flex; align-items: center; gap: 11px; flex-shrink: 0; } +.brand-mark { + width: 40px; height: 40px; border-radius: 12px; + background: var(--fill); color: var(--coral); + display: grid; place-items: center; position: relative; +} +.brand-mark .ic { width: 21px; height: 21px; } +.brand-mark::after { + content: ""; position: absolute; top: -2px; right: -2px; + width: 9px; height: 9px; border-radius: 50%; + background: var(--coral); border: 2px solid var(--bg-top); +} +.brand-tag { line-height: 1.15; } +.brand-tag b { display: block; font-size: 15px; font-weight: 700; letter-spacing: -0.02em; white-space: nowrap; } +.brand-tag span { + font-size: 9.5px; font-weight: 700; letter-spacing: 0.08em; + color: var(--muted); text-transform: uppercase; white-space: nowrap; +} +.mainnav { display: flex; align-items: center; gap: 2px; min-width: 0; overflow-x: auto; scrollbar-width: none; } +.mainnav::-webkit-scrollbar { display: none; } +.mainnav a { + text-decoration: none; cursor: pointer; + display: inline-flex; align-items: center; gap: 8px; + font-size: 14px; font-weight: 500; color: var(--ink-2); + padding: 9px 16px; border-radius: 999px; white-space: nowrap; + transition: background 0.16s, color 0.16s, box-shadow 0.16s; +} +.mainnav a .ic { width: 16px; height: 16px; color: var(--muted); transition: color 0.16s; } +.mainnav a:hover { color: var(--ink); } +.mainnav a:hover .ic { color: var(--ink-2); } +.mainnav a.active { + background: var(--glass); + -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); + color: var(--ink); font-weight: 600; box-shadow: var(--shadow-sm); + border: 1px solid var(--glass-brd); +} +.mainnav a.active .ic { color: var(--ink); } +.mainnav a .badge { + font-size: 11px; font-weight: 700; background: var(--coral); color: #fff; + border-radius: 999px; padding: 0 6px; min-width: 18px; text-align: center; +} +.top-actions { margin-left: auto; display: flex; align-items: center; gap: 9px; flex-shrink: 0; } +.t-btn { + width: 40px; height: 40px; border-radius: 50%; + display: grid; place-items: center; color: var(--ink-2); + background: var(--glass); + -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); + border: 1px solid var(--glass-brd); box-shadow: var(--shadow-sm); + position: relative; transition: transform 0.12s, color 0.16s; +} +.t-btn .ic { width: 18px; height: 18px; } +.t-btn:hover { color: var(--ink); transform: translateY(-1px); } +.t-btn.dot::after { + content: ""; position: absolute; top: 9px; right: 10px; + width: 7px; height: 7px; border-radius: 50%; + background: var(--coral); border: 2px solid var(--card); +} +.t-ava { + width: 40px; height: 40px; border-radius: 50%; + background: linear-gradient(135deg, var(--coral), var(--violet)); + color: #fff; display: grid; place-items: center; + font-weight: 700; font-size: 15px; box-shadow: var(--shadow-sm); cursor: pointer; +} + +/* ====================== 본문 레이아웃 ====================== */ +.mwork { + display: grid; + grid-template-columns: 248px 384px minmax(0, 1fr); + gap: 16px; align-items: start; padding-top: 12px; + height: calc(100vh - 96px); +} + +/* -------- 사이드바: 계정 + 폴더 + 라벨 -------- */ +.subnav { + height: 100%; + background: var(--glass); + -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); + border: 1px solid var(--glass-brd); + border-radius: var(--radius); + box-shadow: var(--shadow), var(--glass-hi); + padding: 14px 12px 12px; + display: flex; flex-direction: column; +} +.compose-btn { + display: flex; align-items: center; justify-content: center; gap: 8px; + width: 100%; padding: 12px; border-radius: 13px; white-space: nowrap; + background: var(--fill); color: var(--on-fill); + font-size: 14px; font-weight: 700; box-shadow: var(--shadow-sm); + margin-bottom: 6px; transition: filter 0.14s, transform 0.12s; +} +.compose-btn .ic { width: 17px; height: 17px; } +.compose-btn:hover { transform: translateY(-1px); filter: brightness(1.06); } + +.sn-scroll { overflow-y: auto; flex: 1; min-height: 0; margin: 0 -4px; padding: 4px; scrollbar-width: thin; } +.sn-scroll::-webkit-scrollbar { width: 6px; } +.sn-scroll::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 99px; } + +/* 아리 정리 — 스마트뷰(강조) */ +.sn-smart { + display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; + padding: 11px 12px; border-radius: 13px; margin-bottom: 8px; white-space: nowrap; + background: + radial-gradient(120% 140% at 0% 0%, color-mix(in oklab, var(--coral) 26%, transparent), transparent 60%), + var(--glass-2); + border: 1px solid color-mix(in oklab, var(--coral) 30%, var(--glass-brd)); + color: var(--ink); transition: transform 0.12s, box-shadow 0.14s; +} +.sn-smart:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); } +.sn-smart.on { box-shadow: var(--shadow-sm), inset 0 0 0 1.5px color-mix(in oklab, var(--coral) 55%, transparent); } +.sn-smart .sp-ico { + width: 30px; height: 30px; border-radius: 9px; flex-shrink: 0; + display: grid; place-items: center; color: #fff; background: var(--coral); + box-shadow: 0 5px 12px -5px var(--coral); +} +.sn-smart .sp-ico .ic { width: 16px; height: 16px; } +.sn-smart .sp-txt { min-width: 0; line-height: 1.25; } +.sn-smart .sp-txt b { display: block; font-size: 13.5px; font-weight: 700; } +.sn-smart .sp-txt span { font-size: 11px; color: var(--ink-2); } +.sn-smart .sp-cnt { + margin-left: auto; flex-shrink: 0; font-size: 11px; font-weight: 800; + background: var(--coral); color: #fff; border-radius: 999px; + padding: 2px 8px; font-variant-numeric: tabular-nums; +} + +.sn-label { + font-size: 10.5px; font-weight: 700; letter-spacing: 0.08em; + text-transform: uppercase; color: var(--faint); + padding: 14px 10px 7px; display: flex; align-items: center; +} +.sn-label .add { + margin-left: auto; width: 20px; height: 20px; border-radius: 6px; + display: grid; place-items: center; color: var(--muted); transition: background 0.13s, color 0.13s; +} +.sn-label .add .ic { width: 13px; height: 13px; } +.sn-label .add:hover { background: var(--glass-2); color: var(--ink); } + +/* 폴더 행 */ +.fl-row { + display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; + padding: 8px 10px; border-radius: 10px; white-space: nowrap; + font-size: 13.5px; font-weight: 500; color: var(--ink-2); + transition: background 0.13s, color 0.13s; +} +.fl-row .ic { width: 16px; height: 16px; color: var(--muted); flex-shrink: 0; } +.fl-row .fl-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; } +.fl-row:hover { background: var(--glass-2); color: var(--ink); } +.fl-row.on { background: var(--fill); color: var(--on-fill); font-weight: 600; } +.fl-row.on .ic { color: var(--on-fill); } +.fl-row .fl-cnt { font-size: 11.5px; font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; } +.fl-row.on .fl-cnt { color: var(--on-fill); opacity: 0.85; } + +/* 계정 행 */ +.acc-row { + display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; + padding: 8px 10px; border-radius: 10px; white-space: nowrap; + transition: background 0.13s; +} +.acc-row:hover { background: var(--glass-2); } +.acc-row.on { background: var(--glass-2); box-shadow: inset 0 0 0 1px var(--glass-brd); } +.acc-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; } +.acc-info { min-width: 0; flex: 1; line-height: 1.2; } +.acc-info b { font-size: 13px; font-weight: 600; color: var(--ink); display: block; } +.acc-info span { font-size: 10.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; } +.acc-row .fl-cnt { font-size: 11px; font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; } +.acc-add { + display: flex; align-items: center; gap: 9px; width: 100%; white-space: nowrap; + padding: 9px 10px; margin-top: 2px; border-radius: 10px; + font-size: 12.5px; font-weight: 500; color: var(--muted); + border: 1px dashed var(--line-2); transition: background 0.13s, color 0.13s, border-color 0.13s; +} +.acc-add .ic { width: 15px; height: 15px; } +.acc-add:hover { background: var(--glass-2); color: var(--ink); border-color: var(--muted); } + +.sn-foot { + margin-top: 10px; padding-top: 12px; border-top: 1px solid var(--line); + display: flex; align-items: center; gap: 10px; +} +.sn-foot .av-lg { + width: 32px; height: 32px; border-radius: 50%; + background: linear-gradient(135deg, var(--coral), var(--violet)); + color: #fff; display: grid; place-items: center; font-weight: 700; font-size: 13px; flex-shrink: 0; +} +.sn-foot .txt { min-width: 0; flex: 1; } +.sn-foot .txt b { font-size: 13px; font-weight: 600; display: block; } +.sn-foot .txt span { font-size: 11.5px; color: var(--muted); } + +/* ====================== 가운데: 메일 목록 ====================== */ +.mlist { + height: 100%; + background: var(--glass); + -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); + border: 1px solid var(--glass-brd); + border-radius: var(--radius); + box-shadow: var(--shadow), var(--glass-hi); + display: flex; flex-direction: column; overflow: hidden; +} +.ml-head { padding: 16px 18px 12px; border-bottom: 1px solid var(--line); } +.ml-titlerow { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; } +.ml-title { font-family: var(--font-disp); font-size: 20px; font-weight: 700; letter-spacing: -0.02em; } +.ml-meta { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; } +.ml-sync { + margin-left: auto; display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0; + font-size: 11.5px; font-weight: 600; color: var(--muted); + padding: 6px 11px; border-radius: 999px; background: var(--glass-2); border: 1px solid var(--glass-brd); + transition: color 0.14s; +} +.ml-sync .ic { width: 13px; height: 13px; } +.ml-sync:hover { color: var(--ink); } +.ml-sync.spin .ic { animation: spin 0.8s linear infinite; } +@keyframes spin { to { transform: rotate(360deg); } } +.ml-search { + display: flex; align-items: center; gap: 9px; + padding: 9px 14px; border-radius: 999px; + background: var(--card-2); border: 1px solid var(--line); + transition: box-shadow 0.16s, border-color 0.16s; +} +.ml-search:focus-within { border-color: color-mix(in oklab, var(--blue) 40%, var(--line)); box-shadow: 0 0 0 4px rgba(79,114,224,0.1); } +.ml-search .ic { width: 15px; height: 15px; color: var(--muted); } +.ml-search input { flex: 1; min-width: 0; border: none; background: none; outline: none; font-size: 13px; color: var(--ink); } +.ml-search input::placeholder { color: var(--faint); } + +.ml-scroll { overflow-y: auto; flex: 1; min-height: 0; scrollbar-width: thin; } +.ml-scroll::-webkit-scrollbar { width: 7px; } +.ml-scroll::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 99px; } + +.mrow { + display: flex; gap: 11px; padding: 13px 16px; + border-bottom: 1px solid var(--line); cursor: pointer; + position: relative; transition: background 0.12s; +} +.mrow:hover { background: var(--glass-2); } +.mrow.on { background: var(--card); box-shadow: inset 3px 0 0 0 var(--coral); } +.mrow.unread::before { + content: ""; position: absolute; left: 6px; top: 20px; + width: 7px; height: 7px; border-radius: 50%; background: var(--blue); +} +.mrow.unread .mr-from b { font-weight: 800; } +.mrow.unread .mr-subj { color: var(--ink); font-weight: 600; } +.mr-ava { + width: 38px; height: 38px; border-radius: 11px; flex-shrink: 0; + display: grid; place-items: center; color: #fff; font-size: 14px; font-weight: 700; + position: relative; +} +.mr-ava .acc-pip { + position: absolute; bottom: -2px; right: -2px; + width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--card); +} +.mr-body { flex: 1; min-width: 0; } +.mr-top { display: flex; align-items: baseline; gap: 8px; } +.mr-from { font-size: 13.5px; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; } +.mr-from b { font-weight: 600; color: var(--ink); } +.mr-time { font-size: 11px; color: var(--faint); white-space: nowrap; flex-shrink: 0; font-variant-numeric: tabular-nums; } +.mr-subj { font-size: 13px; color: var(--ink-2); margin-top: 3px; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.mr-prev { font-size: 12px; color: var(--muted); margin-top: 2px; line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } +.mr-tags { display: flex; align-items: center; gap: 5px; margin-top: 8px; flex-wrap: wrap; } +.ai-pill { + display: inline-flex; align-items: center; gap: 4px; + font-size: 10.5px; font-weight: 700; white-space: nowrap; + padding: 2px 8px 2px 6px; border-radius: 999px; +} +.ai-pill .ic { width: 11px; height: 11px; } +.ai-pill.task { color: var(--blue); background: color-mix(in oklab, var(--blue) 12%, transparent); } +.ai-pill.event { color: var(--coral); background: color-mix(in oklab, var(--coral) 13%, transparent); } +.ai-pill.reply { color: var(--green); background: color-mix(in oklab, var(--green) 13%, transparent); } +.ai-pill.prio { color: var(--coral); background: color-mix(in oklab, var(--coral) 13%, transparent); } +.mr-flags { display: flex; flex-direction: column; align-items: center; gap: 7px; flex-shrink: 0; padding-top: 2px; } +.mr-star { color: var(--faint); transition: color 0.13s, transform 0.12s; } +.mr-star .ic { width: 15px; height: 15px; } +.mr-star.on { color: var(--amber); } +.mr-star:hover { color: var(--amber); transform: scale(1.12); } +.mr-attach { color: var(--muted); } +.mr-attach .ic { width: 14px; height: 14px; } + +/* ====================== 우측: 리딩 + 아리 분석 ====================== */ +.reader { + height: 100%; + background: var(--glass); + -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); + border: 1px solid var(--glass-brd); + border-radius: var(--radius); + box-shadow: var(--shadow), var(--glass-hi); + display: flex; flex-direction: column; overflow: hidden; +} +.rd-toolbar { + display: flex; align-items: center; gap: 6px; + padding: 12px 16px; border-bottom: 1px solid var(--line); flex-shrink: 0; +} +.rd-tool { + display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; + padding: 8px 13px; border-radius: 10px; font-size: 12.5px; font-weight: 600; + color: var(--ink-2); transition: background 0.13s, color 0.13s; +} +.rd-tool .ic { width: 15px; height: 15px; } +.rd-tool:hover { background: var(--glass-2); color: var(--ink); } +.rd-tool.icon-only { padding: 8px; } +.rd-tool.danger:hover { color: var(--coral); } +.rd-spacer { flex: 1; } + +.rd-scroll { overflow-y: auto; flex: 1; min-height: 0; scrollbar-width: thin; } +.rd-scroll::-webkit-scrollbar { width: 8px; } +.rd-scroll::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 99px; } +.rd-inner { padding: 24px 30px 36px; } + +.rd-subject { font-family: var(--font-disp); font-size: 24px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.25; margin: 0 0 16px; } +.rd-sender { display: flex; align-items: center; gap: 12px; margin-bottom: 6px; } +.rd-sender .av-lg { + width: 44px; height: 44px; border-radius: 13px; flex-shrink: 0; + display: grid; place-items: center; color: #fff; font-size: 16px; font-weight: 700; +} +.rd-sender .who { min-width: 0; flex: 1; } +.rd-sender .who b { font-size: 14.5px; font-weight: 700; } +.rd-sender .who .em { font-size: 12px; color: var(--muted); } +.rd-sender .when { font-size: 12px; color: var(--muted); flex-shrink: 0; text-align: right; font-variant-numeric: tabular-nums; } +.rd-to { font-size: 12px; color: var(--muted); margin: 4px 0 0 56px; } +.rd-acctag { + display: inline-flex; align-items: center; gap: 6px; margin: 14px 0 0; + font-size: 11px; font-weight: 600; color: var(--ink-2); + padding: 4px 10px; border-radius: 999px; background: var(--glass-2); border: 1px solid var(--glass-brd); +} +.rd-acctag .acc-dot { width: 7px; height: 7px; } + +.rd-divider { height: 1px; background: var(--line); margin: 20px 0; } +.rd-body p { font-size: 14.5px; line-height: 1.72; color: var(--ink-2); margin: 0 0 14px; } +.rd-attachments { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 20px; } +.att-chip { + display: inline-flex; align-items: center; gap: 9px; + padding: 9px 13px; border-radius: 12px; background: var(--card-2); border: 1px solid var(--line); + transition: background 0.13s, transform 0.12s; cursor: pointer; +} +.att-chip:hover { background: var(--card-3); transform: translateY(-1px); } +.att-chip .ic { width: 17px; height: 17px; color: var(--coral); } +.att-chip .at-meta { line-height: 1.3; min-width: 0; } +.att-chip .at-meta b { font-size: 12.5px; font-weight: 600; display: block; white-space: nowrap; max-width: 180px; overflow: hidden; text-overflow: ellipsis; } +.att-chip .at-meta span { font-size: 10.5px; color: var(--muted); } + +/* ---------- 아리 분석 패널 (핵심) ---------- */ +.ari-panel { + margin-top: 26px; border-radius: 18px; overflow: hidden; + background: + radial-gradient(120% 90% at 0% 0%, color-mix(in oklab, var(--coral) 14%, transparent), transparent 55%), + var(--card); + border: 1px solid color-mix(in oklab, var(--coral) 24%, var(--line-2)); + box-shadow: var(--shadow-sm); +} +.ap-head { + display: flex; align-items: center; gap: 11px; + padding: 15px 18px; border-bottom: 1px solid var(--line); +} +.ap-avatar { + width: 32px; height: 32px; border-radius: 10px; flex-shrink: 0; + display: grid; place-items: center; color: #fff; background: var(--coral); + box-shadow: 0 5px 14px -5px var(--coral); +} +.ap-avatar .ic { width: 17px; height: 17px; } +.ap-head .ap-h { min-width: 0; flex: 1; line-height: 1.25; } +.ap-head .ap-h b { font-size: 14px; font-weight: 700; display: block; } +.ap-head .ap-h span { font-size: 11.5px; color: var(--muted); } +.ap-prio { + flex-shrink: 0; font-size: 10.5px; font-weight: 700; white-space: nowrap; + padding: 3px 9px; border-radius: 999px; +} +.ap-prio.높음 { color: #fff; background: var(--coral); } +.ap-prio.보통 { color: var(--amber); background: color-mix(in oklab, var(--amber) 16%, transparent); } +.ap-prio.낮음 { color: var(--muted); background: var(--card-2); } + +.ap-summary { padding: 15px 18px; font-size: 13.5px; line-height: 1.6; color: var(--ink); } +.ap-summary b { font-weight: 700; } + +.ap-section { padding: 4px 18px 16px; } +.ap-sec-title { + display: flex; align-items: center; gap: 7px; + font-size: 11.5px; font-weight: 700; color: var(--ink-2); + margin-bottom: 9px; +} +.ap-sec-title .ic { width: 14px; height: 14px; color: var(--muted); } +.ap-sec-title .n { margin-left: auto; font-size: 11px; color: var(--muted); font-weight: 600; } + +/* 제안 항목 카드 (할 일 / 일정) */ +.sug { + display: flex; align-items: flex-start; gap: 11px; + padding: 11px 13px; border-radius: 12px; margin-bottom: 8px; + background: var(--card-2); border: 1px solid var(--line); + transition: border-color 0.14s, background 0.14s; +} +.sug:last-child { margin-bottom: 0; } +.sug.added { background: color-mix(in oklab, var(--green) 9%, var(--card-2)); border-color: color-mix(in oklab, var(--green) 30%, var(--line)); } +.sug-ico { + width: 30px; height: 30px; border-radius: 9px; flex-shrink: 0; + display: grid; place-items: center; margin-top: 1px; +} +.sug-ico .ic { width: 15px; height: 15px; } +.sug.task .sug-ico { background: color-mix(in oklab, var(--blue) 14%, transparent); color: var(--blue); } +.sug.event .sug-ico { background: color-mix(in oklab, var(--coral) 14%, transparent); color: var(--coral); } +.sug-main { flex: 1; min-width: 0; } +.sug-text { font-size: 13px; font-weight: 600; color: var(--ink); line-height: 1.35; } +.sug-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 5px; font-size: 11px; color: var(--muted); } +.sug-meta .chip2 { display: inline-flex; align-items: center; gap: 4px; } +.sug-meta .chip2 .ic { width: 11px; height: 11px; } +.sug-meta .dotsep { width: 3px; height: 3px; border-radius: 50%; background: var(--faint); } +.sug-add { + flex-shrink: 0; align-self: center; + display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; + padding: 7px 12px; border-radius: 9px; font-size: 12px; font-weight: 700; + background: var(--fill); color: var(--on-fill); transition: filter 0.13s, transform 0.12s; +} +.sug-add .ic { width: 13px; height: 13px; } +.sug-add:hover { transform: translateY(-1px); filter: brightness(1.08); } +.sug-add.done { background: color-mix(in oklab, var(--green) 16%, transparent); color: var(--green); pointer-events: none; } + +/* 답장 추천 */ +.replies { display: flex; flex-direction: column; gap: 8px; } +.rep { + text-align: left; width: 100%; + padding: 12px 14px; border-radius: 12px; + background: var(--card-2); border: 1px solid var(--line); + transition: border-color 0.14s, background 0.14s, transform 0.12s; +} +.rep:hover { border-color: color-mix(in oklab, var(--green) 40%, var(--line)); background: var(--card); transform: translateY(-1px); } +.rep-top { display: flex; align-items: center; gap: 7px; margin-bottom: 5px; } +.rep-tone { font-size: 11.5px; font-weight: 700; color: var(--green); } +.rep-go { margin-left: auto; color: var(--muted); } +.rep-go .ic { width: 14px; height: 14px; } +.rep-prev { font-size: 12.5px; color: var(--ink-2); line-height: 1.45; } +.rep-prev::before { content: "“"; color: var(--faint); } +.rep-prev::after { content: "”"; color: var(--faint); } + +/* 프로젝트 정리 제안 */ +.ap-file { + margin: 0 18px 16px; padding: 12px 14px; border-radius: 12px; + display: flex; align-items: center; gap: 11px; + background: var(--card-2); border: 1px dashed var(--line-2); +} +.ap-file.added { border-style: solid; background: color-mix(in oklab, var(--violet) 9%, var(--card-2)); border-color: color-mix(in oklab, var(--violet) 32%, var(--line)); } +.ap-file .fi-ico { width: 30px; height: 30px; border-radius: 9px; flex-shrink: 0; display: grid; place-items: center; background: color-mix(in oklab, var(--violet) 14%, transparent); color: var(--violet); } +.ap-file .fi-ico .ic { width: 15px; height: 15px; } +.ap-file .fi-main { flex: 1; min-width: 0; line-height: 1.3; } +.ap-file .fi-main b { font-size: 12.5px; font-weight: 700; } +.ap-file .fi-main span { font-size: 11px; color: var(--muted); display: block; } +.ap-file .sug-add { background: var(--violet); } +.ap-file .sug-add.done { background: color-mix(in oklab, var(--violet) 16%, transparent); color: var(--violet); } + +/* 전체 적용 바 */ +.ap-allbar { + display: flex; align-items: center; gap: 10px; + padding: 13px 18px; border-top: 1px solid var(--line); + background: var(--card-2); +} +.ap-allbar .txt { font-size: 12px; color: var(--muted); flex: 1; min-width: 0; } +.ap-all-btn { + display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; flex-shrink: 0; + padding: 9px 15px; border-radius: 10px; font-size: 12.5px; font-weight: 700; + background: var(--fill); color: var(--on-fill); transition: filter 0.13s, transform 0.12s; +} +.ap-all-btn .ic { width: 14px; height: 14px; } +.ap-all-btn:hover { transform: translateY(-1px); filter: brightness(1.08); } +.ap-all-btn.done { background: color-mix(in oklab, var(--green) 16%, transparent); color: var(--green); pointer-events: none; } + +/* 빈 상태 */ +.rd-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 40px; } +.rd-empty .e-ico { width: 64px; height: 64px; border-radius: 18px; display: grid; place-items: center; background: var(--glass-2); border: 1px solid var(--glass-brd); color: var(--muted); margin-bottom: 18px; } +.rd-empty .e-ico .ic { width: 28px; height: 28px; } +.rd-empty h3 { font-family: var(--font-disp); font-size: 18px; font-weight: 700; margin: 0 0 6px; } +.rd-empty p { font-size: 13px; color: var(--muted); max-width: 30ch; line-height: 1.5; margin: 0; } + +/* ====================== 아리 정리 (트리아지 뷰) ====================== */ +.triage { padding: 22px 26px 30px; } +.tr-hero { + display: flex; align-items: flex-start; gap: 16px; + padding: 20px 22px; border-radius: 18px; margin-bottom: 18px; + background: + radial-gradient(90% 120% at 0% 0%, color-mix(in oklab, var(--coral) 18%, transparent), transparent 55%), + var(--card); + border: 1px solid color-mix(in oklab, var(--coral) 22%, var(--line-2)); + box-shadow: var(--shadow-sm); +} +.tr-hero .ap-avatar { width: 40px; height: 40px; border-radius: 12px; } +.tr-hero .ap-avatar .ic { width: 21px; height: 21px; } +.tr-hero .th-main { flex: 1; min-width: 0; } +.tr-hero .th-eyebrow { font-size: 12px; font-weight: 700; color: var(--coral); letter-spacing: 0.02em; } +.tr-hero h2 { font-family: var(--font-disp); font-size: 22px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.3; margin: 5px 0 0; } +.tr-hero p { font-size: 13.5px; color: var(--ink-2); line-height: 1.55; margin: 9px 0 0; max-width: 60ch; } +.tr-stats { display: flex; gap: 9px; flex-wrap: wrap; margin-top: 14px; } +.tr-stat { + display: inline-flex; align-items: center; gap: 8px; + padding: 8px 13px; border-radius: 12px; background: var(--card-2); border: 1px solid var(--line); +} +.tr-stat .ic { width: 15px; height: 15px; } +.tr-stat b { font-size: 16px; font-weight: 800; font-family: var(--font-disp); font-variant-numeric: tabular-nums; } +.tr-stat span { font-size: 12px; color: var(--muted); } +.tr-stat.task .ic { color: var(--blue); } +.tr-stat.event .ic { color: var(--coral); } +.tr-stat.reply .ic { color: var(--green); } + +.tr-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; } +.tr-card { + background: var(--card); border: 1px solid var(--line); + border-radius: 18px; box-shadow: var(--shadow-sm); padding: 16px 17px; +} +.tr-card-head { display: flex; align-items: center; gap: 9px; margin-bottom: 13px; } +.tr-card-head .h-ico { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; flex-shrink: 0; } +.tr-card-head .h-ico .ic { width: 16px; height: 16px; } +.tr-card.task .h-ico { background: color-mix(in oklab, var(--blue) 13%, transparent); color: var(--blue); } +.tr-card.event .h-ico { background: color-mix(in oklab, var(--coral) 13%, transparent); color: var(--coral); } +.tr-card.reply .h-ico { background: color-mix(in oklab, var(--green) 13%, transparent); color: var(--green); } +.tr-card-head b { font-size: 14px; font-weight: 700; flex: 1; } +.tr-card-head .n { font-size: 11.5px; font-weight: 700; color: var(--muted); background: var(--card-2); border-radius: 999px; padding: 2px 9px; } +.tr-item { padding: 10px 0; border-top: 1px solid var(--line); } +.tr-item:first-of-type { border-top: none; padding-top: 0; } +.tr-item .ti-src { font-size: 10.5px; font-weight: 600; color: var(--muted); margin-bottom: 3px; } +.tr-item .ti-text { font-size: 13px; font-weight: 600; color: var(--ink); line-height: 1.35; } +.tr-item .ti-meta { display: flex; align-items: center; gap: 7px; margin-top: 6px; flex-wrap: wrap; } +.tr-item .ti-meta .chip2 { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--muted); } +.tr-item .ti-meta .chip2 .ic { width: 11px; height: 11px; } +.tr-empty { font-size: 12.5px; color: var(--faint); padding: 8px 0; } + +.entered .mwork > * { animation: rise 0.42s cubic-bezier(0.2,0.7,0.2,1) both; } +.entered .mlist { animation-delay: 0.05s; } +.entered .reader { animation-delay: 0.1s; } +@keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } + +/* ====================== 작성 (Compose) 슬라이드오버 ====================== */ +.cp-backdrop { + position: fixed; inset: 0; z-index: 60; + background: rgba(26, 22, 18, 0.34); + -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); + animation: fade 0.18s ease; +} +@keyframes fade { from { opacity: 0; } to { opacity: 1; } } +.compose { + position: fixed; top: 0; right: 0; bottom: 0; z-index: 61; + width: 620px; max-width: 100vw; + background: var(--card); border-left: 1px solid var(--line); + box-shadow: -20px 0 60px -20px rgba(26,22,18,0.4); + display: flex; flex-direction: column; + animation: slidein 0.24s cubic-bezier(0.2, 0.7, 0.2, 1); +} +@keyframes slidein { from { transform: translateX(40px); opacity: 0; } to { transform: none; opacity: 1; } } +.cp-head { display: flex; align-items: center; gap: 10px; padding: 16px 22px; border-bottom: 1px solid var(--line); } +.cp-head .h-ic { width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; background: var(--fill); color: var(--on-fill); } +.cp-head .h-ic .ic { width: 16px; height: 16px; } +.cp-head b { font-family: var(--font-disp); font-size: 16px; font-weight: 700; } +.cp-close { margin-left: auto; width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center; color: var(--muted); transition: background 0.13s, color 0.13s; } +.cp-close .ic { width: 17px; height: 17px; } +.cp-close:hover { background: var(--card-2); color: var(--ink); } + +.cp-body { flex: 1; min-height: 0; overflow-y: auto; padding: 6px 22px 22px; display: flex; flex-direction: column; } +.cp-field { display: flex; align-items: center; gap: 10px; padding: 13px 2px; border-bottom: 1px solid var(--line); } +.cp-field label { font-size: 12px; font-weight: 600; color: var(--muted); width: 48px; flex-shrink: 0; } +.cp-field input { flex: 1; min-width: 0; border: none; background: none; outline: none; font-size: 14px; color: var(--ink); } +.cp-field input::placeholder { color: var(--faint); } +.cp-field .from-pick { display: inline-flex; align-items: center; gap: 7px; font-size: 13.5px; font-weight: 600; color: var(--ink); } +.cp-field .from-pick .acc-dot { width: 8px; height: 8px; } +.cp-text { + flex: 1; min-height: 220px; resize: none; border: none; background: none; outline: none; + font-size: 14.5px; line-height: 1.7; color: var(--ink-2); padding: 16px 2px; width: 100%; +} +.cp-text::placeholder { color: var(--faint); } + +/* AI 작성 도우미 바 */ +.cp-ai { + margin: 4px 0 0; padding: 12px 14px; border-radius: 14px; + background: + radial-gradient(120% 120% at 0% 0%, color-mix(in oklab, var(--coral) 13%, transparent), transparent 60%), + var(--card-2); + border: 1px solid color-mix(in oklab, var(--coral) 22%, var(--line)); +} +.cp-ai-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; } +.cp-ai-head .a-ic { width: 24px; height: 24px; border-radius: 7px; display: grid; place-items: center; background: var(--coral); color: #fff; } +.cp-ai-head .a-ic .ic { width: 13px; height: 13px; } +.cp-ai-head b { font-size: 12.5px; font-weight: 700; } +.cp-ai-actions { display: flex; flex-wrap: wrap; gap: 7px; } +.cp-ai-btn { + display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; + font-size: 12px; font-weight: 600; color: var(--ink-2); + padding: 7px 12px; border-radius: 999px; background: var(--card); border: 1px solid var(--line); + transition: background 0.13s, color 0.13s, transform 0.12s; +} +.cp-ai-btn .ic { width: 13px; height: 13px; color: var(--coral); } +.cp-ai-btn:hover { color: var(--ink); transform: translateY(-1px); } +.cp-ai-btn:disabled { opacity: 0.55; cursor: default; transform: none; } + +.cp-foot { display: flex; align-items: center; gap: 10px; padding: 14px 22px; border-top: 1px solid var(--line); } +.cp-send { + display: inline-flex; align-items: center; gap: 8px; + padding: 12px 20px; border-radius: 12px; font-size: 14px; font-weight: 700; + background: var(--fill); color: var(--on-fill); box-shadow: var(--shadow-sm); + transition: filter 0.13s, transform 0.12s; +} +.cp-send .ic { width: 16px; height: 16px; } +.cp-send:hover { transform: translateY(-1px); filter: brightness(1.06); } +.cp-foot .cp-tool { width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center; color: var(--muted); transition: background 0.13s, color 0.13s; } +.cp-foot .cp-tool .ic { width: 17px; height: 17px; } +.cp-foot .cp-tool:hover { background: var(--card-2); color: var(--ink); } +.cp-foot .cp-spacer { flex: 1; } +.cp-foot .cp-discard { font-size: 13px; color: var(--muted); padding: 8px 12px; border-radius: 9px; } +.cp-foot .cp-discard:hover { color: var(--coral); } + +/* 토스트 */ +.toast-wrap { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); z-index: 80; display: flex; flex-direction: column; gap: 8px; align-items: center; } +.toast { + display: flex; align-items: center; gap: 10px; + padding: 12px 18px; border-radius: 13px; + background: var(--fill); color: var(--on-fill); + font-size: 13px; font-weight: 600; box-shadow: var(--shadow); + animation: toastin 0.3s cubic-bezier(0.2,0.7,0.2,1); +} +.toast .ic { width: 16px; height: 16px; } +.toast .t-dot { width: 8px; height: 8px; border-radius: 50%; } +@keyframes toastin { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } } + +/* ====================== 반응형 ====================== */ +@media (max-width: 1320px) { + .mwork { grid-template-columns: 224px 340px minmax(0, 1fr); } +} +@media (max-width: 1080px) { + .mwork { grid-template-columns: 320px minmax(0, 1fr); height: auto; } + .subnav { display: none; } +} +@media (max-width: 760px) { + .mpage { padding: 0 12px 20px; } + .topbar { margin: 0 -12px; padding: 0 14px; gap: 14px; } + .mainnav { display: none; } + .mwork { grid-template-columns: 1fr; } + .mlist.has-sel { display: none; } + .compose { width: 100vw; } +} diff --git a/design-reference/assets/mail.jsx b/design-reference/assets/mail.jsx new file mode 100644 index 0000000..29e839a --- /dev/null +++ b/design-reference/assets/mail.jsx @@ -0,0 +1,647 @@ +/* 아리 — 메일(Mail) · 클린 화이트 글래스 + 여러 계정 통합 수신 · 읽기 · 작성 + AI 비서가 할 일/일정/답장/정리를 미리 준비 */ +const { useState, useEffect, useRef, useMemo } = React; +const MD = window.MailData; +const PPL = MD.people; +const ACC = MD.acc; + +/* ---------- 아이콘 ---------- */ +const P = { + spark: "M12 3l1.9 5.2L19 10l-5.1 1.8L12 17l-1.9-5.2L5 10l5.1-1.8z|M19 4v3|M5 17v3", + grid: "M3 3h7v7H3z|M14 3h7v7h-7z|M14 14h7v7h-7z|M3 14h7v7H3z", + cal: "M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", + route: "M6 19a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M18 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z|M9 16h6a3 3 0 0 0 3-3V11", + check: "M9 11l3 3L22 4|M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11", + tick: "M20 6 9 17l-5-5", + mail: "M22 7 13.03 12.7a1.94 1.94 0 0 1-2.06 0L2 7|M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", + brain: "M12 5a3 3 0 1 0-5.9.8A3 3 0 0 0 4 9.5 3 3 0 0 0 6 14a3 3 0 0 0 6 1zM12 5a3 3 0 1 1 5.9.8A3 3 0 0 1 20 9.5 3 3 0 0 1 18 14a3 3 0 0 1-6 1z|M12 5v14", + heart: "M19 14c1.5-1.5 3-3.4 3-5.5A4.5 4.5 0 0 0 12 5.5 4.5 4.5 0 0 0 2 8.5c0 2.1 1.5 4 3 5.5l7 7z", + inbox: "M22 12h-6l-2 3h-4l-2-3H2|M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z", + star: "M12 2.5l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 18.8 6.2 21l1.1-6.5L2.6 9.8l6.5-.9z", + send: "M22 2 11 13|M22 2 15 22l-4-9-9-4z", + file: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z|M14 2v6h6", + archive: "M21 8v13H3V8|M1 3h22v5H1z|M10 12h4", + search: "M21 21l-4.34-4.34|M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z", + bell: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9|M10.3 21a1.94 1.94 0 0 0 3.4 0", + sun: "M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10z|M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4", + moon: "M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z", + plus: "M12 5v14M5 12h14", + x: "M18 6 6 18M6 6l12 12", + more: "M5 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0zM11 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0zM17 12a1 1 0 1 0 2 0 1 1 0 0 0-2 0z", + chev: "M9 6l6 6-6 6", + reply: "M9 17l-5-5 5-5|M4 12h11a5 5 0 0 1 5 5v2", + replyAll: "M7 17l-5-5 5-5|M12 17l-5-5 5-5|M7 12h9a5 5 0 0 1 5 5v2", + forward: "M15 17l5-5-5-5|M20 12H9a5 5 0 0 0-5 5v2", + trash: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6", + clip: "M21 11.5 12 20a5.5 5.5 0 0 1-8-8l8.5-8a3.7 3.7 0 0 1 5.3 5.3l-8.6 8a1.9 1.9 0 0 1-2.7-2.7l8-7.5", + refresh: "M21 12a9 9 0 1 1-3-6.7|M21 3v5h-5", + pen: "M12 20h9|M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z", + clock: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18z|M12 7v5l3 2", + user: "M20 21a8 8 0 0 0-16 0|M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z", + arrow: "M5 12h14|M13 6l6 6-6 6", + calPlus: "M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v6|M19 16v6M16 19h6", + folder: "M3 7a2 2 0 0 1 2-2h4l2 2.5h8a2 2 0 0 1 2 2V18a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z", + flag: "M4 22V4M4 4s1.5-1 5-1 5 2 8 2 4-1 4-1v10s-1.5 1-4 1-5-2-8-2-5 1-5 1", + wand: "M15 4V2M15 16v-2M8 9h2M20 9h2M17.8 11.8 19 13M17.8 6.2 19 5M3 21l9-9M12.2 6.2 11 5", + at: "M12 16a4 4 0 1 0 0-8 4 4 0 0 0 0 8z|M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.9 7.9", + layers: "M12 2 2 7l10 5 10-5z|M2 17l10 5 10-5M2 12l10 5 10-5", + zap: "M13 2 3 14h9l-1 8 10-12h-9z", +}; +function I({ d, w }) { + return ( + + {P[d].split("|").map((s, i) => )} + + ); +} + +const MAIN = [ + { id: "dash", label: "대시보드", icon: "grid", href: "대시보드.html" }, + { id: "sbox", label: "인박스", icon: "inbox", href: "인박스.html" }, + { id: "appr", label: "결재함", icon: "spark", badge: "3", href: "결재함.html" }, + { id: "auto", label: "자동화", icon: "zap", href: "자동화.html" }, + { id: "journey", label: "여정", icon: "route", href: "여정.html" }, + { id: "cal", label: "일정", icon: "cal", href: "일정.html" }, + { id: "task", label: "작업", icon: "check", badge: "4", href: "작업.html" }, + { id: "mail", label: "메일", icon: "mail", href: "메일.html" }, + { id: "noti", label: "알림", icon: "bell", badge: "6", href: "알림.html" }, + { id: "research", label: "리서치", icon: "brain", href: "리서치.html" }, + { id: "trip", label: "여행", icon: "send", href: "여행.html" }, + { id: "life", label: "라이프", icon: "heart", href: "라이프.html" }, + { id: "wrap", label: "하루 마감", icon: "moon", href: "하루 마감.html" }, +]; + +function load(k, fb) { try { const v = localStorage.getItem("ariM." + k); return v === null ? fb : JSON.parse(v); } catch (e) { return fb; } } +function save(k, v) { try { localStorage.setItem("ariM." + k, JSON.stringify(v)); } catch (e) {} } + +const aiCount = (m) => (m.ai ? m.ai.tasks.length + m.ai.events.length + (m.ai.replies.length ? 1 : 0) : 0); + +/* ================= 메일 목록 행 ================= */ +function MailRow({ m, on, onClick, onStar }) { + const p = PPL[m.from]; + const a = ACC[m.account]; + return ( +
+
+ {p.initial} + +
+
+
+ {p.name} + {m.time} +
+
{m.subject}
+
{m.preview}
+ {m.ai && (m.ai.priority === "높음" || aiCount(m) > 0) && ( +
+ {m.ai.priority === "높음" && 중요} + {m.ai.tasks.length > 0 && 할 일 {m.ai.tasks.length}} + {m.ai.events.length > 0 && 일정 {m.ai.events.length}} + {m.ai.replies.length > 0 && 답장 추천} +
+ )} +
+
+ + {m.hasAttach && } +
+
+ ); +} + +/* ================= 제안 항목 (할 일 / 일정) ================= */ +function Sug({ kind, text, meta, added, onAdd }) { + return ( +
+
+
+
{text}
+
{meta}
+
+ +
+ ); +} + +/* ================= 아리 분석 패널 ================= */ +function AriPanel({ m, added, onAdd, onReply, onAddAll }) { + const ai = m.ai; + const keys = []; + ai.tasks.forEach((_, i) => keys.push("t:" + m.id + ":" + i)); + ai.events.forEach((_, i) => keys.push("e:" + m.id + ":" + i)); + if (ai.file) keys.push("f:" + m.id); + const allDone = keys.length > 0 && keys.every((k) => added[k]); + + return ( +
+
+
+
+ 아리가 분석했어요 + {ai.category} · 새 메일을 읽고 필요한 일을 정리했어요 +
+ {ai.priority} +
+ +
요약 — " + ai.summary }} /> + + {ai.tasks.length > 0 && ( +
+
할 일로 추가{ai.tasks.length}건
+ {ai.tasks.map((t, i) => { + const k = "t:" + m.id + ":" + i; + return ( + onAdd(k, "작업", t.text)} + meta={<> + {t.project} + + {t.due} + + {t.prio} + } /> + ); + })} +
+ )} + + {ai.events.length > 0 && ( +
+
일정에 추가{ai.events.length}건
+ {ai.events.map((ev, i) => { + const k = "e:" + m.id + ":" + i; + return ( + onAdd(k, "일정", ev.title)} + meta={<> + {ev.date} ({ev.day}) + + {ev.time}{ev.dur ? " · " + ev.dur : ""} + {ev.place && <>{ev.place}} + } /> + ); + })} +
+ )} + + {ai.replies.length > 0 && ( +
+
답장 추천{ai.replies.length}개 초안
+
+ {ai.replies.map((r, i) => ( + + ))} +
+
+ )} + + {ai.file && (() => { + const k = "f:" + m.id; + return ( +
+
+
{ai.file.project}에 정리{ai.file.reason}
+ +
+ ); + })()} + + {keys.length > 1 && ( +
+ 아리가 준비한 {keys.length}개 작업을 한 번에 적용할 수 있어요 + +
+ )} +
+ ); +} + +/* ================= 리딩 패널 ================= */ +function Reader({ m, added, onAdd, onReply, onAddAll, onCompose, onArchive, onStar }) { + if (!m) { + return ( +
+
+
+

읽을 메일을 선택하세요

+

메일을 열면 아리가 내용을 분석해 할 일·일정·답장을 미리 준비해 드려요.

+
+
+ ); + } + const p = PPL[m.from], a = ACC[m.account]; + return ( +
+
+ + + +
+ + + +
+
+
+

{m.subject}

+
+
{p.initial}
+
+ {p.name} {p.role && · {p.role}} +
{p.email}
+
+
{m.date}
+
+
받는 사람: {m.to}
+
{a.name} · {a.email}
+ +
+
+ {m.body.map((para, i) =>

{para}

)} +
+ + {m.attachments && m.attachments.length > 0 && ( +
+ {m.attachments.map((at, i) => ( +
+ +
{at.name}{at.size}
+
+ ))} +
+ )} + + {m.ai && } +
+
+
+ ); +} + +/* ================= 아리 정리 (트리아지) ================= */ +function Triage({ emails, onOpen, onReply }) { + const fresh = emails.filter((m) => !m.read && m.ai); + const tasks = [], events = [], replies = []; + fresh.forEach((m) => { + m.ai.tasks.forEach((t) => tasks.push({ ...t, src: PPL[m.from].name, m })); + m.ai.events.forEach((e) => events.push({ ...e, src: PPL[m.from].name, m })); + if (m.ai.replies.length) replies.push({ src: PPL[m.from].name, subj: m.subject, r: m.ai.replies[0], m }); + }); + return ( +
+
+
+
+
+
+
아리 정리 · 오늘 아침
+

새 메일 {fresh.length}통을 읽고 할 일을 정리해뒀어요

+

세 계정에 새로 온 메일을 분석했어요. 중요도 순으로 보면, 분기 전략 미팅과 온보딩 시안 피드백이 오늘 가장 급해요. 아래에서 바로 작업·일정으로 옮기거나 답장을 보낼 수 있어요.

+
+
{tasks.length}할 일 제안
+
{events.length}일정 감지
+
{replies.length}답장 초안
+
+
+
+ +
+
+
할 일로 정리{tasks.length}
+ {tasks.length === 0 &&
정리할 할 일이 없어요
} + {tasks.map((t, i) => ( +
onOpen(t.m.id)} style={{ cursor: "pointer" }}> +
{t.src}님 메일에서
+
{t.text}
+
+ {t.project} + {t.due} +
+
+ ))} +
+ +
+
일정에 추가{events.length}
+ {events.length === 0 &&
감지된 일정이 없어요
} + {events.map((e, i) => ( +
onOpen(e.m.id)} style={{ cursor: "pointer" }}> +
{e.src}님 메일에서
+
{e.title}
+
+ {e.date} ({e.day}) + {e.time} +
+
+ ))} +
+ +
+
답장 초안 준비됨{replies.length}
+ {replies.length === 0 &&
추천할 답장이 없어요
} + {replies.map((rp, i) => ( +
onReply(rp.m, rp.r)} style={{ cursor: "pointer" }}> +
{rp.src}님께 · {rp.subj}
+
{rp.r.tone}
+
“{rp.r.preview}”
+
+ ))} +
+
+
+
+
+ ); +} + +/* ================= 작성 (Compose) ================= */ +const AI_DRAFTS = { + default: "안녕하세요,\n\n메일 잘 받았습니다. 내용 확인하고 빠른 시일 내 회신드리겠습니다.\n\n감사합니다.\n지우 드림", +}; +function Compose({ init, onClose, onSend }) { + const [from, setFrom] = useState(init.from || "work"); + const [to, setTo] = useState(init.to || ""); + const [subject, setSubject] = useState(init.subject || ""); + const [body, setBody] = useState(init.body || ""); + const [busy, setBusy] = useState(false); + const a = ACC[from]; + const cycleFrom = () => { + const ids = MD.accounts.map((x) => x.id); + setFrom(ids[(ids.indexOf(from) + 1) % ids.length]); + }; + const aiAction = (label, fn) => { + setBusy(true); + setTimeout(() => { setBody(fn(body)); setBusy(false); }, 650); + }; + useEffect(() => { + const h = (e) => { if (e.key === "Escape") onClose(); }; + window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h); + }, [onClose]); + + return ( + <> +
+