diff --git a/README.ja.md b/README.ja.md index 11f2fd1..c7d42d2 100644 --- a/README.ja.md +++ b/README.ja.md @@ -21,7 +21,7 @@ Artic は、実装前にホームページのデザイン方向を契約とし ```text @artic init # 意図と参照を収集 @artic start # strategy artifacts を作成し DESIGN.md 文書をコンパイル -@artic show # 安全な静的 preview をレンダリング +@artic show # provenance を記録した asset-first preview bundle をレンダリング @artic review # 実装を文書に照らして確認 ``` @@ -121,7 +121,7 @@ Artic が呼び出されると、エージェントは次を行います: 4. 色の役割、タイプ階層、余白リズム、コンポーネント、モーション、アクセシビリティなど、再利用可能なルールを抽出します。 5. ユーザーのプロジェクト目標に基づいて、レファレンス間の衝突を解決します。 6. `@artic start` で public agent workflow が `.artic/strategy.json` を作成し、`docs/artic-strategy.md` を保存してから compiler を実行し、`DESIGN.md` と補助文書を生成します。 -7. `@artic show` で strategy artifacts に基づく安全な静的 preview `.artic/show/index.html` をレンダリングし、アプリのソースファイルは変更しません。 +7. `@artic show` で strategy artifacts に基づく asset-first visual draft bundle を `.artic/show/` にレンダリングし、アプリのソースファイルは変更しません。 8. `@artic review` で実装を `.artic/strategy.json`、`docs/artic-strategy.md`、`DESIGN.md` と比較し、生成されたデザイン文書を検証します。 ## When To Use It @@ -146,7 +146,7 @@ Artic が呼び出されると、エージェントは次を行います: | デザインヒアリング開始 | `@artic init` | | 高速ヒアリング実行 | `@artic init quick` | | strategy 作成と文書コンパイル | `@artic start` | -| strategy artifacts から安全な静的 preview をレンダリング | `@artic show` | +| strategy artifacts から asset-first preview bundle をレンダリング | `@artic show` | | strategy + `DESIGN.md` に対して実装をレビュー | `@artic review the homepage against DESIGN.md` | | インストール済み/最新バージョン確認 | `@artic version` | | 安全な更新コマンド表示 | `@artic update` | @@ -172,7 +172,14 @@ DESIGN.md docs/design-rules.md docs/design-qa-checklist.md docs/homepage-design-prompt.md -.artic/show/index.html # strategy artifacts から @artic show が生成する preview 専用ファイル。デフォルトではアプリファイルを変更しません +.artic/show/index.html +.artic/show/styles.css +.artic/show/tokens.json +.artic/show/assets/manifest.json # asset provenance; 未検証 asset は preview-only で production-cleared ではありません +.artic/show/report.json +.artic/show/critique.md +.artic/show/selected.json +.artic/show/iterations//... # @artic show が生成。アプリファイルは変更しません ``` ## Repository Layout diff --git a/README.ko.md b/README.ko.md index 6860608..1ea3666 100644 --- a/README.ko.md +++ b/README.ko.md @@ -21,7 +21,7 @@ Artic은 구현 전에 홈페이지 디자인 방향을 계약으로 고정하 ```text @artic init # 의도와 레퍼런스 수집 @artic start # strategy artifacts를 작성한 뒤 DESIGN.md 문서 컴파일 -@artic show # 안전한 정적 preview 렌더링 +@artic show # 출처가 기록된 asset-first preview bundle 렌더링 @artic review # 구현 결과를 문서 기준으로 검토 ``` @@ -121,7 +121,7 @@ Artic이 호출되면 에이전트는 다음을 수행합니다: 4. 색상 역할, 타이포그래피, spacing, 컴포넌트, 모션, 접근성 규칙을 추출합니다. 5. 사용자 목표를 기준으로 레퍼런스 간 충돌을 해결합니다. 6. `@artic start`에서 public agent workflow가 `.artic/strategy.json`을 작성하고 `docs/artic-strategy.md`를 저장한 뒤 compiler를 실행해 `DESIGN.md`와 보조 문서를 생성합니다. -7. `@artic show`로 strategy artifacts 기반의 안전한 정적 preview인 `.artic/show/index.html`을 렌더링하며 앱 소스 파일은 바꾸지 않습니다. +7. `@artic show`로 strategy artifacts 기반의 asset-first 시각 초안 bundle을 `.artic/show/` 아래 렌더링하며 앱 소스 파일은 바꾸지 않습니다. 8. `@artic review`로 구현을 `.artic/strategy.json`, `docs/artic-strategy.md`, `DESIGN.md`와 비교하고 생성 문서를 검증합니다. ## When To Use It @@ -146,7 +146,7 @@ Artic이 호출되면 에이전트는 다음을 수행합니다: | 디자인 인터뷰 시작 | `@artic init` | | 빠른 인터뷰 실행 | `@artic init quick` | | strategy 작성 및 문서 컴파일 | `@artic start` | -| strategy artifacts 기반 안전한 정적 preview 렌더링 | `@artic show` | +| strategy artifacts 기반 asset-first preview bundle 렌더링 | `@artic show` | | strategy + `DESIGN.md` 기준 구현 결과 검토 | `@artic review the homepage against DESIGN.md` | | 설치/최신 버전 확인 | `@artic version` | | 안전한 업데이트 명령 출력 | `@artic update` | @@ -174,7 +174,14 @@ DESIGN.md docs/design-rules.md docs/design-qa-checklist.md docs/homepage-design-prompt.md -.artic/show/index.html # strategy artifacts에서 @artic show가 생성하는 preview 전용 파일; 기본적으로 앱 파일은 변경하지 않음 +.artic/show/index.html +.artic/show/styles.css +.artic/show/tokens.json +.artic/show/assets/manifest.json # asset 출처 기록; 미검증 asset은 preview-only이며 production-cleared가 아님 +.artic/show/report.json +.artic/show/critique.md +.artic/show/selected.json +.artic/show/iterations//... # @artic show가 생성; 앱 파일은 변경하지 않음 ``` ## Repository Layout diff --git a/README.md b/README.md index dacfa89..907621b 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ It keeps the public workflow small: ```text @artic init # collect intent and references @artic start # author strategy artifacts, then compile DESIGN.md docs -@artic show # render a safe static preview +@artic show # render a provenance-recorded asset-first preview bundle @artic review # check implementation against the docs ``` @@ -121,7 +121,7 @@ When invoked, Artic asks the agent to: 4. Extract reusable rules: color roles, type hierarchy, spacing rhythm, components, motion, accessibility. 5. Resolve conflicts between references based on the user's project goal. 6. Run `@artic start` so the public agent workflow authors `.artic/strategy.json`, writes `docs/artic-strategy.md`, then runs the compiler for `DESIGN.md` and supporting docs. -7. Run `@artic show` to render `.artic/show/index.html` from strategy artifacts as a safe static preview without changing app source files. +7. Run `@artic show` to render a provenance-recorded asset-first visual draft bundle under `.artic/show/` without changing app source files. 8. Run `@artic review` to compare implementation against `.artic/strategy.json`, `docs/artic-strategy.md`, and `DESIGN.md`, then validate the generated design docs before implementation. ## When To Use It @@ -146,7 +146,7 @@ Skip it for: | Start the design interview | `@artic init` | | Run the fast interview | `@artic init quick` | | Author strategy and compile docs | `@artic start` | -| Render a safe static preview from strategy artifacts | `@artic show` | +| Render an asset-first preview bundle from strategy artifacts | `@artic show` | | Review implementation against strategy + `DESIGN.md` | `@artic review the homepage against DESIGN.md` | | Check installed/latest version | `@artic version` | | Print safe update commands | `@artic update` | @@ -174,7 +174,14 @@ DESIGN.md docs/design-rules.md docs/design-qa-checklist.md docs/homepage-design-prompt.md -.artic/show/index.html # generated by @artic show from strategy artifacts; preview only, app files unchanged by default +.artic/show/index.html +.artic/show/styles.css +.artic/show/tokens.json +.artic/show/assets/manifest.json # asset provenance; unverified assets are preview-only, not production-cleared +.artic/show/report.json +.artic/show/critique.md +.artic/show/selected.json +.artic/show/iterations//... # generated by @artic show; app files unchanged ``` ## Repository Layout diff --git a/README.zh-CN.md b/README.zh-CN.md index 8a0cd89..4f200fd 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -21,7 +21,7 @@ Artic 是一个兼容 Claude/Codex 的 agent design-direction protocol,也是 ```text @artic init # 收集意图和参考 @artic start # 编写 strategy artifacts,再编译 DESIGN.md 文档 -@artic show # 渲染安全的静态 preview +@artic show # 渲染记录来源的 asset-first preview bundle @artic review # 按文档检查实现 ``` @@ -121,7 +121,7 @@ Artic 被调用后,Agent 会: 4. 提取可复用规则:颜色角色、字体层级、间距节奏、组件、动效与可访问性。 5. 根据用户的项目目标解决不同参考之间的冲突。 6. 运行 `@artic start`,让 public agent workflow 编写 `.artic/strategy.json`、保存 `docs/artic-strategy.md`,再运行 compiler 生成 `DESIGN.md` 和辅助文档。 -7. 运行 `@artic show`,基于 strategy artifacts 渲染 `.artic/show/index.html`,作为不会修改应用源码的安全静态 preview。 +7. 运行 `@artic show`,基于 strategy artifacts 在 `.artic/show/` 下渲染 asset-first visual draft bundle,且不会修改应用源码。 8. 运行 `@artic review`,把实现与 `.artic/strategy.json`、`docs/artic-strategy.md`、`DESIGN.md` 对比,并验证生成的设计文档。 ## When To Use It @@ -146,7 +146,7 @@ Artic 被调用后,Agent 会: | 开始设计访谈 | `@artic init` | | 运行快速访谈 | `@artic init quick` | | 编写 strategy 并编译文档 | `@artic start` | -| 基于 strategy artifacts 渲染安全静态 preview | `@artic show` | +| 基于 strategy artifacts 渲染 asset-first preview bundle | `@artic show` | | 按 strategy + `DESIGN.md` 审查实现 | `@artic review the homepage against DESIGN.md` | | 检查已安装/最新版本 | `@artic version` | | 输出安全更新命令 | `@artic update` | @@ -172,7 +172,14 @@ DESIGN.md docs/design-rules.md docs/design-qa-checklist.md docs/homepage-design-prompt.md -.artic/show/index.html # 由 @artic show 基于 strategy artifacts 生成的 preview 文件;默认不修改应用文件 +.artic/show/index.html +.artic/show/styles.css +.artic/show/tokens.json +.artic/show/assets/manifest.json # asset provenance;未验证 asset 仅限 preview-only,不代表 production-cleared +.artic/show/report.json +.artic/show/critique.md +.artic/show/selected.json +.artic/show/iterations//... # 由 @artic show 生成;不修改应用文件 ``` ## Repository Layout diff --git a/README.zh-TW.md b/README.zh-TW.md index 1cbc3a0..37ff154 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -21,7 +21,7 @@ Artic 是一個相容 Claude/Codex 的 agent design-direction protocol,也是 ```text @artic init # 收集意圖和參考 @artic start # 編寫 strategy artifacts,再編譯 DESIGN.md 文件 -@artic show # 渲染安全的靜態 preview +@artic show # 渲染記錄來源的 asset-first preview bundle @artic review # 依文件檢查實作 ``` @@ -121,7 +121,7 @@ Artic 被呼叫後,Agent 會: 4. 萃取可重用規則:色彩角色、字體層級、間距節奏、元件、動效與可存取性。 5. 依據使用者的專案目標解決不同參考之間的衝突。 6. 執行 `@artic start`,讓 public agent workflow 編寫 `.artic/strategy.json`、儲存 `docs/artic-strategy.md`,再執行 compiler 生成 `DESIGN.md` 和輔助文件。 -7. 執行 `@artic show`,基於 strategy artifacts 渲染 `.artic/show/index.html`,作為不會修改應用程式原始碼的安全靜態 preview。 +7. 執行 `@artic show`,基於 strategy artifacts 在 `.artic/show/` 下渲染 asset-first visual draft bundle,且不會修改應用程式原始碼。 8. 執行 `@artic review`,把實作與 `.artic/strategy.json`、`docs/artic-strategy.md`、`DESIGN.md` 對比,並驗證生成的設計文件。 ## When To Use It @@ -146,7 +146,7 @@ Artic 被呼叫後,Agent 會: | 開始設計訪談 | `@artic init` | | 執行快速访谈 | `@artic init quick` | | 編寫 strategy 並編譯文件 | `@artic start` | -| 基於 strategy artifacts 渲染安全靜態 preview | `@artic show` | +| 基於 strategy artifacts 渲染 asset-first preview bundle | `@artic show` | | 依 strategy + `DESIGN.md` 審查實作 | `@artic review the homepage against DESIGN.md` | | 检查已安裝/最新版本 | `@artic version` | | 輸出安全更新命令 | `@artic update` | @@ -172,7 +172,14 @@ DESIGN.md docs/design-rules.md docs/design-qa-checklist.md docs/homepage-design-prompt.md -.artic/show/index.html # 由 @artic show 基於 strategy artifacts 生成的 preview 檔案;預設不修改應用程式檔案 +.artic/show/index.html +.artic/show/styles.css +.artic/show/tokens.json +.artic/show/assets/manifest.json # asset provenance;未驗證 asset 僅限 preview-only,不代表 production-cleared +.artic/show/report.json +.artic/show/critique.md +.artic/show/selected.json +.artic/show/iterations//... # 由 @artic show 生成;不修改應用程式檔案 ``` ## Repository Layout diff --git a/plugins/claude-artic/skills/artic/SKILL.md b/plugins/claude-artic/skills/artic/SKILL.md index c872aaa..1a7db6c 100644 --- a/plugins/claude-artic/skills/artic/SKILL.md +++ b/plugins/claude-artic/skills/artic/SKILL.md @@ -30,8 +30,8 @@ Core flow: → validate outputs @artic show -→ render the strategy artifacts and generated design docs into a safe static homepage preview -→ write `.artic/show/index.html` without modifying app source files by default +→ render the strategy artifacts and sourced/generated assets into a provenance-recorded asset-first visual draft bundle +→ write `.artic/show/` preview files without modifying app source files @artic review → compare implementation against `.artic/strategy.json`, `docs/artic-strategy.md`, and `DESIGN.md` @@ -91,8 +91,9 @@ Default interview questions: External reference vs external asset boundary: - External references are design/runtime sources used for reusable principles, interaction patterns, accessibility/performance constraints, token relationships, and implementation guidance. - External assets are concrete media that can appear in the generated site: GLB files, images, models, icons, textures, fonts, illustrations, or similar files. -- Unless the user explicitly allows asset usage, treat all external sources as reference-principles only. -- If the user allows asset usage, use only user-owned assets or license-verifiable public assets such as CC0/CC-BY/public-domain/MIT/Apache-2.0 where appropriate, and record source URL, license, and attribution in `docs/artic-brief.md` or implementation docs. +- `@artic start` still treats external sources as reference-principles unless the user explicitly allows asset usage. +- `@artic show` may use sourced or generated assets for the preview bundle when doing so improves the first visual draft, but every asset must be recorded in `.artic/show/assets/manifest.json` and `.artic/show/report.json` with source/generation notes, license status when known, and preview-only status when unverified. +- Unverified assets are allowed only inside the preview bundle as provenance-recorded, preview-only material. Do not claim production license clearance; production/apply review belongs to a future `@artic apply` flow or a separate implementation review. Fast path: if user says `@artic init quick`, ask only product, audience, goal, vibe, and references. @@ -143,7 +144,7 @@ Lifecycle transition rule: `@artic start` is the only transition that may finali ### `@artic show` -Purpose: render the current Artic design outputs into a safe static homepage preview. +Purpose: render the current Artic strategy into a provenance-recorded, asset-first first visual draft bundle. The goal is a stronger strategy-grounded first visual draft by using sourced/generated assets when helpful, while recording where each asset came from and whether it is preview-only. Executable path for agents/hosts that expose shell-backed commands: @@ -153,11 +154,14 @@ python3 /scripts/artic_show.py --root Required behavior: 1. Require `DESIGN.md`, `docs/homepage-design-prompt.md`, `.artic/brief.json`, `.artic/references.json`, and `.artic/strategy.json` from a completed `@artic start` flow. -2. Generate `.artic/show/index.html` as a static preview of the homepage direction. -3. Do not modify app/source files by default; downstream files such as `app/page.tsx`, `src/App.tsx`, or `pages/index.tsx` require an explicit future `--apply` or separate apply command. -4. Include the reference-safety policy in the preview so the preview remains reference-informed, not reference-copied. -5. If selected reference roles include `3d_runtime`, render a runtime-aware preview structure such as a `model-viewer`/Three.js placeholder, central interaction zone, poster/reduced-motion fallback notes, and asset-license reminders instead of a generic landing-page-only preview. -6. Return JSON with `preview_file` and `modified_app_files` for verification. +2. Generate a strategy-grounded first visual draft bundle under `.artic/show/`, not just a minimal static preview. +3. Prefer asset-first draft quality: use user-provided, sourced, or generated images/models/icons/textures when they improve the first visual draft, and fall back to styled placeholders only when assets are unavailable or inappropriate. +4. Record provenance for every preview asset in `.artic/show/assets/manifest.json` and summarize asset decisions, gaps, and warnings in `.artic/show/report.json`. +5. Mark unverified assets as preview-only/provenance-recorded. Do not state or imply production license clearance for unverified sourced/generated assets. +6. Do not modify app/source files. Downstream files such as `app/page.tsx`, `src/App.tsx`, or `pages/index.tsx` are out of scope for `@artic show`; a future `@artic apply` or separate implementation review would handle production/apply decisions later. +7. Include the reference-safety policy in the preview so the preview remains reference-informed, not reference-copied. +8. If selected reference roles include `3d_runtime`, render a runtime-aware preview structure such as a `model-viewer`/Three.js placeholder, central interaction zone, poster/reduced-motion fallback notes, and asset provenance/license reminders instead of a generic landing-page-only preview. +9. Return JSON with at least `preview_file`, `bundle_dir`, `asset_manifest`, `report_file`, and `modified_app_files` for verification. ### `@artic review` MVP-light @@ -276,6 +280,21 @@ docs/design-qa-checklist.md docs/homepage-design-prompt.md ``` +`@artic show` creates a provenance-recorded preview bundle: + +```text +.artic/show/index.html +.artic/show/styles.css +.artic/show/tokens.json +.artic/show/assets/manifest.json +.artic/show/report.json +.artic/show/critique.md +.artic/show/selected.json +.artic/show/iterations//... +``` + +Show bundles are preview-only and do not modify app source files. Unverified sourced/generated assets may be included for first-draft visual quality only when marked preview-only and provenance-recorded; production/apply clearance is out of scope for `@artic show` and belongs to future `@artic apply`/implementation review. + Optional exports: `tailwind.theme.json`, `tokens.json`. ## Presets diff --git a/plugins/claude-artic/skills/artic/scripts/artic_show.py b/plugins/claude-artic/skills/artic/scripts/artic_show.py index 1f38360..cff99e6 100644 --- a/plugins/claude-artic/skills/artic/scripts/artic_show.py +++ b/plugins/claude-artic/skills/artic/scripts/artic_show.py @@ -5,6 +5,7 @@ import html import json import re +import shutil import sys from datetime import datetime, timezone from pathlib import Path @@ -21,6 +22,18 @@ POLICY_BY_LOCALE = { "ko-KR": "참고 정책: 재사용 가능한 원칙만 추출하고, 로고, 상표, 독점 일러스트, 정확한 레이아웃은 복사하지 않습니다.", } +SCORE_KEYS = [ + "strategy_alignment", + "visual_fidelity", + "asset_richness", + "asset_provenance_completeness", + "conversion_clarity", + "visual_specificity", + "accessibility_basics", + "mobile_first", + "genericness_penalty", + "overall", +] def read_json(path: Path) -> dict[str, Any]: @@ -35,6 +48,16 @@ def read_json(path: Path) -> dict[str, Any]: return payload +def write_json(path: Path, payload: Any) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + +def write_text(path: Path, content: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + + def required_missing(root: Path) -> list[str]: return [rel for rel in REQUIRED_INPUTS if not (root / rel).exists()] @@ -119,17 +142,19 @@ def has_3d_runtime_reference(brief: dict[str, Any], references: dict[str, Any]) for role in roles: if isinstance(role, dict) and "3d_runtime" == str(role.get("role")): return True - style = brief.get("style") - facets = style.get("search_facets", []) if isinstance(style, dict) else [] - return isinstance(facets, list) and any("3d" in str(item).lower() or "webgl" in str(item).lower() for item in facets) + for bucket in (brief.get("style"), references, brief): + text = json.dumps(bucket, ensure_ascii=False).lower() if isinstance(bucket, (dict, list)) else str(bucket).lower() + if any(term in text for term in ("3d", "webgl", "glb", "model-viewer", "runtime")): + return True + return False def localized_copy(locale: str) -> dict[str, str]: if locale.startswith("ko"): return { - "preview_badge": "Artic 미리보기 · DESIGN.md 기반 생성", + "preview_badge": "Artic 미리보기 · 에셋 우선 번들", "for_label": "대상", - "headline_suffix": "홈페이지 방향", + "headline_suffix": "첫 시각 초안", "drive": "목표 이끌기", "view_logic": "디자인 논리 보기", "north_star": "Design north star", @@ -138,23 +163,23 @@ def localized_copy(locale: str) -> dict[str, str]: "promise_title": "1. 약속", "promise_body": "프로젝트 고유의 약속과 가장 중요한 전환 경로를 먼저 보여줍니다.", "proof_title": "2. 증거", - "proof_body": "신뢰 근거를 hero 가까이에 배치해 첫 CTA를 뒷받침합니다.", + "proof_body": "신뢰 근거와 에셋 출처를 hero 가까이에 배치해 첫 CTA를 뒷받침합니다.", "system_title": "3. 시스템", "system_body": "토큰, 간격 리듬, 시맨틱 컨트롤, 독창적 구성을 Artic 문서에서 가져옵니다.", "reference_title": "레퍼런스 기반, 복제 아님", - "reference_body": "이 정적 미리보기는 Artic start 산출물에서 생성됩니다. 기본 동작은 .artic/show/index.html만 작성하고 앱 소스 파일은 건드리지 않습니다.", + "reference_body": "이 정적 번들은 Artic start 산출물에서 생성됩니다. 앱 소스 파일은 건드리지 않습니다.", "policy_title": "참고 정책", "generated": "생성 시각", "root": "루트", "model_label": "3D 모델 자리표시자", - "model_body": "실제 GLB/이미지/모델은 사용자가 허용한 소유 에셋 또는 라이선스 확인 가능한 공개 에셋만 연결합니다. 이 영역은 회전, 줌, 포커스, poster fallback, reduced motion 상태를 검토하기 위한 구조입니다.", + "model_body": "실제 GLB/이미지/모델은 소유 에셋 또는 라이선스 확인 가능한 공개 에셋만 연결합니다. 이 preview-only 에셋은 poster fallback, reduced motion, 로딩 실패 상태를 검토하기 위한 구조입니다.", "interaction_title": "상호작용 영역", - "interaction_body": "드래그/탭/키보드 포커스로 석고상을 만지는 경험을 설계하고, 로딩 실패 시 정적 포스터와 설명 텍스트를 제공합니다.", + "interaction_body": "드래그/탭/키보드 포커스로 3D 오브젝트를 탐색하고, 실패 시 정적 포스터와 설명 텍스트를 제공합니다.", } return { - "preview_badge": "Artic Preview · generated from DESIGN.md", + "preview_badge": "Artic Preview · asset-first bundle", "for_label": "For", - "headline_suffix": "homepage direction", + "headline_suffix": "first visual draft", "drive": "Drive", "view_logic": "View design logic", "north_star": "Design north star", @@ -163,16 +188,16 @@ def localized_copy(locale: str) -> dict[str, str]: "promise_title": "1. Promise", "promise_body": "Lead with one clear project-specific promise and one dominant conversion path.", "proof_title": "2. Proof", - "proof_body": "Place evidence close to the hero so trust supports the first CTA.", + "proof_body": "Place evidence and asset provenance close to the hero so trust supports the first CTA.", "system_title": "3. System", "system_body": "Use tokens, spacing rhythm, semantic controls, and original composition from the Artic docs.", "reference_title": "Reference-informed, not reference-copied", - "reference_body": "This static preview is generated from Artic start outputs. It intentionally writes only .artic/show/index.html and leaves app source files untouched.", + "reference_body": "This static bundle is generated from Artic start outputs. It intentionally leaves app source files untouched.", "policy_title": "Reference policy", "generated": "Generated", "root": "Root", - "model_label": "3D model placeholder", - "model_body": "Attach only user-owned or license-verifiable public GLB/image/model assets when allowed. This zone is for rotation, zoom, focus, poster fallback, and reduced-motion review.", + "model_label": "3D asset placeholder", + "model_body": "Attach only user-owned or license-verifiable public GLB/image/model assets when allowed. This preview-only asset validates poster fallback, reduced motion, and loading failure states.", "interaction_title": "Interaction zone", "interaction_body": "Design drag, tap, and keyboard focus affordances for the central 3D object, with a static poster and description fallback when loading fails.", } @@ -187,125 +212,315 @@ def project_name(brief: dict[str, Any], design_frontmatter: str) -> str: return yaml_value(design_frontmatter, "name", "Artic Preview") -def render_html(root: Path, brief: dict[str, Any], references: dict[str, Any], design_text: str) -> str: +def build_tokens(design_text: str) -> dict[str, Any]: + fm = frontmatter(design_text) + colors = { + "primary": safe_css_color(yaml_section_value(fm, "colors", "primary", "#1F4FD8"), "#1F4FD8"), + "accent": safe_css_color(yaml_section_value(fm, "colors", "accent", "#7C3AED"), "#7C3AED"), + "surface": safe_css_color(yaml_section_value(fm, "colors", "surface", "#FFFFFF"), "#FFFFFF"), + "neutral": safe_css_color(yaml_section_value(fm, "colors", "neutral", "#F6F8FB"), "#F6F8FB"), + "text": safe_css_color(yaml_section_value(fm, "colors", "text", "#111827"), "#111827"), + "muted": safe_css_color(yaml_section_value(fm, "colors", "muted", "#6B7280"), "#6B7280"), + "border": safe_css_color(yaml_section_value(fm, "colors", "border", "#DDE3EA"), "#DDE3EA"), + } + return { + "colors": colors, + "type": { + "family": yaml_section_value(fm, "typography", "fontFamily", "Inter, ui-sans-serif, system-ui"), + "hero": "clamp(3rem, 8vw, 5.7rem)", + "heading": "clamp(2rem, 4vw, 3rem)", + "body": "1rem", + }, + "radius": {"sm": "8px", "md": "14px", "lg": "24px", "xl": "32px", "pill": "999px"}, + "spacing": {"xs": "4px", "sm": "8px", "md": "16px", "lg": "24px", "xl": "40px", "section": "96px"}, + "shadow": {"card": "0 24px 80px rgba(15,23,42,.08)", "cta": "0 18px 40px rgba(31,79,216,.22)"}, + "motion": {"duration": "180ms", "easing": "cubic-bezier(.2,.8,.2,1)", "reduced_motion": "preserve layout without parallax"}, + } + + +def variant_names(max_iterations: int, runtime_3d: bool) -> list[str]: + pool = ["asset-hero", "conversion-proof", "immersive-runtime" if runtime_3d else "editorial-system"] + return pool[:max_iterations] + + +def make_placeholder_svg(kind: str, tokens: dict[str, Any]) -> str: + c = tokens["colors"] + title = "Model Poster" if kind == "model" else "Scene Fallback" + label = "preview-only 3D poster" if kind == "model" else "preview-only runtime fallback" + shape = "" if kind == "model" else "" + return f""" + + + + {shape} + {html.escape(title)} + {html.escape(label)} + +""" + + +def build_asset_manifest(root: Path, show_dir: Path, iteration: str, variant: str, brief: dict[str, Any], references: dict[str, Any], tokens: dict[str, Any], asset_mode: str, runtime_3d: bool) -> dict[str, Any]: + assets_dir = show_dir / "assets" + placeholders_dir = assets_dir / "placeholders" + sources = selected_source_names(references) + assets: list[dict[str, Any]] = [] + for idx, source in enumerate(sources or ["artic-generated-visual-direction"], start=1): + assets.append({ + "id": f"catalog-reference-{idx}", + "path": None, + "type": "catalog-reference", + "status": "catalog-reference", + "retrieval": "catalog-reference", + "provenance": "selected Artic reference source", + "kind": "catalog-reference", + "title": source, + "source": source, + "license": "unknown", + "license_status": "unknown-unverified", + "usage": "preview-only visual direction and provenance record; do not copy protected marks or exact layouts", + "downloaded": False, + "local_path": None, + }) + hero_name = f"hero-{variant}.svg" + write_text(placeholders_dir / hero_name, make_placeholder_svg("scene", tokens)) + assets.append({ + "id": f"generated-{variant}-hero", + "path": f"assets/placeholders/{hero_name}", + "type": "image/svg+xml", + "status": "generated", + "retrieval": "generated-placeholder", + "provenance": "generated locally by Artic show", + "kind": "generated-placeholder", + "title": f"{variant} hero placeholder", + "source": "generated locally by Artic show", + "license": "Artic generated preview placeholder", + "license_status": "preview-only-generated", + "usage": "safe first-draft hero artwork", + "downloaded": False, + "local_path": f"assets/placeholders/{hero_name}", + }) + if runtime_3d: + model_svg = make_placeholder_svg("model", tokens) + scene_svg = make_placeholder_svg("scene", tokens) + write_text(placeholders_dir / "model-poster.svg", model_svg) + write_text(placeholders_dir / "scene-fallback.svg", scene_svg) + write_text(assets_dir / "model-poster.svg", model_svg) + write_text(assets_dir / "scene-fallback.svg", scene_svg) + assets.extend([ + { + "id": "generated-model-poster", + "path": "assets/placeholders/model-poster.svg", + "type": "image/svg+xml", + "status": "generated", + "retrieval": "generated-placeholder", + "provenance": "generated locally by Artic show", + "kind": "generated-placeholder", + "title": "3D model poster placeholder", + "source": "generated locally by Artic show", + "license": "Artic generated preview placeholder", + "license_status": "preview-only-generated", + "usage": "3D poster fallback before verified GLB/model asset is supplied", + "downloaded": False, + "local_path": "assets/placeholders/model-poster.svg", + }, + { + "id": "generated-model-poster-root-alias", + "path": "assets/model-poster.svg", + "type": "image/svg+xml", + "status": "generated", + "retrieval": "generated-placeholder", + "provenance": "generated locally by Artic show", + "kind": "generated-placeholder", + "title": "3D model poster placeholder root alias", + "source": "generated locally by Artic show", + "license": "Artic generated preview placeholder", + "license_status": "preview-only-generated", + "usage": "backward-compatible 3D poster fallback alias", + "downloaded": False, + "local_path": "assets/model-poster.svg", + }, + { + "id": "generated-scene-fallback", + "path": "assets/placeholders/scene-fallback.svg", + "type": "image/svg+xml", + "status": "generated", + "retrieval": "generated-placeholder", + "provenance": "generated locally by Artic show", + "kind": "generated-placeholder", + "title": "3D scene fallback placeholder", + "source": "generated locally by Artic show", + "license": "Artic generated preview placeholder", + "license_status": "preview-only-generated", + "usage": "runtime failure and reduced-motion scene fallback", + "downloaded": False, + "local_path": "assets/placeholders/scene-fallback.svg", + }, + { + "id": "generated-scene-fallback-root-alias", + "path": "assets/scene-fallback.svg", + "type": "image/svg+xml", + "status": "generated", + "retrieval": "generated-placeholder", + "provenance": "generated locally by Artic show", + "kind": "generated-placeholder", + "title": "3D scene fallback placeholder root alias", + "source": "generated locally by Artic show", + "license": "Artic generated preview placeholder", + "license_status": "preview-only-generated", + "usage": "backward-compatible runtime failure fallback alias", + "downloaded": False, + "local_path": "assets/scene-fallback.svg", + }, + ]) + return { + "schema_version": 1, + "mode": "asset-first-preview", + "asset_mode": asset_mode, + "iteration": iteration, + "variant": variant, + "root": str(root.resolve()), + "created_at": datetime.now(timezone.utc).isoformat(), + "notes": ["Unknown or unverified sources are recorded as preview-only/catalog-reference and do not block preview generation."], + "assets": assets, + } + + +def render_css(tokens: dict[str, Any], variant: str, runtime_3d: bool) -> str: + c = tokens["colors"] + hero_background = { + "asset-hero": "radial-gradient(circle at 80% 10%, color-mix(in srgb, var(--accent) 18%, transparent), transparent 34%), linear-gradient(180deg, var(--neutral), var(--surface))", + "conversion-proof": "linear-gradient(180deg, var(--surface), var(--neutral))", + "immersive-runtime": "radial-gradient(circle at 62% 20%, color-mix(in srgb, var(--accent) 24%, transparent), transparent 32%), radial-gradient(circle at 20% 20%, color-mix(in srgb, var(--primary) 18%, transparent), transparent 26%), var(--surface)", + "editorial-system": "linear-gradient(135deg, var(--neutral), var(--surface) 48%, color-mix(in srgb, var(--primary) 8%, var(--surface)))", + }.get(variant, "linear-gradient(180deg, var(--neutral), var(--surface))") + return f""":root {{ + --primary: {c['primary']}; --accent: {c['accent']}; --surface: {c['surface']}; --neutral: {c['neutral']}; --text: {c['text']}; --muted: {c['muted']}; --border: {c['border']}; + --radius-sm: {tokens['radius']['sm']}; --radius-md: {tokens['radius']['md']}; --radius-lg: {tokens['radius']['lg']}; --radius-xl: {tokens['radius']['xl']}; --shadow-card: {tokens['shadow']['card']}; --shadow-cta: {tokens['shadow']['cta']}; +}} +* {{ box-sizing: border-box; }} +html {{ scroll-behavior: smooth; }} +body {{ margin: 0; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: var(--text); background: {hero_background}; }} +a {{ color: inherit; }} +.page {{ width: min(1160px, calc(100% - 32px)); margin: 0 auto; padding: 28px 0 64px; }} +.nav {{ display: flex; justify-content: space-between; align-items: center; gap: 16px; margin-bottom: clamp(36px, 8vw, 76px); }} +.brand {{ font-weight: 850; letter-spacing: -0.045em; font-size: 1.12rem; }} +.pill {{ display: inline-flex; align-items: center; border: 1px solid var(--border); border-radius: 999px; padding: 8px 12px; color: var(--muted); background: rgba(255,255,255,.72); backdrop-filter: blur(12px); }} +.hero {{ display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(280px, .95fr); gap: clamp(24px, 5vw, 56px); align-items: center; }} +h1 {{ font-size: clamp(3rem, 8vw, 5.9rem); line-height: .91; letter-spacing: -0.075em; margin: 18px 0; max-width: 10ch; }} +h2 {{ font-size: clamp(2rem, 4vw, 3rem); letter-spacing: -0.045em; margin: 0 0 16px; }} +p {{ color: var(--muted); line-height: 1.7; font-size: 1.04rem; }} +.actions {{ display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }} +.button {{ border: 0; border-radius: var(--radius-md); padding: 14px 18px; font-weight: 780; text-decoration: none; transition: transform 180ms cubic-bezier(.2,.8,.2,1), box-shadow 180ms cubic-bezier(.2,.8,.2,1); }} +.button:focus-visible {{ outline: 3px solid color-mix(in srgb, var(--accent) 45%, white); outline-offset: 3px; }} +.button:hover {{ transform: translateY(-1px); }} +.primary {{ background: var(--primary); color: white; box-shadow: var(--shadow-cta); }} +.secondary {{ color: var(--text); border: 1px solid var(--border); background: white; }} +.panel {{ border: 1px solid var(--border); background: rgba(255,255,255,.84); border-radius: var(--radius-xl); padding: clamp(20px, 4vw, 32px); box-shadow: var(--shadow-card); }} +.visual-card {{ position: relative; overflow: hidden; min-height: 440px; display: flex; flex-direction: column; justify-content: flex-end; }} +.visual-card img {{ width: 100%; border-radius: 24px; border: 1px solid var(--border); background: var(--neutral); margin-bottom: 18px; }} +.asset-caption {{ font-size: .9rem; color: var(--muted); }} +.metric-grid {{ display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; margin-top: 18px; }} +.metric {{ padding: 18px; border-radius: 20px; background: var(--neutral); }} +.metric strong {{ display: block; font-size: 1.35rem; color: var(--primary); }} +.sections {{ display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 16px; margin-top: 56px; }} +.card {{ border: 1px solid var(--border); border-radius: 24px; padding: 22px; background: white; }} +.card b {{ display: block; margin-bottom: 8px; }} +.source-list {{ padding-left: 20px; color: var(--muted); line-height: 1.8; }} +.model-viewer-stub {{ display: grid; place-items: center; min-height: 300px; margin: 18px 0; border-radius: 28px; border: 1px dashed var(--border); color: var(--muted); background: radial-gradient(circle at 50% 36%, rgba(124,58,237,.18), transparent 34%), linear-gradient(145deg, var(--neutral), var(--surface)); font-weight: 800; letter-spacing: .08em; }} +.interaction-zone {{ border: 1px solid var(--border); border-radius: 20px; padding: 16px; background: var(--neutral); }} +.runtime-3d {{ margin-top: 24px; }} +.policy {{ margin-top: 56px; padding: 18px; border-left: 4px solid var(--accent); background: white; border-radius: 16px; color: var(--muted); }} +@media (max-width: 820px) {{ .hero, .sections {{ grid-template-columns: 1fr; }} .nav {{ align-items: flex-start; flex-direction: column; }} .visual-card {{ min-height: 0; }} h1 {{ max-width: 12ch; }} }} +@media (prefers-reduced-motion: reduce) {{ html {{ scroll-behavior: auto; }} .button {{ transition: none; }} .button:hover {{ transform: none; }} }} +""" + + +def render_html(root: Path, brief: dict[str, Any], references: dict[str, Any], strategy: dict[str, Any], design_text: str, variant: str, iteration: str, runtime_3d: bool) -> str: fm = frontmatter(design_text) name = project_name(brief, fm) description = yaml_value(fm, "description", "Artic-generated homepage preview") - primary = safe_css_color(yaml_section_value(fm, "colors", "primary", "#1F4FD8"), "#1F4FD8") - accent = safe_css_color(yaml_section_value(fm, "colors", "accent", "#7C3AED"), "#7C3AED") - surface = safe_css_color(yaml_section_value(fm, "colors", "surface", "#FFFFFF"), "#FFFFFF") - neutral = safe_css_color(yaml_section_value(fm, "colors", "neutral", "#F6F8FB"), "#F6F8FB") - text = safe_css_color(yaml_section_value(fm, "colors", "text", "#111827"), "#111827") - muted = safe_css_color(yaml_section_value(fm, "colors", "muted", "#6B7280"), "#6B7280") - border = safe_css_color(yaml_section_value(fm, "colors", "border", "#DDE3EA"), "#DDE3EA") - raw_project = brief.get("project") project = raw_project if isinstance(raw_project, dict) else {} audience = ", ".join(str(item) for item in project.get("target_users", []) if item) or "the target audience" goal = str(project.get("primary_goal") or "the primary conversion") raw_style = brief.get("style") style = raw_style if isinstance(raw_style, dict) else {} - north_star = markdown_section(design_text, "Design North Star") or str(style.get("design_north_star", "")) + north_star = markdown_section(design_text, "Design North Star") or str(style.get("design_north_star") or strategy.get("design_north_star") or "") overview = markdown_section(design_text, "Overview") or description - composition = markdown_section(design_text, "Page Composition") or "Hero, proof, features, trust, conversion, FAQ, and final CTA." + composition = markdown_section(design_text, "Page Composition") or str(strategy.get("implementation_guidance") or "Hero, proof, features, trust, conversion, FAQ, and final CTA.") sources = selected_source_names(references) locale = brief_locale(brief) copy = localized_copy(locale) policy_text = POLICY_BY_LOCALE.get(locale, POLICY_FALLBACK) - source_items = "\n".join(f"
  • {html.escape(source)}
  • " for source in sources[:5]) or "
  • Artic selected references
  • " - if has_3d_runtime_reference(brief, references): + source_items = "\n".join(f"
  • {html.escape(source)} preview-only/catalog-reference
  • " for source in sources[:5]) or "
  • Artic selected references preview-only/catalog-reference
  • " + runtime_block = "" + if runtime_3d: runtime_block = f"""
    {html.escape(copy['model_label'])} -
    model-viewer · GLB
    + \"Preview-only +
    model-viewer · GLB · poster fallback
    {html.escape(copy['interaction_title'])}

    {html.escape(copy['interaction_body'])}

    {html.escape(copy['model_body'])}

    """ - else: - runtime_block = "" - generated_at = datetime.now(timezone.utc).isoformat() + variant_label = variant.replace("-", " ").title() return f""" - + - - - {html.escape(name)} — Artic Preview - + + + + {html.escape(name)} — Artic Preview {html.escape(iteration)} + -
    -