jekyll-theme-zer0 1.28.0 → 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1143 -13
  3. data/_data/README.md +2 -0
  4. data/_data/ai.yml +5 -3
  5. data/_data/ai_pricing.yml +36 -0
  6. data/_data/backlog.yml +507 -2
  7. data/_data/consumers.yml +157 -9
  8. data/_data/features.yml +303 -20
  9. data/_data/feedback_types.yml +17 -12
  10. data/_data/i18n/fr.yml +12 -7
  11. data/_data/i18n/manifest.yml +39 -12
  12. data/_data/ingredient_densities.yml +122 -0
  13. data/_data/landing.yml +5 -2
  14. data/_data/navigation/main.yml +16 -0
  15. data/_data/navigation/quickstart.yml +4 -0
  16. data/_data/recipe_courses.yml +64 -0
  17. data/_data/site_builder.yml +874 -0
  18. data/_data/theme-manifest.yml +160 -124
  19. data/_data/ui-text.yml +26 -0
  20. data/_includes/README.md +26 -2
  21. data/_includes/analytics/posthog.html +2 -2
  22. data/_includes/components/admin-links.html +2 -2
  23. data/_includes/components/admin-tabs.html +2 -2
  24. data/_includes/components/ai-chat.html +14 -11
  25. data/_includes/components/analytics-dashboard.html +8 -8
  26. data/_includes/components/author-bio.html +1 -1
  27. data/_includes/components/author-card.html +10 -2
  28. data/_includes/components/author-eeat.html +4 -4
  29. data/_includes/components/background-customizer.html +10 -10
  30. data/_includes/components/background-image.html +114 -0
  31. data/_includes/components/background-settings.html +28 -15
  32. data/_includes/components/collection-manager.html +5 -5
  33. data/_includes/components/component-showcase.html +13 -13
  34. data/_includes/components/config-editor.html +12 -12
  35. data/_includes/components/config-viewer.html +8 -8
  36. data/_includes/components/cookie-consent.html +15 -15
  37. data/_includes/components/cta-button.html +7 -2
  38. data/_includes/components/dev-shortcuts.html +7 -7
  39. data/_includes/components/env-dashboard.html +8 -8
  40. data/_includes/components/env-switcher.html +9 -9
  41. data/_includes/components/feature-card.html +2 -2
  42. data/_includes/components/halfmoon.html +2 -2
  43. data/_includes/components/info-section.html +42 -37
  44. data/_includes/components/js-cdn.html +15 -15
  45. data/_includes/components/language-toggle.html +168 -21
  46. data/_includes/components/mermaid.html +72 -435
  47. data/_includes/components/nanobar.html +5 -5
  48. data/_includes/components/nav-editor.html +2 -2
  49. data/_includes/components/nav-export.html +2 -2
  50. data/_includes/components/nav-overview.html +2 -2
  51. data/_includes/components/page-feedback.html +45 -30
  52. data/_includes/components/page-views-init.html +55 -0
  53. data/_includes/components/page-views.html +33 -0
  54. data/_includes/components/post-card.html +22 -22
  55. data/_includes/components/post-type-badge.html +2 -2
  56. data/_includes/components/powered-by.html +2 -2
  57. data/_includes/components/preview-image.html +6 -0
  58. data/_includes/components/quick-index.html +2 -2
  59. data/_includes/components/recipe-card.html +67 -0
  60. data/_includes/components/recipe-duration.html +50 -0
  61. data/_includes/components/recipe-grams.html +58 -0
  62. data/_includes/components/recipe-index.html +96 -0
  63. data/_includes/components/recipe-ingredients.html +90 -0
  64. data/_includes/components/recipe-meta.html +96 -0
  65. data/_includes/components/recipe-nutrition.html +57 -0
  66. data/_includes/components/recipe-qty.html +73 -0
  67. data/_includes/components/recipe-ratio.html +151 -0
  68. data/_includes/components/recipe-scaler.html +73 -0
  69. data/_includes/components/recipe-steps.html +86 -0
  70. data/_includes/components/recipe-temp.html +45 -0
  71. data/_includes/components/search-modal.html +29 -4
  72. data/_includes/components/searchbar.html +2 -2
  73. data/_includes/components/shortcuts-modal.html +3 -0
  74. data/_includes/components/svg-background.html +2 -2
  75. data/_includes/components/theme-customizer.html +2 -2
  76. data/_includes/components/theme-info.html +14 -7
  77. data/_includes/components/theme-preview-gallery.html +22 -22
  78. data/_includes/content/giscus.html +2 -2
  79. data/_includes/content/intro.html +8 -8
  80. data/_includes/content/jsonld-faq.html +2 -2
  81. data/_includes/content/jsonld-software.html +24 -5
  82. data/_includes/content/seo.html +4 -4
  83. data/_includes/content/sitemap.html +27 -27
  84. data/_includes/content/toc.html +183 -183
  85. data/_includes/core/branding.html +6 -6
  86. data/_includes/core/console-capture.html +32 -74
  87. data/_includes/core/favicon.html +49 -7
  88. data/_includes/core/footer-fabs.html +17 -3
  89. data/_includes/core/footer.html +49 -34
  90. data/_includes/core/head.html +110 -86
  91. data/_includes/core/header.html +76 -54
  92. data/_includes/docs/bootstrap-docs.html +8 -8
  93. data/_includes/landing/landing-install-cards.html +2 -2
  94. data/_includes/landing/landing-quick-links.html +1 -1
  95. data/_includes/navigation/admin-nav.html +2 -2
  96. data/_includes/navigation/nav-tree.html +8 -8
  97. data/_includes/navigation/navbar.html +12 -12
  98. data/_includes/navigation/section-sidebar.html +109 -27
  99. data/_includes/navigation/sidebar-config.html +36 -2
  100. data/_includes/navigation/sidebar-left.html +17 -16
  101. data/_includes/navigation/sidebar-right.html +8 -7
  102. data/_includes/obsidian/full-graph.html +2 -2
  103. data/_includes/setup/claude-session.html +72 -0
  104. data/_includes/setup/prereq-checklist.html +90 -0
  105. data/_includes/setup/wizard.html +924 -222
  106. data/_includes/stats/stats-categories.html +8 -8
  107. data/_includes/stats/stats-header.html +14 -14
  108. data/_includes/stats/stats-metrics.html +14 -14
  109. data/_includes/stats/stats-no-data.html +12 -12
  110. data/_includes/stats/stats-overview.html +6 -6
  111. data/_includes/stats/stats-tags.html +8 -8
  112. data/_layouts/404.html +38 -24
  113. data/_layouts/README.md +2 -0
  114. data/_layouts/admin.html +24 -24
  115. data/_layouts/article.html +43 -33
  116. data/_layouts/author.html +20 -20
  117. data/_layouts/authors.html +2 -2
  118. data/_layouts/book-abc.html +12 -12
  119. data/_layouts/book-story.html +15 -15
  120. data/_layouts/book.html +12 -12
  121. data/_layouts/collection.html +33 -33
  122. data/_layouts/cookbook.html +88 -0
  123. data/_layouts/default.html +31 -28
  124. data/_layouts/home.html +23 -23
  125. data/_layouts/index.html +10 -10
  126. data/_layouts/landing.html +17 -17
  127. data/_layouts/news.html +44 -44
  128. data/_layouts/note.html +38 -38
  129. data/_layouts/notebook.html +34 -34
  130. data/_layouts/recipe.html +274 -0
  131. data/_layouts/root.html +92 -55
  132. data/_layouts/section.html +62 -33
  133. data/_layouts/setup.html +3 -3
  134. data/_layouts/sitemap-collection.html +49 -49
  135. data/_layouts/stats.html +40 -40
  136. data/_layouts/tag.html +12 -12
  137. data/_layouts/welcome.html +21 -21
  138. data/_sass/components/_callout.scss +1 -1
  139. data/_sass/components/_footer.scss +37 -1
  140. data/_sass/components/_mermaid.scss +375 -0
  141. data/_sass/components/_page-views.scss +36 -0
  142. data/_sass/components/_recipe.scss +506 -0
  143. data/_sass/components/_setup-wizard.scss +764 -0
  144. data/_sass/components/_ui-enhancements.scss +6 -6
  145. data/_sass/core/_navbar.scss +261 -46
  146. data/_sass/layouts/_landing.scss +2 -2
  147. data/_sass/layouts/_navbar-extras.scss +14 -4
  148. data/_sass/tokens/_color.scss +6 -0
  149. data/_sass/tokens/_index.scss +2 -0
  150. data/_sass/tokens/_radius.scss +21 -0
  151. data/_sass/tokens/_typography.scss +4 -0
  152. data/_sass/utilities/_focus.scss +14 -0
  153. data/assets/css/main.scss +4 -0
  154. data/assets/js/ai-chat.js +47 -5
  155. data/assets/js/fleet-feedback-capture.js +124 -0
  156. data/assets/js/fleet-feedback.js +853 -0
  157. data/assets/js/mermaid-diagrams.js +1267 -0
  158. data/assets/js/modules/navigation/config.js +9 -6
  159. data/assets/js/modules/navigation/navbar.js +55 -0
  160. data/assets/js/modules/navigation/scroll-spy.js +315 -80
  161. data/assets/js/modules/theme/appearance.js +8 -2
  162. data/assets/js/obsidian-wiki-links.js +8 -3
  163. data/assets/js/page-feedback.js +125 -192
  164. data/assets/js/page-views.js +372 -0
  165. data/assets/js/recipe-scaler.js +501 -0
  166. data/assets/js/search-modal.js +36 -0
  167. data/assets/js/setup-wizard.js +2279 -226
  168. data/assets/js/site-builder.js +1834 -0
  169. data/assets/js/ui-enhancements.js +11 -3
  170. data/scripts/README.md +44 -0
  171. data/scripts/ai/README.md +38 -0
  172. data/scripts/ai/api_call.rb +124 -0
  173. data/scripts/ai/usage.rb +314 -0
  174. data/scripts/ai/usage_report.rb +225 -0
  175. data/scripts/bin/audit-consumer +39 -7
  176. data/scripts/bin/giscus-discussions +213 -14
  177. data/scripts/bin/manifest +35 -12
  178. data/scripts/ci/agent_review_result.py +164 -0
  179. data/scripts/ci/test_agent_review_result.py +172 -0
  180. data/scripts/ci/test_visual_evidence_autogen.py +341 -0
  181. data/scripts/ci/visual_evidence_autogen.py +1060 -0
  182. data/scripts/content-review.rb +20 -1
  183. data/scripts/design-system-check.rb +170 -0
  184. data/scripts/lib/audit.sh +42 -2
  185. data/scripts/lint-liquid-raw.rb +137 -0
  186. data/scripts/test/integration/mermaid +22 -8
  187. data/scripts/test/lib/run_tests.sh +3 -1
  188. data/scripts/test/lib/test_agent_review_result.sh +27 -0
  189. data/scripts/test/lib/test_visual_evidence_autogen.sh +24 -0
  190. data/scripts/translate.rb +94 -16
  191. metadata +48 -2
@@ -1,331 +1,1033 @@
1
- {% comment %} Feature: ZER0-067 {% endcomment %}
2
- <!--
1
+ {% comment %} Feature: ZER0-067, ZER0-086, ZER0-087 {% endcomment %}
2
+ {% comment %}
3
3
  ===================================================================
4
- SETUP WIZARD — Interactive _config.yml Generator
4
+ SITE BUILDER — guided, Claude-assisted site setup wizard
5
5
  ===================================================================
6
6
 
7
- File: wizard.html
8
- Path: _includes/setup/wizard.html
9
- Purpose: Multi-step Bootstrap form that builds a complete _config.yml
10
- and lets the user download or copy it.
11
-
12
- Dependencies: Bootstrap 5.3, assets/js/setup-wizard.js
13
- Environment: Development only (guarded by calling page)
7
+ Component: setup/wizard
8
+ Path: _includes/setup/wizard.html
9
+ Purpose: Nine-step wizard that takes a reader from "nothing installed"
10
+ to a complete, personalised zer0-mistakes site: prerequisites,
11
+ identity, URLs, structure, appearance, voice, integrations and
12
+ a Build step that writes the project and starts Docker — with
13
+ an embedded AI session (Claude or Grok, the user's own token)
14
+ that can read the form, fill it in, read the theme's real
15
+ source, drive the build, and — in an open session — modify an
16
+ existing site file by file and generate images for it.
17
+ Params: none — configured via site.site_builder + site.ai_chat, data
18
+ from _data/site_builder.yml
19
+ Depends on: Bootstrap 5.3, Bootstrap Icons, assets/js/setup-wizard.js
20
+ (form, generators, drafts), assets/js/site-builder.js (Claude
21
+ session + dev-proxy tools), _sass/components/_setup-wizard.scss,
22
+ setup/prereq-checklist.html, setup/claude-session.html
23
+ Notes:
24
+ - Works with NO proxy at all: the form, every generated file, per-file
25
+ download and the self-extracting bundle are pure client-side, so a
26
+ GitHub Pages consumer on the welcome layout still gets a full wizard.
27
+ Live prerequisite checks, "Write project" and the Docker actions
28
+ light up when the local dev proxy answers GET
29
+ {site_builder.endpoint}/status; the AI panel lights up once the
30
+ chosen provider has a credential — from .env, or pasted on the
31
+ Connect step (sent once to the localhost proxy, never stored in the
32
+ page). See templates/deploy/chat-proxy/.
33
+ - Layout at lg+: stepper (2) · steps (6) · side column (4) holding the
34
+ Claude session on top and the live file preview underneath, so every
35
+ answer and every generated file stays in view on every step.
36
+ - Stable-height contract (T-040 / #408): `.wizard-panes` is a CSS grid
37
+ with every pane in one cell; `.wizard-nav` is pinned with mt-auto. Do
38
+ not put inactive panes back on display:none — see _setup-wizard.scss.
39
+ - Draft persistence: every field, switch and check is mirrored to
40
+ localStorage under `zer0-setup-draft` (debounced) and restored on load.
41
+ "Start over" clears it; a successful project write clears it too.
42
+ - Accessibility: the stepper is a vertical tablist; each pane is a
43
+ tabpanel; status regions use aria-live; icons are aria-hidden; every
44
+ control has a label; touch targets are ≥ 44px.
14
45
  ===================================================================
15
- -->
46
+ {% endcomment %}
47
+
48
+ {% assign sb = site.data.site_builder %}
49
+ {% assign sb_cfg = site.site_builder %}
50
+ {% assign sb_port = sb_cfg.default_port | default: 4000 %}
16
51
 
17
- <div class="container py-4" id="setup-wizard">
18
- <div class="row justify-content-center">
19
- <div class="col-lg-10">
52
+ <div class="container-xl py-4" id="setup-wizard" data-default-port="{{ sb_port }}">
20
53
 
21
- <h1 class="mb-2"><i class="bi bi-gear-wide-connected"></i> Site Setup Wizard</h1>
22
- <p class="lead text-muted mb-4">
23
- Fill in the form to generate a personalised <code>_config.yml</code>.
24
- Download it and replace the one in your project root.
54
+ <header class="wizard-header d-flex flex-wrap align-items-end justify-content-between gap-3 mb-4">
55
+ <div>
56
+ <p class="text-uppercase small fw-semibold text-body-secondary mb-1">
57
+ <i class="bi bi-magic" aria-hidden="true"></i> zer0-mistakes
25
58
  </p>
59
+ <h1 class="mb-1">Site Builder</h1>
60
+ <p class="lead text-body-secondary mb-0">
61
+ Answer a few questions — or just talk to Claude or Grok — and leave with a running site.
62
+ </p>
63
+ </div>
64
+ <div class="d-flex align-items-center gap-2">
65
+ <span id="wizard-draft-chip" class="wizard-draft-chip" hidden aria-live="polite">
66
+ <i class="bi bi-cloud-check" aria-hidden="true"></i> Draft saved
67
+ </span>
68
+ <button class="btn btn-outline-secondary btn-sm" type="button" id="btn-reset"
69
+ title="Clear every answer and start again">
70
+ <i class="bi bi-arrow-counterclockwise" aria-hidden="true"></i> Start over
71
+ </button>
72
+ </div>
73
+ </header>
26
74
 
27
- <!-- ============================================================ -->
28
- <!-- Progress Tabs -->
29
- <!-- ============================================================ -->
30
- <ul class="nav nav-pills nav-fill mb-4" id="wizardTabs" role="tablist">
31
- <li class="nav-item" role="presentation">
32
- <button class="nav-link active" id="tab-identity"
33
- data-bs-toggle="pill" data-bs-target="#step-identity"
34
- type="button" role="tab">
35
- <i class="bi bi-person-badge"></i> Identity
36
- </button>
37
- </li>
38
- <li class="nav-item" role="presentation">
39
- <button class="nav-link" id="tab-urls"
40
- data-bs-toggle="pill" data-bs-target="#step-urls"
41
- type="button" role="tab">
42
- <i class="bi bi-link-45deg"></i> URLs
43
- </button>
44
- </li>
45
- <li class="nav-item" role="presentation">
46
- <button class="nav-link" id="tab-collections"
47
- data-bs-toggle="pill" data-bs-target="#step-collections"
48
- type="button" role="tab">
49
- <i class="bi bi-collection"></i> Collections
50
- </button>
51
- </li>
52
- <li class="nav-item" role="presentation">
53
- <button class="nav-link" id="tab-analytics"
54
- data-bs-toggle="pill" data-bs-target="#step-analytics"
55
- type="button" role="tab">
56
- <i class="bi bi-graph-up"></i> Analytics
57
- </button>
58
- </li>
59
- <li class="nav-item" role="presentation">
60
- <button class="nav-link" id="tab-review"
61
- data-bs-toggle="pill" data-bs-target="#step-review"
62
- type="button" role="tab">
63
- <i class="bi bi-file-earmark-code"></i> Review
75
+ <div class="row g-4 wizard-grid">
76
+
77
+ {% comment %} ============================================================ {% endcomment %}
78
+ {% comment %} 1. Vertical stepper (generated from _data/site_builder.yml) {% endcomment %}
79
+ {% comment %} ============================================================ {% endcomment %}
80
+ <div class="col-lg-2">
81
+ <div class="wizard-stepper-wrap sticky-lg-top">
82
+ <div class="nav flex-column wizard-stepper" id="wizardTabs" role="tablist"
83
+ aria-orientation="vertical" aria-label="Site Builder steps">
84
+ {% for step in sb.steps %}
85
+ <button class="wizard-step{% if forloop.first %} is-active{% endif %}" id="tab-{{ step.id }}"
86
+ data-bs-toggle="pill" data-bs-target="#step-{{ step.id }}"
87
+ type="button" role="tab" aria-controls="step-{{ step.id }}"
88
+ aria-selected="{% if forloop.first %}true{% else %}false{% endif %}"
89
+ data-step="{{ forloop.index }}" data-step-id="{{ step.id }}"
90
+ title="{{ step.summary | escape }}">
91
+ <span class="wizard-step-marker" aria-hidden="true"><i class="bi {{ step.icon }}"></i></span>
92
+ <span class="wizard-step-text">
93
+ <span class="wizard-step-label">{{ step.label }}</span>
94
+ <span class="wizard-step-state visually-hidden">{% if forloop.first %}current step{% endif %}</span>
95
+ </span>
64
96
  </button>
65
- </li>
66
- </ul>
97
+ {% endfor %}
98
+ </div>
99
+ <div class="wizard-progress mt-3" aria-hidden="true">
100
+ <div class="progress" style="height: 4px;">
101
+ <div class="progress-bar" id="wizard-progress-bar" role="progressbar" style="width: 0%"></div>
102
+ </div>
103
+ <div class="small text-body-secondary mt-1" id="wizard-progress-text">Step 1 of {{ sb.steps | size }}</div>
104
+ </div>
105
+ </div>
106
+ </div>
67
107
 
68
- <!-- ============================================================ -->
69
- <!-- Step Panels -->
70
- <!-- ============================================================ -->
71
- <div class="tab-content" id="wizardTabContent">
108
+ {% comment %} ============================================================ {% endcomment %}
109
+ {% comment %} 2. Step panes {% endcomment %}
110
+ {% comment %} ============================================================ {% endcomment %}
111
+ <div class="col-lg-6">
112
+ <div class="tab-content wizard-panes" id="wizardTabContent">
72
113
 
73
- <!-- ───── Step 1: Identity ───── -->
74
- <div class="tab-pane fade show active" id="step-identity" role="tabpanel">
114
+ {% comment %} ───── Step 1: Connect ───── {% endcomment %}
115
+ <div class="tab-pane fade show active" id="step-connect" role="tabpanel" aria-labelledby="tab-connect">
75
116
  <div class="card">
76
117
  <div class="card-body">
77
- <h4 class="card-title mb-3">Site Identity</h4>
118
+ <h2 class="h4 card-title mb-1">Connect your AI</h2>
119
+ <p class="text-body-secondary">
120
+ The builder runs entirely in your browser. Connect <strong>Claude</strong> or <strong>Grok</strong> through the
121
+ <strong>local dev proxy</strong> with your own token and it can fill in these forms, check your machine, read the
122
+ theme's source, write <em>or modify</em> your project, generate images and start Docker — in the order you choose.
123
+ </p>
78
124
 
79
- <div class="mb-3">
80
- <label for="cfg-title" class="form-label">Site Title</label>
81
- <input type="text" class="form-control cfg-field" id="cfg-title"
82
- placeholder="My Awesome Site" data-key="title">
125
+ <div class="sb-connect-status alert d-flex align-items-start gap-3 mb-3" id="connect-status"
126
+ role="status" aria-live="polite" data-state="checking">
127
+ <span class="sb-status-dot mt-1" aria-hidden="true"></span>
128
+ <div class="flex-grow-1">
129
+ <div class="fw-semibold sb-status-title">Looking for the dev proxy…</div>
130
+ <div class="small sb-status-detail">Checking <code class="sb-endpoint"></code></div>
131
+ </div>
132
+ <button class="btn btn-sm btn-outline-primary" type="button" id="btn-connect-retry">
133
+ <i class="bi bi-arrow-repeat" aria-hidden="true"></i> Retry
134
+ </button>
83
135
  </div>
84
136
 
85
- <div class="mb-3">
86
- <label for="cfg-subtitle" class="form-label">Subtitle <small class="text-muted">(optional)</small></label>
87
- <input type="text" class="form-control cfg-field" id="cfg-subtitle"
88
- placeholder="A short tagline" data-key="subtitle">
89
- </div>
137
+ <fieldset class="mb-3" id="sb-providers">
138
+ <legend class="form-label fs-6 mb-2">AI provider</legend>
139
+ <div class="row g-2">
140
+ {% for p in sb.providers %}
141
+ <div class="col-sm-6">
142
+ <input type="radio" class="btn-check" name="sb_provider" id="sb-provider-{{ p.id }}"
143
+ value="{{ p.id }}" autocomplete="off" {% if forloop.first %}checked{% endif %}>
144
+ <label class="btn btn-outline-primary w-100 text-start wizard-choice sb-provider-card" for="sb-provider-{{ p.id }}">
145
+ <span class="d-flex justify-content-between align-items-center gap-2">
146
+ <span class="fw-semibold"><i class="bi {{ p.icon }}" aria-hidden="true"></i> {{ p.label }} <small class="fw-normal">{{ p.vendor }}</small></span>
147
+ <span class="badge rounded-pill text-bg-secondary sb-provider-state" data-provider="{{ p.id }}">proxy offline</span>
148
+ </span>
149
+ <span class="small d-block">{{ p.description }}</span>
150
+ </label>
151
+ </div>
152
+ {% endfor %}
153
+ </div>
154
+ </fieldset>
90
155
 
91
- <div class="mb-3">
92
- <label for="cfg-description" class="form-label">Description <small class="text-muted">(SEO, ≤ 160 chars)</small></label>
93
- <textarea class="form-control cfg-field" id="cfg-description" rows="2"
94
- maxlength="160" data-key="description"
95
- placeholder="Brief description of your site for search engines"></textarea>
96
- <div class="form-text"><span id="desc-count">0</span>/160</div>
97
- </div>
156
+ <form class="sb-credential mb-3" id="sb-credential-form" autocomplete="off">
157
+ <label for="sb-token" class="form-label fw-semibold">Token for <span class="sb-provider-name">Claude</span></label>
158
+ <div class="input-group">
159
+ <input type="password" class="form-control" id="sb-token" placeholder="sk-ant-oat01-…"
160
+ autocomplete="off" autocapitalize="off" spellcheck="false" disabled>
161
+ <button class="btn btn-primary" type="submit" id="sb-token-use" disabled>
162
+ <i class="bi bi-plug" aria-hidden="true"></i> Use for this session
163
+ </button>
164
+ </div>
165
+ <div class="form-text" id="sb-token-hint">Mint one with <code>claude setup-token</code>. The token goes once to the local proxy and stays in its memory — never in this page or your browser storage.</div>
166
+ <div class="form-check form-switch mt-2">
167
+ <input class="form-check-input" type="checkbox" id="sb-token-persist" disabled>
168
+ <label class="form-check-label small" for="sb-token-persist">Also save it to <code>.env</code> (mode 600) so the proxy remembers it next time</label>
169
+ </div>
170
+ <div class="small mt-2" id="sb-credential-status" aria-live="polite"></div>
171
+ </form>
98
172
 
99
- <div class="row">
100
- <div class="col-md-6 mb-3">
101
- <label for="cfg-author" class="form-label">Author / Owner</label>
102
- <input type="text" class="form-control cfg-field" id="cfg-author"
103
- placeholder="Your Name" data-key="founder">
173
+ <div class="row g-3 mb-3">
174
+ <div class="col-md-6">
175
+ <label for="sb-model" class="form-label">Model</label>
176
+ <select class="form-select form-select-sm" id="sb-model" disabled>
177
+ <option value="{{ sb_cfg.model | default: site.ai_chat.model | default: 'claude-opus-4-8' }}">{{ sb_cfg.model | default: site.ai_chat.model | default: 'claude-opus-4-8' }}</option>
178
+ </select>
179
+ <div class="form-text" id="sb-model-note">Sent with every request; the proxy can pin one with <code>CHAT_MODEL</code> / <code>XAI_CHAT_MODEL</code>.</div>
104
180
  </div>
105
- <div class="col-md-6 mb-3">
106
- <label for="cfg-email" class="form-label">Email</label>
107
- <input type="email" class="form-control cfg-field" id="cfg-email"
108
- placeholder="you@example.com" data-key="email">
181
+ <div class="col-md-6">
182
+ <label for="sb-image-provider" class="form-label">Image generation</label>
183
+ <select class="form-select form-select-sm" id="sb-image-provider" disabled>
184
+ <option value="">Off</option>
185
+ {% for ip in sb.image_providers %}<option value="{{ ip.id }}" data-label="{{ ip.label }} ({{ ip.vendor }})">{{ ip.label }} ({{ ip.vendor }})</option>{% endfor %}
186
+ </select>
187
+ <div class="form-text" id="sb-image-note">Renders hero, logo and post images straight into the project's <code>assets/</code>.</div>
109
188
  </div>
110
189
  </div>
111
190
 
112
- <div class="mb-3">
113
- <label for="cfg-github-user" class="form-label">GitHub Username</label>
114
- <input type="text" class="form-control cfg-field" id="cfg-github-user"
115
- placeholder="your-username" data-key="github_user">
191
+ <fieldset class="mb-3" id="sb-mode">
192
+ <legend class="form-label fs-6 mb-2">How do you want to work?</legend>
193
+ <div class="row g-2">
194
+ {% for m in sb.session_modes %}
195
+ <div class="col-sm-6">
196
+ <input type="radio" class="btn-check" name="sb_mode" id="sb-mode-{{ m.id }}" value="{{ m.id }}"
197
+ autocomplete="off" {% if m.id == sb_cfg.mode %}checked{% elsif sb_cfg.mode == nil and forloop.first %}checked{% endif %}>
198
+ <label class="btn btn-outline-secondary w-100 text-start wizard-choice" for="sb-mode-{{ m.id }}">
199
+ <span class="fw-semibold d-block"><i class="bi {{ m.icon }}" aria-hidden="true"></i> {{ m.label }}</span>
200
+ <span class="small d-block">{{ m.description }}</span>
201
+ </label>
202
+ </div>
203
+ {% endfor %}
204
+ </div>
205
+ </fieldset>
206
+
207
+ <div class="sb-connect-howto" id="connect-howto">
208
+ <h3 class="h6 text-uppercase text-body-secondary">How to connect (once, on your machine)</h3>
209
+ <ol class="sb-steps list-unstyled mb-3">
210
+ <li>
211
+ <span class="sb-step-num" aria-hidden="true">1</span>
212
+ <div>
213
+ <div class="fw-semibold">Start the dev proxy next to the site</div>
214
+ <p class="small text-body-secondary mb-1">Node 20.6+. It starts without any key and waits for the token you paste above; <code>--env-file=.env</code> loads keys you saved earlier.</p>
215
+ <div class="wizard-cmd"><code>node --env-file=.env templates/deploy/chat-proxy/dev-proxy.mjs</code><button class="btn btn-sm btn-link" type="button" data-copy="node --env-file=.env templates/deploy/chat-proxy/dev-proxy.mjs" aria-label="Copy command"><i class="bi bi-clipboard" aria-hidden="true"></i></button></div>
216
+ </div>
217
+ </li>
218
+ <li>
219
+ <span class="sb-step-num" aria-hidden="true">2</span>
220
+ <div>
221
+ <div class="fw-semibold">Get a token from the provider you picked</div>
222
+ <p class="small text-body-secondary mb-1">Claude: a personal OAuth token from the Claude Code CLI (Pro/Max) or an API key from console.anthropic.com. Grok: an API key from <a href="https://console.x.ai/" target="_blank" rel="noopener">console.x.ai</a>.</p>
223
+ <div class="wizard-cmd"><code>claude setup-token</code><button class="btn btn-sm btn-link" type="button" data-copy="claude setup-token" aria-label="Copy command"><i class="bi bi-clipboard" aria-hidden="true"></i></button></div>
224
+ </div>
225
+ </li>
226
+ <li>
227
+ <span class="sb-step-num" aria-hidden="true">3</span>
228
+ <div>
229
+ <div class="fw-semibold">Paste it above — or keep it in <code>.env</code></div>
230
+ <p class="small text-body-secondary mb-1">Pasting keeps the token in the proxy's memory for this run (tick the switch to save it). Or put it in the git-ignored <code>.env</code> yourself and restart the proxy.</p>
231
+ <div class="wizard-cmd"><code>CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-… # or XAI_API_KEY=xai-…</code></div>
232
+ </div>
233
+ </li>
234
+ </ol>
235
+ <details class="small">
236
+ <summary class="text-body-secondary">What the proxy is allowed to do</summary>
237
+ <ul class="mt-2 mb-0">
238
+ <li>Hold the token you paste in memory only; write it to <code>.env</code> (mode 600) only when you tick the switch; report it masked.</li>
239
+ <li>Run a fixed list of read-only version checks (Docker, Git, gh, VS Code, Node, Claude CLI).</li>
240
+ <li>Read text source files inside the theme checkout — never <code>.env</code>, keys or build output.</li>
241
+ <li>Write generated files into a folder beside the theme (never inside it), and read, edit, add or delete text files inside a site you open there — each change behind a confirmation card.</li>
242
+ <li>Save generated images (PNG/JPEG/WebP only) under that site's <code>assets/</code>.</li>
243
+ <li>Run <code>docker compose</code> up / ps / logs / down / build and <code>git status</code> / diff / log / init in that folder — nothing else.</li>
244
+ </ul>
245
+ </details>
116
246
  </div>
117
247
 
118
- <div class="mb-3">
119
- <label for="cfg-repo-name" class="form-label">Repository Name</label>
120
- <input type="text" class="form-control cfg-field" id="cfg-repo-name"
121
- placeholder="my-site" data-key="repository_name">
122
- <div class="form-text">The GitHub repository name for this site (e.g. <code>my-site</code>).</div>
248
+ <div class="alert alert-light border small mt-3 mb-0">
249
+ <i class="bi bi-info-circle" aria-hidden="true"></i>
250
+ No proxy? Continue anyway. Every step still works and the Build step gives you the files and the commands to run yourself.
123
251
  </div>
124
252
 
125
- <div class="text-end">
126
- <button class="btn btn-primary btn-next" data-next="tab-urls">
127
- Next: URLs <i class="bi bi-arrow-right"></i>
253
+ <div class="wizard-nav">
254
+ <button class="btn btn-primary btn-next" data-next="tab-prereqs">
255
+ Next: Prerequisites <i class="bi bi-arrow-right" aria-hidden="true"></i>
128
256
  </button>
129
257
  </div>
130
258
  </div>
131
259
  </div>
132
260
  </div>
133
261
 
134
- <!-- ───── Step 2: URLs ───── -->
135
- <div class="tab-pane fade" id="step-urls" role="tabpanel">
262
+ {% comment %} ───── Step 2: Prerequisites ───── {% endcomment %}
263
+ <div class="tab-pane fade" id="step-prereqs" role="tabpanel" aria-labelledby="tab-prereqs">
136
264
  <div class="card">
137
265
  <div class="card-body">
138
- <h4 class="card-title mb-3">URLs &amp; Deployment</h4>
266
+ <h2 class="h4 card-title mb-1">Prerequisites</h2>
267
+ <p class="text-body-secondary">
268
+ Docker runs Jekyll for you, Git and the GitHub CLI publish it. Connected? Press
269
+ <strong>Run checks</strong>. Otherwise tick each item as you install it.
270
+ </p>
271
+ {% include setup/prereq-checklist.html %}
272
+ <div class="wizard-nav d-flex justify-content-between">
273
+ <button class="btn btn-outline-secondary btn-prev" data-prev="tab-connect">
274
+ <i class="bi bi-arrow-left" aria-hidden="true"></i> Back
275
+ </button>
276
+ <button class="btn btn-primary btn-next" data-next="tab-identity">
277
+ Next: Identity <i class="bi bi-arrow-right" aria-hidden="true"></i>
278
+ </button>
279
+ </div>
280
+ </div>
281
+ </div>
282
+ </div>
283
+
284
+ {% comment %} ───── Step 3: Identity ───── {% endcomment %}
285
+ <div class="tab-pane fade" id="step-identity" role="tabpanel" aria-labelledby="tab-identity">
286
+ <div class="card">
287
+ <div class="card-body">
288
+ <h2 class="h4 card-title mb-1">Site identity</h2>
289
+ <p class="text-body-secondary">Describe the site in your own words first — the assistant uses that brief to draft everything else.</p>
139
290
 
140
291
  <div class="mb-3">
141
- <label for="cfg-url" class="form-label">Site URL</label>
142
- <input type="url" class="form-control cfg-field" id="cfg-url"
143
- placeholder="https://yourdomain.com" data-key="url">
144
- <div class="form-text">Your production URL. For GitHub Pages: <code>https://USERNAME.github.io</code></div>
292
+ <label for="cfg-brief" class="form-label fw-semibold">What is this site about, and who is it for?</label>
293
+ <textarea class="form-control cfg-field" id="cfg-brief" rows="3" data-key="brief"
294
+ placeholder="e.g. A blog about running Postgres in production, written for backend engineers who are new to operations."></textarea>
295
+ <div class="form-text d-flex justify-content-between">
296
+ <span>The brief is not published; it steers the drafts.</span>
297
+ <button class="btn btn-link btn-sm p-0 sb-ask" type="button"
298
+ data-ask="Here is my brief: {brief}. Draft a title, subtitle, tagline and a 120–160 character description for the site, then apply them.">
299
+ <i class="bi bi-stars" aria-hidden="true"></i> Draft with <span class="sb-provider-name">Claude</span>
300
+ </button>
301
+ </div>
145
302
  </div>
146
303
 
147
- <div class="mb-3">
148
- <label for="cfg-baseurl" class="form-label">Base URL <small class="text-muted">(path prefix)</small></label>
149
- <input type="text" class="form-control cfg-field" id="cfg-baseurl"
150
- placeholder="/repo-name" data-key="baseurl">
151
- <div class="form-text">Leave empty for apex domains. Use <code>/repo-name</code> for project pages.</div>
304
+ <div class="row g-3">
305
+ <div class="col-md-7">
306
+ <label for="cfg-title" class="form-label">Site title <span class="text-danger" aria-hidden="true">*</span></label>
307
+ <input type="text" class="form-control cfg-field" id="cfg-title" placeholder="My Awesome Site"
308
+ data-key="title" data-recommended="Site Title" maxlength="60" autocomplete="off">
309
+ <div class="form-text">Shown in the navbar, browser tab and feed.</div>
310
+ </div>
311
+ <div class="col-md-5">
312
+ <label for="cfg-subtitle" class="form-label">Subtitle <small class="text-body-secondary">(optional)</small></label>
313
+ <input type="text" class="form-control cfg-field" id="cfg-subtitle" placeholder="Notes from the field"
314
+ data-key="subtitle" maxlength="80" autocomplete="off">
315
+ </div>
316
+ <div class="col-12">
317
+ <label for="cfg-tagline" class="form-label">Tagline <small class="text-body-secondary">(optional)</small></label>
318
+ <input type="text" class="form-control cfg-field" id="cfg-tagline" placeholder="One line for the home hero"
319
+ data-key="tagline" maxlength="120" autocomplete="off">
320
+ </div>
321
+ <div class="col-12">
322
+ <label for="cfg-description" class="form-label">Description</label>
323
+ <textarea class="form-control cfg-field" id="cfg-description" rows="2" data-key="description"
324
+ data-recommended="Description" maxlength="240"
325
+ placeholder="A search-friendly sentence about the site."></textarea>
326
+ <div class="form-text d-flex justify-content-between">
327
+ <span>Meta description for search and social cards. Aim for 120–160 characters.</span>
328
+ <span><span id="desc-count">0</span>/160</span>
329
+ </div>
330
+ </div>
331
+ <div class="col-md-6">
332
+ <label for="cfg-author" class="form-label">Author / owner</label>
333
+ <input type="text" class="form-control cfg-field" id="cfg-author" placeholder="Your name"
334
+ data-key="founder" data-recommended="Author / Owner" autocomplete="name">
335
+ </div>
336
+ <div class="col-md-6">
337
+ <label for="cfg-email" class="form-label">Contact email</label>
338
+ <input type="email" class="form-control cfg-field" id="cfg-email" placeholder="you@example.com"
339
+ data-key="email" data-recommended="Email" autocomplete="email" aria-describedby="cfg-email-feedback">
340
+ <div class="invalid-feedback" id="cfg-email-feedback"></div>
341
+ </div>
152
342
  </div>
153
343
 
154
- <div class="mb-3">
155
- <label for="cfg-remote-theme" class="form-label">Remote Theme</label>
156
- <input type="text" class="form-control cfg-field" id="cfg-remote-theme"
157
- value="bamr87/zer0-mistakes" data-key="remote_theme">
158
- <div class="form-text">Used by GitHub Pages. Set to <code>false</code> for local development.</div>
344
+ <div class="wizard-nav d-flex justify-content-between">
345
+ <button class="btn btn-outline-secondary btn-prev" data-prev="tab-prereqs">
346
+ <i class="bi bi-arrow-left" aria-hidden="true"></i> Back
347
+ </button>
348
+ <button class="btn btn-primary btn-next" data-next="tab-urls">
349
+ Next: URLs <i class="bi bi-arrow-right" aria-hidden="true"></i>
350
+ </button>
159
351
  </div>
352
+ </div>
353
+ </div>
354
+ </div>
160
355
 
161
- <div class="mb-3">
162
- <label for="cfg-permalink" class="form-label">Permalink Style</label>
163
- <select class="form-select cfg-field" id="cfg-permalink" data-key="permalink">
164
- <option value="/:categories/:title/">/:categories/:title/</option>
165
- <option value="/blog/:year/:month/:day/:title/">Date-based</option>
166
- <option value="/:title/">Title only</option>
167
- <option value="pretty">Pretty (/:categories/:year/:month/:day/:title/)</option>
168
- </select>
356
+ {% comment %} ───── Step 4: URLs ───── {% endcomment %}
357
+ <div class="tab-pane fade" id="step-urls" role="tabpanel" aria-labelledby="tab-urls">
358
+ <div class="card">
359
+ <div class="card-body">
360
+ <h2 class="h4 card-title mb-1">GitHub &amp; URLs</h2>
361
+ <p class="text-body-secondary">Where the code lives and where the site is served from.</p>
362
+
363
+ <div class="row g-3">
364
+ <div class="col-md-6">
365
+ <label for="cfg-github-user" class="form-label">GitHub username</label>
366
+ <div class="input-group">
367
+ <span class="input-group-text"><i class="bi bi-github" aria-hidden="true"></i></span>
368
+ <input type="text" class="form-control cfg-field" id="cfg-github-user" placeholder="octocat"
369
+ data-key="github_user" data-recommended="GitHub Username" autocomplete="username">
370
+ </div>
371
+ </div>
372
+ <div class="col-md-6">
373
+ <label for="cfg-repo-name" class="form-label">Repository name</label>
374
+ <input type="text" class="form-control cfg-field" id="cfg-repo-name" placeholder="my-site"
375
+ data-key="repository_name" data-recommended="Repository Name" autocomplete="off">
376
+ <div class="form-text">Also the project folder name in the Build step.</div>
377
+ </div>
378
+
379
+ <div class="col-12">
380
+ <fieldset>
381
+ <legend class="form-label fs-6 mb-2">How will the theme be loaded?</legend>
382
+ <div class="row g-2">
383
+ {% for t in sb.deploy_targets %}
384
+ <div class="col-md-6">
385
+ <input type="radio" class="btn-check cfg-radio" name="deploy_target" id="deploy-{{ t.id }}"
386
+ value="{{ t.id }}" data-key="deploy_target" autocomplete="off"
387
+ {% if forloop.first %}checked{% endif %}>
388
+ <label class="btn btn-outline-primary w-100 text-start wizard-choice" for="deploy-{{ t.id }}">
389
+ <span class="fw-semibold d-block">{{ t.label }}</span>
390
+ <span class="small d-block">{{ t.description }}</span>
391
+ </label>
392
+ </div>
393
+ {% endfor %}
394
+ </div>
395
+ </fieldset>
396
+ </div>
397
+
398
+ <div class="col-md-8">
399
+ <label for="cfg-url" class="form-label">Site URL</label>
400
+ <div class="input-group">
401
+ <input type="url" class="form-control cfg-field" id="cfg-url" placeholder="https://octocat.github.io"
402
+ data-key="url" data-recommended="Site URL" aria-describedby="cfg-url-feedback" autocomplete="url">
403
+ <button class="btn btn-outline-secondary" type="button" id="btn-suggest-urls" title="Fill url and baseurl from the GitHub fields">
404
+ <i class="bi bi-lightning-charge" aria-hidden="true"></i> Suggest
405
+ </button>
406
+ <div class="invalid-feedback" id="cfg-url-feedback"></div>
407
+ </div>
408
+ </div>
409
+ <div class="col-md-4">
410
+ <label for="cfg-baseurl" class="form-label">Base path</label>
411
+ <input type="text" class="form-control cfg-field" id="cfg-baseurl" placeholder="/my-site"
412
+ data-key="baseurl" autocomplete="off">
413
+ <div class="form-text">Empty for <code>user.github.io</code>; <code>/repo</code> for project sites.</div>
414
+ </div>
415
+
416
+ <div class="col-md-6">
417
+ <label for="cfg-permalink" class="form-label">Permalink style</label>
418
+ <select class="form-select cfg-field" id="cfg-permalink" data-key="permalink">
419
+ <option value="/:categories/:title/">/:categories/:title/</option>
420
+ <option value="/:title/">/:title/</option>
421
+ <option value="/:year/:month/:title/">/:year/:month/:title/</option>
422
+ <option value="pretty">pretty</option>
423
+ </select>
424
+ </div>
425
+ <div class="col-md-3">
426
+ <label for="cfg-port" class="form-label">Dev port</label>
427
+ <input type="number" class="form-control cfg-field" id="cfg-port" min="1024" max="65535"
428
+ value="{{ sb_port }}" data-key="port">
429
+ </div>
430
+ <div class="col-md-3">
431
+ <label for="cfg-remote-theme" class="form-label">Remote theme</label>
432
+ <input type="text" class="form-control cfg-field" id="cfg-remote-theme" value="bamr87/zer0-mistakes"
433
+ data-key="remote_theme" autocomplete="off">
434
+ </div>
169
435
  </div>
170
436
 
171
- <div class="d-flex justify-content-between">
437
+ <div class="wizard-nav d-flex justify-content-between">
172
438
  <button class="btn btn-outline-secondary btn-prev" data-prev="tab-identity">
173
- <i class="bi bi-arrow-left"></i> Back
439
+ <i class="bi bi-arrow-left" aria-hidden="true"></i> Back
174
440
  </button>
175
- <button class="btn btn-primary btn-next" data-next="tab-collections">
176
- Next: Collections <i class="bi bi-arrow-right"></i>
441
+ <button class="btn btn-primary btn-next" data-next="tab-structure">
442
+ Next: Structure <i class="bi bi-arrow-right" aria-hidden="true"></i>
177
443
  </button>
178
444
  </div>
179
445
  </div>
180
446
  </div>
181
447
  </div>
182
448
 
183
- <!-- ───── Step 3: Collections ───── -->
184
- <div class="tab-pane fade" id="step-collections" role="tabpanel">
449
+ {% comment %} ───── Step 5: Structure ───── {% endcomment %}
450
+ <div class="tab-pane fade" id="step-structure" role="tabpanel" aria-labelledby="tab-structure">
185
451
  <div class="card">
186
452
  <div class="card-body">
187
- <h4 class="card-title mb-3">Content Collections</h4>
188
- <p class="text-muted">Toggle which content collections to enable.</p>
453
+ <h2 class="h4 card-title mb-1">Structure</h2>
454
+ <p class="text-body-secondary">Pick a starting shape, then fine-tune the collections and the top navigation.</p>
189
455
 
190
- <div class="form-check form-switch mb-3">
191
- <input class="form-check-input cfg-collection" type="checkbox" id="col-posts" checked data-col="posts">
192
- <label class="form-check-label" for="col-posts">
193
- <strong>Blog Posts</strong> — <code>pages/_posts/</code>
194
- </label>
195
- </div>
456
+ <fieldset class="mb-3">
457
+ <legend class="form-label fs-6 mb-2">Kind of site</legend>
458
+ <div class="d-flex flex-wrap gap-2">
459
+ {% for st in sb.site_types %}
460
+ <input type="radio" class="btn-check cfg-radio" name="site_type" id="site-type-{{ st.id }}"
461
+ value="{{ st.id }}" data-key="site_type" data-collections="{{ st.collections | join: ',' }}" autocomplete="off">
462
+ <label class="btn btn-outline-secondary btn-sm rounded-pill" for="site-type-{{ st.id }}">{{ st.label }}</label>
463
+ {% endfor %}
464
+ </div>
465
+ </fieldset>
196
466
 
197
- <div class="form-check form-switch mb-3">
198
- <input class="form-check-input cfg-collection" type="checkbox" id="col-docs" checked data-col="docs">
199
- <label class="form-check-label" for="col-docs">
200
- <strong>Documentation</strong> — <code>pages/_docs/</code>
201
- </label>
202
- </div>
467
+ <fieldset class="mb-3">
468
+ <legend class="form-label fs-6 mb-2">Collections</legend>
469
+ <div class="row g-2">
470
+ {% for c in sb.collections %}
471
+ <div class="col-sm-6">
472
+ <div class="form-check wizard-check">
473
+ <input class="form-check-input cfg-collection" type="checkbox" id="col-{{ c.id }}"
474
+ data-col="{{ c.id }}" data-layout="{{ c.layout }}" data-permalink="{{ c.permalink }}"
475
+ {% if c.default %}checked{% endif %}>
476
+ <label class="form-check-label" for="col-{{ c.id }}">
477
+ <i class="bi {{ c.icon }} me-1" aria-hidden="true"></i><strong>{{ c.label }}</strong>
478
+ <span class="d-block small text-body-secondary">{{ c.description }}</span>
479
+ </label>
480
+ </div>
481
+ </div>
482
+ {% endfor %}
483
+ </div>
484
+ </fieldset>
203
485
 
204
- <div class="form-check form-switch mb-3">
205
- <input class="form-check-input cfg-collection" type="checkbox" id="col-about" checked data-col="about">
206
- <label class="form-check-label" for="col-about">
207
- <strong>About Pages</strong> — <code>pages/_about/</code>
208
- </label>
486
+ <div class="row g-3 mb-3">
487
+ <div class="col-md-6">
488
+ <fieldset>
489
+ <legend class="form-label fs-6 mb-2">Navigation style</legend>
490
+ <div class="d-flex flex-wrap gap-2">
491
+ {% for n in sb.navigation_styles %}
492
+ <input type="radio" class="btn-check cfg-radio" name="nav_style" id="nav-style-{{ n.id }}" value="{{ n.id }}"
493
+ data-key="plan.navigation.style" autocomplete="off" {% if forloop.first %}checked{% endif %}>
494
+ <label class="btn btn-outline-secondary btn-sm rounded-pill" for="nav-style-{{ n.id }}" title="{{ n.description | escape }}">{{ n.label }}</label>
495
+ {% endfor %}
496
+ </div>
497
+ </fieldset>
498
+ </div>
499
+ <div class="col-md-6">
500
+ <label for="cfg-sidebar-mode" class="form-label fs-6 mb-2">Sidebar</label>
501
+ <select class="form-select form-select-sm cfg-field" id="cfg-sidebar-mode" data-key="plan.navigation.sidebar">
502
+ {% for m in sb.sidebar_modes %}<option value="{{ m.id }}" title="{{ m.description | escape }}">{{ m.label }} — {{ m.description }}</option>{% endfor %}
503
+ </select>
504
+ </div>
209
505
  </div>
210
506
 
211
- <div class="form-check form-switch mb-3">
212
- <input class="form-check-input cfg-collection" type="checkbox" id="col-notes" data-col="notes">
213
- <label class="form-check-label" for="col-notes">
214
- <strong>Notes</strong> — <code>pages/_notes/</code>
215
- </label>
216
- </div>
507
+ <fieldset class="mb-3">
508
+ <legend class="form-label fs-6 mb-2 d-flex justify-content-between align-items-center">
509
+ <span>Landing page</span>
510
+ <button class="btn btn-link btn-sm p-0 sb-ask" type="button"
511
+ data-ask="Design the landing page for my site: pick the template, write the hero (eyebrow, headline, subheadline, up to three CTAs) and fill every section with real copy from my brief — then apply it with set_site_plan.">
512
+ <i class="bi bi-stars" aria-hidden="true"></i> Design with <span class="sb-provider-name">Claude</span>
513
+ </button>
514
+ </legend>
515
+ <div class="row g-2">
516
+ {% for t in sb.landing_templates %}
517
+ <div class="col-sm-6 col-lg-4">
518
+ <input type="radio" class="btn-check cfg-radio" name="landing_template" id="landing-{{ t.id }}" value="{{ t.id }}"
519
+ data-key="plan.landing.template" autocomplete="off" {% if t.id == 'hero' %}checked{% endif %}>
520
+ <label class="btn btn-outline-primary w-100 text-start wizard-choice" for="landing-{{ t.id }}">
521
+ <span class="fw-semibold d-block">{{ t.label }}</span>
522
+ <span class="small d-block">{{ t.description }}</span>
523
+ </label>
524
+ </div>
525
+ {% endfor %}
526
+ </div>
527
+ <div class="form-text" id="landing-summary" aria-live="polite"></div>
528
+ </fieldset>
217
529
 
218
- <div class="form-check form-switch mb-3">
219
- <input class="form-check-input cfg-collection" type="checkbox" id="col-notebooks" data-col="notebooks">
220
- <label class="form-check-label" for="col-notebooks">
221
- <strong>Notebooks</strong> — <code>pages/_notebooks/</code>
222
- </label>
223
- </div>
530
+ <fieldset class="mb-3">
531
+ <legend class="form-label fs-6 mb-2 d-flex justify-content-between align-items-center">
532
+ <span>Example pages <span class="badge text-bg-secondary rounded-pill" id="pages-count">0</span></span>
533
+ <span>
534
+ <button class="btn btn-link btn-sm p-0 me-2 sb-ask" type="button"
535
+ data-ask="Plan five or six example pages for my site across the enabled collections — each with a slug, title, description and a 150–300 word Markdown body in the {tone} tone for {audience} — then apply them with set_site_plan.">
536
+ <i class="bi bi-stars" aria-hidden="true"></i> Plan with <span class="sb-provider-name">Claude</span>
537
+ </button>
538
+ <button class="btn btn-link btn-sm p-0" type="button" id="btn-page-add">
539
+ <i class="bi bi-plus-circle" aria-hidden="true"></i> Add page
540
+ </button>
541
+ </span>
542
+ </legend>
543
+ <div id="pages-planner" class="pages-planner" aria-live="polite"></div>
544
+ <div class="form-text">Each row becomes a real file with front matter. Bodies come from the assistant, or are drafted from the title.</div>
545
+ </fieldset>
224
546
 
225
- <div class="d-flex justify-content-between">
547
+ <fieldset>
548
+ <legend class="form-label fs-6 mb-2 d-flex justify-content-between align-items-center">
549
+ <span>Main navigation</span>
550
+ <span>
551
+ <button class="btn btn-link btn-sm p-0 me-2" type="button" id="btn-nav-regenerate" title="Rebuild the menu from the selected collections">
552
+ <i class="bi bi-arrow-repeat" aria-hidden="true"></i> From collections
553
+ </button>
554
+ <button class="btn btn-link btn-sm p-0" type="button" id="btn-nav-add">
555
+ <i class="bi bi-plus-circle" aria-hidden="true"></i> Add item
556
+ </button>
557
+ </span>
558
+ </legend>
559
+ <div id="nav-editor" class="nav-editor" aria-live="polite"></div>
560
+ </fieldset>
561
+
562
+ <div class="wizard-nav d-flex justify-content-between">
226
563
  <button class="btn btn-outline-secondary btn-prev" data-prev="tab-urls">
227
- <i class="bi bi-arrow-left"></i> Back
564
+ <i class="bi bi-arrow-left" aria-hidden="true"></i> Back
228
565
  </button>
229
- <button class="btn btn-primary btn-next" data-next="tab-analytics">
230
- Next: Analytics <i class="bi bi-arrow-right"></i>
566
+ <button class="btn btn-primary btn-next" data-next="tab-appearance">
567
+ Next: Appearance <i class="bi bi-arrow-right" aria-hidden="true"></i>
231
568
  </button>
232
569
  </div>
233
570
  </div>
234
571
  </div>
235
572
  </div>
236
573
 
237
- <!-- ───── Step 4: Analytics ───── -->
238
- <div class="tab-pane fade" id="step-analytics" role="tabpanel">
574
+ {% comment %} ───── Step 6: Appearance ───── {% endcomment %}
575
+ <div class="tab-pane fade" id="step-appearance" role="tabpanel" aria-labelledby="tab-appearance">
239
576
  <div class="card">
240
577
  <div class="card-body">
241
- <h4 class="card-title mb-3">Analytics &amp; Integrations</h4>
578
+ <h2 class="h4 card-title mb-1">Appearance</h2>
579
+ <p class="text-body-secondary">Seven skins re-colour every component and background layer. Preview any of them on this page.</p>
242
580
 
243
- <div class="mb-3">
244
- <label for="cfg-ga" class="form-label">Google Analytics ID <small class="text-muted">(optional)</small></label>
245
- <input type="text" class="form-control cfg-field" id="cfg-ga"
246
- placeholder="G-XXXXXXXXXX" data-key="google_analytics">
581
+ <fieldset class="mb-3">
582
+ <legend class="form-label fs-6 mb-2">Skin</legend>
583
+ <div class="row g-2 skin-grid">
584
+ {% for s in sb.skins %}
585
+ <div class="col-6 col-md-4">
586
+ <input type="radio" class="btn-check cfg-radio" name="theme_skin" id="skin-{{ s.id }}"
587
+ value="{{ s.id }}" data-key="theme_skin" autocomplete="off" {% if forloop.first %}checked{% endif %}>
588
+ <label class="skin-card" for="skin-{{ s.id }}">
589
+ <span class="skin-swatch" aria-hidden="true"
590
+ style="--sw-a: {{ s.primary }}; --sw-b: {{ s.accent }};"></span>
591
+ <span class="skin-name">{{ s.label }}</span>
592
+ <span class="skin-mood">{{ s.mood }}</span>
593
+ </label>
594
+ </div>
595
+ {% endfor %}
596
+ </div>
597
+ <div class="form-text d-flex justify-content-between align-items-center flex-wrap gap-2">
598
+ <span>Applies <code>theme_skin</code> in <code>_config.yml</code>.</span>
599
+ <span class="d-flex gap-2">
600
+ <button class="btn btn-link btn-sm p-0 sb-ask" type="button"
601
+ data-ask="Generate a hero image for the landing page that fits my brief, the {theme_skin} skin and the chosen palette (no text in the image), save it as assets/images/hero.png in my project, and set landing.hero.image to /assets/images/hero.png.">
602
+ <i class="bi bi-image" aria-hidden="true"></i> Hero image with <span class="sb-provider-name">Claude</span>
603
+ </button>
604
+ <button class="btn btn-outline-secondary btn-sm" type="button" id="btn-skin-preview" aria-pressed="false">
605
+ <i class="bi bi-eye" aria-hidden="true"></i> Preview on this page
606
+ </button>
607
+ </span>
608
+ </div>
609
+ </fieldset>
610
+
611
+ <div class="row g-3">
612
+ <div class="col-md-6">
613
+ <label for="cfg-color-mode" class="form-label">Colour mode</label>
614
+ <select class="form-select cfg-field" id="cfg-color-mode" data-key="color_mode_default">
615
+ {% for m in sb.color_modes %}<option value="{{ m.id }}">{{ m.label }}</option>{% endfor %}
616
+ </select>
617
+ </div>
618
+ <div class="col-md-6 d-flex flex-column justify-content-end gap-2">
619
+ <div class="form-check form-switch">
620
+ <input class="form-check-input cfg-switch" type="checkbox" id="cfg-color-lock" data-key="color_mode_lock">
621
+ <label class="form-check-label" for="cfg-color-lock">Lock the colour mode (hide the toggle)</label>
622
+ </div>
623
+ <div class="form-check form-switch">
624
+ <input class="form-check-input cfg-switch" type="checkbox" id="cfg-backgrounds" data-key="theme_background" checked>
625
+ <label class="form-check-label" for="cfg-backgrounds">SVG background layers (gradient, pattern, noise)</label>
626
+ </div>
627
+ </div>
628
+ <div class="col-12">
629
+ <fieldset>
630
+ <legend class="form-label fs-6 mb-2">Palette <small class="text-body-secondary">layered over the skin</small></legend>
631
+ <div class="d-flex flex-wrap gap-2 palette-grid">
632
+ {% for p in sb.palettes %}
633
+ <input type="radio" class="btn-check cfg-radio" name="palette" id="palette-{{ p.id }}" value="{{ p.id }}"
634
+ data-key="plan.theme.palette.preset" autocomplete="off" {% if forloop.first %}checked{% endif %}>
635
+ <label class="palette-card" for="palette-{{ p.id }}" title="{{ p.description | default: p.label | escape }}">
636
+ <span class="palette-swatches" aria-hidden="true">
637
+ {% if p.primary %}
638
+ <span style="background: {{ p.primary }}"></span><span style="background: {{ p.secondary }}"></span><span style="background: {{ p.accent }}"></span>
639
+ {% else %}
640
+ <span class="palette-swatch-skin"></span>
641
+ {% endif %}
642
+ </span>
643
+ <span class="palette-name">{{ p.label }}</span>
644
+ </label>
645
+ {% endfor %}
646
+ <input type="radio" class="btn-check cfg-radio" name="palette" id="palette-custom" value="custom" data-key="plan.theme.palette.preset" autocomplete="off">
647
+ <label class="palette-card" for="palette-custom" title="Pick your own three colours">
648
+ <span class="palette-swatches palette-swatches--custom" aria-hidden="true"><i class="bi bi-eyedropper"></i></span>
649
+ <span class="palette-name">Custom</span>
650
+ </label>
651
+ </div>
652
+ <div class="row g-2 mt-2 wizard-subfields" data-for="palette-custom" hidden>
653
+ <div class="col-4"><label for="cfg-color-primary" class="form-label small mb-1">Primary</label><input type="color" class="form-control form-control-color cfg-field" id="cfg-color-primary" data-key="plan.theme.palette.primary" value="#0d6efd"></div>
654
+ <div class="col-4"><label for="cfg-color-secondary" class="form-label small mb-1">Secondary</label><input type="color" class="form-control form-control-color cfg-field" id="cfg-color-secondary" data-key="plan.theme.palette.secondary" value="#6c757d"></div>
655
+ <div class="col-4"><label for="cfg-color-accent" class="form-label small mb-1">Accent</label><input type="color" class="form-control form-control-color cfg-field" id="cfg-color-accent" data-key="plan.theme.palette.accent" value="#20c997"></div>
656
+ </div>
657
+ </fieldset>
658
+ </div>
659
+ <div class="col-md-7">
660
+ <label for="cfg-fonts" class="form-label">Fonts</label>
661
+ <select class="form-select cfg-field" id="cfg-fonts" data-key="plan.theme.fonts">
662
+ {% for fp in sb.font_pairings %}<option value="{{ fp.id }}">{{ fp.label }} — {{ fp.description }}</option>{% endfor %}
663
+ </select>
664
+ <div class="form-text font-preview" id="font-preview">The quick brown fox jumps over the lazy dog. <strong>Headline weight</strong>.</div>
665
+ </div>
666
+ <div class="col-md-5">
667
+ <fieldset>
668
+ <legend class="form-label fs-6 mb-2">Corners</legend>
669
+ <div class="btn-group w-100" role="group" aria-label="Corner radius">
670
+ {% for r in sb.radius_options %}
671
+ <input type="radio" class="btn-check cfg-radio" name="radius" id="radius-{{ r.id }}" value="{{ r.id }}" data-key="plan.theme.radius" autocomplete="off" {% if r.id == 'soft' %}checked{% endif %}>
672
+ <label class="btn btn-outline-secondary" for="radius-{{ r.id }}">{{ r.label }}</label>
673
+ {% endfor %}
674
+ </div>
675
+ </fieldset>
676
+ </div>
677
+ <div class="col-md-6">
678
+ <label for="cfg-title-icon" class="form-label">Navbar icon <small class="text-body-secondary">(Bootstrap icon name)</small></label>
679
+ <div class="input-group">
680
+ <span class="input-group-text"><i class="bi bi-robot" id="cfg-title-icon-preview" aria-hidden="true"></i></span>
681
+ <input type="text" class="form-control cfg-field" id="cfg-title-icon" value="robot" data-key="title_icon" autocomplete="off">
682
+ </div>
683
+ </div>
684
+ </div>
685
+
686
+ <div class="wizard-nav d-flex justify-content-between">
687
+ <button class="btn btn-outline-secondary btn-prev" data-prev="tab-structure">
688
+ <i class="bi bi-arrow-left" aria-hidden="true"></i> Back
689
+ </button>
690
+ <button class="btn btn-primary btn-next" data-next="tab-voice">
691
+ Next: Voice <i class="bi bi-arrow-right" aria-hidden="true"></i>
692
+ </button>
247
693
  </div>
694
+ </div>
695
+ </div>
696
+ </div>
248
697
 
249
- <div class="mb-4">
250
- <label class="form-label">PostHog Analytics</label>
251
- <div class="form-check form-switch mb-2">
252
- <input class="form-check-input" type="checkbox" id="cfg-posthog-enabled" data-key="posthog.enabled">
253
- <label class="form-check-label" for="cfg-posthog-enabled">Enable PostHog</label>
698
+ {% comment %} ───── Step 7: Voice ───── {% endcomment %}
699
+ <div class="tab-pane fade" id="step-voice" role="tabpanel" aria-labelledby="tab-voice">
700
+ <div class="card">
701
+ <div class="card-body">
702
+ <h2 class="h4 card-title mb-1">Voice &amp; first content</h2>
703
+ <p class="text-body-secondary">Set the tone, then let the assistant draft the welcome post and about page — or write them yourself.</p>
704
+
705
+ <div class="row g-3 mb-3">
706
+ <div class="col-md-7">
707
+ <fieldset>
708
+ <legend class="form-label fs-6 mb-2">Tone</legend>
709
+ <div class="d-flex flex-wrap gap-2">
710
+ {% for t in sb.tones %}
711
+ <input type="radio" class="btn-check cfg-radio" name="tone" id="tone-{{ t.id }}" value="{{ t.id }}"
712
+ data-key="tone" autocomplete="off" {% if forloop.first %}checked{% endif %}>
713
+ <label class="btn btn-outline-secondary btn-sm rounded-pill" for="tone-{{ t.id }}" title="{{ t.description | escape }}">{{ t.label }}</label>
714
+ {% endfor %}
715
+ </div>
716
+ </fieldset>
717
+ </div>
718
+ <div class="col-md-5">
719
+ <label for="cfg-audience" class="form-label">Audience</label>
720
+ <select class="form-select cfg-field" id="cfg-audience" data-key="audience">
721
+ {% for a in sb.audiences %}<option value="{{ a.id }}">{{ a.label }}</option>{% endfor %}
722
+ </select>
254
723
  </div>
255
- <input type="text" class="form-control cfg-field" id="cfg-posthog-key"
256
- placeholder="phc_your_key_here" data-key="posthog.api_key">
257
724
  </div>
258
725
 
259
726
  <div class="mb-3">
260
- <label class="form-label">Social Links <small class="text-muted">(optional)</small></label>
261
- <div class="row g-2">
262
- <div class="col-md-6">
263
- <div class="input-group">
264
- <span class="input-group-text"><i class="bi bi-twitter-x"></i></span>
265
- <input type="text" class="form-control cfg-field" placeholder="username" data-key="twitter_username">
727
+ <label for="cfg-welcome-title" class="form-label d-flex justify-content-between">
728
+ <span>Welcome post title</span>
729
+ <button class="btn btn-link btn-sm p-0 sb-ask" type="button"
730
+ data-ask="Using my brief and the {tone} tone for {audience}, write the welcome post (title + 150–250 words of Markdown body) and the about page (100–200 words), then apply both.">
731
+ <i class="bi bi-stars" aria-hidden="true"></i> Draft both with <span class="sb-provider-name">Claude</span>
732
+ </button>
733
+ </label>
734
+ <input type="text" class="form-control cfg-field" id="cfg-welcome-title" data-key="welcome_title"
735
+ placeholder="Hello, world" autocomplete="off">
736
+ </div>
737
+ <div class="mb-3">
738
+ <label for="cfg-welcome-body" class="form-label">Welcome post body <small class="text-body-secondary">(Markdown)</small></label>
739
+ <textarea class="form-control cfg-field font-monospace small" id="cfg-welcome-body" rows="5" data-key="welcome_body"
740
+ placeholder="Why this site exists, what readers will find, what is coming next."></textarea>
741
+ </div>
742
+ <div>
743
+ <label for="cfg-about-body" class="form-label">About page <small class="text-body-secondary">(Markdown)</small></label>
744
+ <textarea class="form-control cfg-field font-monospace small" id="cfg-about-body" rows="4" data-key="about_body"
745
+ placeholder="Who you are, what you do, how to reach you."></textarea>
746
+ </div>
747
+
748
+ <div class="wizard-nav d-flex justify-content-between">
749
+ <button class="btn btn-outline-secondary btn-prev" data-prev="tab-appearance">
750
+ <i class="bi bi-arrow-left" aria-hidden="true"></i> Back
751
+ </button>
752
+ <button class="btn btn-primary btn-next" data-next="tab-integrations">
753
+ Next: Integrations <i class="bi bi-arrow-right" aria-hidden="true"></i>
754
+ </button>
755
+ </div>
756
+ </div>
757
+ </div>
758
+ </div>
759
+
760
+ {% comment %} ───── Step 8: Integrations ───── {% endcomment %}
761
+ <div class="tab-pane fade" id="step-integrations" role="tabpanel" aria-labelledby="tab-integrations">
762
+ <div class="card">
763
+ <div class="card-body">
764
+ <h2 class="h4 card-title mb-1">Integrations</h2>
765
+ <p class="text-body-secondary">Everything here is off-by-default safe: nothing sends data until you add a key, and keys never go in <code>_config.yml</code>.</p>
766
+
767
+ <div class="list-group wizard-integrations mb-3">
768
+ {% for i in sb.integrations %}
769
+ <div class="list-group-item">
770
+ <div class="form-check form-switch d-flex align-items-start gap-3 ps-0">
771
+ <input class="form-check-input cfg-switch ms-0 mt-1 flex-shrink-0" type="checkbox" id="int-{{ i.id }}"
772
+ data-key="integration.{{ i.id }}" {% if i.default %}checked{% endif %}>
773
+ <label class="form-check-label flex-grow-1" for="int-{{ i.id }}">
774
+ <i class="bi {{ i.icon }} me-1" aria-hidden="true"></i><strong>{{ i.label }}</strong>
775
+ <span class="d-block small text-body-secondary">{{ i.description }}</span>
776
+ </label>
777
+ </div>
778
+ {% if i.id == 'giscus' %}
779
+ <div class="row g-2 mt-2 wizard-subfields" data-for="int-giscus">
780
+ <div class="col-md-6">
781
+ <label for="cfg-giscus-repo-id" class="form-label small mb-1">Repo ID</label>
782
+ <input type="text" class="form-control form-control-sm cfg-field" id="cfg-giscus-repo-id" data-key="giscus.repo_id" placeholder="R_kgDO…" autocomplete="off">
783
+ </div>
784
+ <div class="col-md-6">
785
+ <label for="cfg-giscus-category-id" class="form-label small mb-1">Category ID</label>
786
+ <input type="text" class="form-control form-control-sm cfg-field" id="cfg-giscus-category-id" data-key="giscus.category_id" placeholder="DIC_kwDO…" autocomplete="off">
787
+ </div>
788
+ <div class="col-12 form-text">Get both from <a href="https://giscus.app" target="_blank" rel="noopener">giscus.app</a> after enabling Discussions on the repo.</div>
789
+ </div>
790
+ {% endif %}
791
+ {% if i.id == 'posthog' %}
792
+ <div class="row g-2 mt-2 wizard-subfields" data-for="int-posthog">
793
+ <div class="col-12">
794
+ <label for="cfg-posthog-key" class="form-label small mb-1">PostHog project key</label>
795
+ <input type="text" class="form-control form-control-sm cfg-field" id="cfg-posthog-key" data-key="posthog.api_key" placeholder="phc_…" autocomplete="off">
266
796
  </div>
267
797
  </div>
268
- <div class="col-md-6">
269
- <div class="input-group">
270
- <span class="input-group-text"><i class="bi bi-linkedin"></i></span>
271
- <input type="text" class="form-control cfg-field" placeholder="username" data-key="linkedin_username">
798
+ {% endif %}
799
+ {% if i.id == 'ai_chat' %}
800
+ <div class="row g-2 mt-2 wizard-subfields" data-for="int-ai_chat">
801
+ <div class="col-md-6">
802
+ <label for="cfg-ai-provider" class="form-label small mb-1">Chat provider</label>
803
+ <select class="form-select form-select-sm cfg-field" id="cfg-ai-provider" data-key="ai_provider">
804
+ {% for p in sb.providers %}<option value="{{ p.id }}">{{ p.label }} ({{ p.vendor }})</option>{% endfor %}
805
+ </select>
272
806
  </div>
807
+ <div class="col-12 form-text">Written as <code>ai_chat.provider</code>; the site's chat proxy needs the matching key (<code>.env.example</code> lists it).</div>
273
808
  </div>
809
+ {% endif %}
274
810
  </div>
811
+ {% endfor %}
275
812
  </div>
276
813
 
277
- <div class="d-flex justify-content-between">
278
- <button class="btn btn-outline-secondary btn-prev" data-prev="tab-collections">
279
- <i class="bi bi-arrow-left"></i> Back
814
+ <div class="row g-3">
815
+ <div class="col-md-6">
816
+ <label for="cfg-ga" class="form-label">Google Analytics 4 ID <small class="text-body-secondary">(optional)</small></label>
817
+ <input type="text" class="form-control cfg-field" id="cfg-ga" placeholder="G-XXXXXXXXXX" data-key="google_analytics" autocomplete="off">
818
+ </div>
819
+ <div class="col-md-3">
820
+ <label for="cfg-twitter" class="form-label">X / Twitter</label>
821
+ <div class="input-group">
822
+ <span class="input-group-text"><i class="bi bi-twitter-x" aria-hidden="true"></i></span>
823
+ <input type="text" class="form-control cfg-field" id="cfg-twitter" placeholder="handle" data-key="twitter_username" autocomplete="off">
824
+ </div>
825
+ </div>
826
+ <div class="col-md-3">
827
+ <label for="cfg-linkedin" class="form-label">LinkedIn</label>
828
+ <div class="input-group">
829
+ <span class="input-group-text"><i class="bi bi-linkedin" aria-hidden="true"></i></span>
830
+ <input type="text" class="form-control cfg-field" id="cfg-linkedin" placeholder="handle" data-key="linkedin_username" autocomplete="off">
831
+ </div>
832
+ </div>
833
+ <div class="col-md-6">
834
+ <label for="cfg-image-provider" class="form-label">Preview-image renderer <small class="text-body-secondary">(optional)</small></label>
835
+ <select class="form-select cfg-field" id="cfg-image-provider" data-key="image_provider">
836
+ <option value="">None</option>
837
+ {% for ip in sb.image_providers %}<option value="{{ ip.id }}">{{ ip.label }} ({{ ip.vendor }}) — {{ ip.model }}</option>{% endfor %}
838
+ </select>
839
+ <div class="form-text">Pre-configures <code>preview_images</code> in <code>_config.yml</code> for the theme's preview-image generator; the key goes in <code>.env</code>.</div>
840
+ </div>
841
+ </div>
842
+
843
+ <div class="wizard-nav d-flex justify-content-between">
844
+ <button class="btn btn-outline-secondary btn-prev" data-prev="tab-voice">
845
+ <i class="bi bi-arrow-left" aria-hidden="true"></i> Back
280
846
  </button>
281
- <button class="btn btn-primary btn-next" data-next="tab-review">
282
- Review &amp; Download <i class="bi bi-arrow-right"></i>
847
+ <button class="btn btn-primary btn-next" data-next="tab-build">
848
+ Next: Build <i class="bi bi-arrow-right" aria-hidden="true"></i>
283
849
  </button>
284
850
  </div>
285
851
  </div>
286
852
  </div>
287
853
  </div>
288
854
 
289
- <!-- ───── Step 5: Review & Download ───── -->
290
- <div class="tab-pane fade" id="step-review" role="tabpanel">
855
+ {% comment %} ───── Step 9: Build ───── {% endcomment %}
856
+ <div class="tab-pane fade" id="step-build" role="tabpanel" aria-labelledby="tab-build">
291
857
  <div class="card">
292
858
  <div class="card-body">
293
- <h4 class="card-title mb-3">Review Your Configuration</h4>
294
- <p class="text-muted">This is the <code>_config.yml</code> that will be generated. Review it, then download or copy.</p>
295
-
296
- <div class="position-relative">
297
- <pre class="bg-dark text-light p-3 rounded" style="max-height:450px;overflow:auto"><code id="yaml-preview">Loading…</code></pre>
298
- <button class="btn btn-sm btn-outline-light position-absolute top-0 end-0 m-2" id="btn-copy"
299
- title="Copy to clipboard">
300
- <i class="bi bi-clipboard"></i>
301
- </button>
302
- </div>
859
+ <h2 class="h4 card-title mb-1">Build &amp; launch</h2>
860
+ <p class="text-body-secondary">Every file is in the preview on the right. Write the project and start Docker from here, or take the files and run the commands yourself.</p>
303
861
 
304
- <div class="d-flex justify-content-between mt-3">
305
- <button class="btn btn-outline-secondary btn-prev" data-prev="tab-analytics">
306
- <i class="bi bi-arrow-left"></i> Back
307
- </button>
308
- <div>
309
- <button class="btn btn-outline-primary me-2" id="btn-copy-full">
310
- <i class="bi bi-clipboard"></i> Copy YAML
862
+ <div id="wizard-review-warnings" role="status" aria-live="polite" class="mb-3"></div>
863
+
864
+ <div class="mb-3" id="sb-project-picker">
865
+ <label for="sb-project-select" class="form-label fw-semibold">Modify an existing site</label>
866
+ <div class="input-group">
867
+ <select class="form-select" id="sb-project-select" disabled>
868
+ <option value="">Connect the dev proxy to list sites</option>
869
+ </select>
870
+ <button class="btn btn-outline-secondary sb-needs-proxy" type="button" id="btn-project-open" disabled>
871
+ <i class="bi bi-folder2-open" aria-hidden="true"></i> Open
311
872
  </button>
312
- <button class="btn btn-primary" id="btn-download">
313
- <i class="bi bi-download"></i> Download _config.yml
873
+ </div>
874
+ <div class="form-text">Any site under the target root — one this builder wrote, or one you copied there. The assistant can then read, edit and add files, generate images and rebuild it.</div>
875
+ </div>
876
+
877
+ <div class="mb-3">
878
+ <label for="cfg-target" class="form-label fw-semibold">Project folder</label>
879
+ <div class="input-group">
880
+ <span class="input-group-text sb-target-root" id="target-root" title="New sites are created under this folder">…/</span>
881
+ <input type="text" class="form-control cfg-field" id="cfg-target" data-key="target" placeholder="my-site" autocomplete="off" aria-describedby="target-resolved">
882
+ <button class="btn btn-outline-secondary" type="button" id="btn-target-check">
883
+ <i class="bi bi-folder2-open" aria-hidden="true"></i> Check
314
884
  </button>
315
885
  </div>
886
+ <div class="form-text" id="target-resolved">Created beside the theme checkout. Must be new or empty unless you allow overwriting.</div>
887
+ <div class="form-check form-switch mt-2">
888
+ <input class="form-check-input cfg-switch" type="checkbox" id="cfg-overwrite" data-key="overwrite">
889
+ <label class="form-check-label" for="cfg-overwrite">Allow replacing files that already exist there</label>
890
+ </div>
316
891
  </div>
317
892
 
318
- <div class="alert alert-info mt-4" role="alert">
319
- <i class="bi bi-info-circle"></i>
320
- <strong>Next step:</strong> Replace the <code>_config.yml</code> in your project root with the downloaded file, then restart your dev server.
893
+ <div class="sb-build-actions d-grid gap-2 d-sm-flex flex-wrap mb-3">
894
+ <button class="btn btn-primary sb-needs-proxy" type="button" id="btn-write-project" disabled>
895
+ <i class="bi bi-hdd" aria-hidden="true"></i> Write project
896
+ </button>
897
+ <button class="btn btn-success sb-needs-proxy" type="button" id="btn-compose-up" disabled>
898
+ <i class="bi bi-play-fill" aria-hidden="true"></i> docker compose up
899
+ </button>
900
+ <button class="btn btn-outline-secondary sb-needs-proxy" type="button" id="btn-compose-build" disabled
901
+ title="Runs jekyll build inside the running container to validate the site">
902
+ <i class="bi bi-hammer" aria-hidden="true"></i> jekyll build
903
+ </button>
904
+ <button class="btn btn-outline-secondary sb-needs-proxy" type="button" id="btn-compose-logs" disabled>
905
+ <i class="bi bi-terminal" aria-hidden="true"></i> Logs
906
+ </button>
907
+ <button class="btn btn-outline-danger sb-needs-proxy" type="button" id="btn-compose-down" disabled>
908
+ <i class="bi bi-stop-fill" aria-hidden="true"></i> Stop
909
+ </button>
910
+ <a class="btn btn-outline-primary" id="btn-open-site" href="http://localhost:{{ sb_port }}/" target="_blank" rel="noopener">
911
+ <i class="bi bi-box-arrow-up-right" aria-hidden="true"></i> Open site
912
+ </a>
913
+ <button class="btn btn-outline-secondary" type="button" id="btn-check-live">
914
+ <i class="bi bi-activity" aria-hidden="true"></i> Is it live?
915
+ </button>
916
+ </div>
917
+ <p class="small text-body-secondary sb-proxy-hint" id="build-proxy-hint">
918
+ <i class="bi bi-plug" aria-hidden="true"></i> Connect the dev proxy (step 1) to write and run from here. Without it, download the bundle and run the commands below.
919
+ </p>
920
+
921
+ <pre class="wizard-terminal" id="wizard-terminal" aria-live="polite" aria-label="Build output" tabindex="0" hidden></pre>
922
+
923
+ <details class="mt-3" id="manual-run">
924
+ <summary class="fw-semibold">Run it yourself</summary>
925
+ <ol class="small mt-2 mb-0 sb-manual-steps">
926
+ <li>Download <strong>Bundle</strong> from the preview panel and run it: <span class="wizard-cmd d-inline-flex"><code id="cmd-bundle">bash zer0-site-bundle.sh my-site</code><button class="btn btn-sm btn-link" type="button" data-copy-target="#cmd-bundle" aria-label="Copy command"><i class="bi bi-clipboard" aria-hidden="true"></i></button></span></li>
927
+ <li>Start the dev server: <span class="wizard-cmd d-inline-flex"><code id="cmd-up">cd my-site &amp;&amp; docker compose up</code><button class="btn btn-sm btn-link" type="button" data-copy-target="#cmd-up" aria-label="Copy command"><i class="bi bi-clipboard" aria-hidden="true"></i></button></span></li>
928
+ <li>Open <a id="link-local" href="http://localhost:{{ sb_port }}/" target="_blank" rel="noopener">http://localhost:{{ sb_port }}/</a>. Edit a file — the page reloads by itself.</li>
929
+ <li>Publish: <span class="wizard-cmd d-inline-flex"><code id="cmd-publish">gh repo create my-site --public --source=. --push</code><button class="btn btn-sm btn-link" type="button" data-copy-target="#cmd-publish" aria-label="Copy command"><i class="bi bi-clipboard" aria-hidden="true"></i></button></span> then enable Pages in the repository settings.</li>
930
+ </ol>
931
+ </details>
932
+
933
+ <div class="wizard-nav d-flex justify-content-between">
934
+ <button class="btn btn-outline-secondary btn-prev" data-prev="tab-integrations">
935
+ <i class="bi bi-arrow-left" aria-hidden="true"></i> Back
936
+ </button>
937
+ <button class="btn btn-outline-primary" type="button" id="btn-download-all">
938
+ <i class="bi bi-file-earmark-zip" aria-hidden="true"></i> Download bundle
939
+ </button>
321
940
  </div>
322
941
  </div>
323
942
  </div>
324
943
  </div>
325
944
 
326
- </div><!-- /tab-content -->
945
+ </div>{% comment %} /tab-content {% endcomment %}
946
+ </div>
947
+
948
+ {% comment %} ============================================================ {% endcomment %}
949
+ {% comment %} 3. Side column — Claude session + live multi-file preview {% endcomment %}
950
+ {% comment %} ============================================================ {% endcomment %}
951
+ <div class="col-lg-4">
952
+ <aside class="wizard-side sticky-lg-top" aria-label="AI session and generated files">
953
+
954
+ {% include setup/claude-session.html %}
955
+
956
+ <section class="wizard-preview mt-3" aria-labelledby="wizard-preview-heading">
957
+ <div class="d-flex align-items-center justify-content-between mb-2">
958
+ <h2 class="h6 mb-0" id="wizard-preview-heading">
959
+ <i class="bi bi-files" aria-hidden="true"></i> Generated files
960
+ </h2>
961
+ <span class="badge text-bg-secondary wizard-preview-live">live</span>
962
+ </div>
963
+
964
+ <div class="wizard-file-tabs nav nav-pills flex-nowrap overflow-auto mb-2" id="wizard-file-tabs" role="tablist" aria-label="Generated files">
965
+ {% for f in sb.generated_files %}
966
+ <button class="nav-link wizard-file-tab{% if forloop.first %} active{% endif %}" type="button" role="tab"
967
+ data-file="{{ f.path }}" title="{{ f.description | escape }}"
968
+ aria-selected="{% if forloop.first %}true{% else %}false{% endif %}">{{ f.label }}</button>
969
+ {% endfor %}
970
+ </div>
971
+
972
+ <div class="position-relative">
973
+ <pre class="wizard-preview-code bg-dark text-light p-3 rounded" data-file="_config.yml"><code id="yaml-preview">Loading…</code></pre>
974
+ <button class="btn btn-sm btn-outline-light position-absolute top-0 end-0 m-2" id="btn-copy"
975
+ type="button" title="Copy this file" aria-label="Copy this file to clipboard">
976
+ <i class="bi bi-clipboard" aria-hidden="true"></i>
977
+ </button>
978
+ </div>
979
+ <div class="small text-body-secondary mt-1" id="wizard-file-description"></div>
980
+
981
+ <div class="d-grid gap-2 d-sm-flex mt-3">
982
+ <button class="btn btn-outline-primary flex-sm-fill" id="btn-copy-full" type="button">
983
+ <i class="bi bi-clipboard" aria-hidden="true"></i> Copy file
984
+ </button>
985
+ <button class="btn btn-primary flex-sm-fill" id="btn-download" type="button">
986
+ <i class="bi bi-download" aria-hidden="true"></i> Download file
987
+ </button>
988
+ </div>
989
+ </section>
990
+ </aside>
991
+ </div>
992
+
993
+ </div>{% comment %} /row {% endcomment %}
994
+
995
+ <div class="toast-container position-fixed bottom-0 start-50 translate-middle-x p-3">
996
+ <div class="toast align-items-center text-bg-dark border-0" id="wizard-toast" role="status" aria-live="polite" aria-atomic="true">
997
+ <div class="d-flex">
998
+ <div class="toast-body" id="wizard-toast-body">Copied</div>
999
+ <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
1000
+ </div>
327
1001
  </div>
328
1002
  </div>
329
1003
  </div>
330
1004
 
331
- <script src="{{ '/assets/js/setup-wizard.js' | relative_url }}"></script>
1005
+ {% comment %} Catalogs + framework brief shared by both scripts (from _data/site_builder.yml). {% endcomment %}
1006
+ <script id="siteBuilderData" type="application/json">{{ sb | jsonify }}</script>
1007
+
1008
+ {% comment %}
1009
+ Runtime configuration. The AI session is proxy-only: the endpoint below is
1010
+ the dev proxy's /api/wizard base; chat goes to ai_chat.endpoint (or an explicit
1011
+ site_builder.chat_endpoint). No credential is ever present in this JSON —
1012
+ a token pasted on the Connect step goes straight to the localhost proxy.
1013
+ {% endcomment %}
1014
+ <script id="siteBuilderConfig" type="application/json">
1015
+ {
1016
+ "enabled": {% if sb_cfg.enabled == false %}false{% else %}true{% endif %},
1017
+ "endpoint": {{ sb_cfg.endpoint | default: "/api/wizard" | jsonify }},
1018
+ "chatEndpoint": {{ sb_cfg.chat_endpoint | default: site.ai_chat.endpoint | default: "/api/chat" | jsonify }},
1019
+ "provider": {{ sb_cfg.provider | default: "auto" | jsonify }},
1020
+ "model": {{ sb_cfg.model | default: site.ai_chat.model | default: "claude-opus-4-8" | jsonify }},
1021
+ "mode": {{ sb_cfg.mode | default: "guided" | jsonify }},
1022
+ "imageProvider": {{ sb_cfg.image_provider | default: "" | jsonify }},
1023
+ "maxTokens": {{ sb_cfg.max_tokens | default: 8192 }},
1024
+ "defaultPort": {{ sb_port }},
1025
+ "searchIndex": {{ "/search.json" | relative_url | jsonify }},
1026
+ "docsBase": {{ "/docs/" | relative_url | jsonify }},
1027
+ "themeVersion": {{ site.theme_version | default: site.data.theme-manifest.version | default: "" | jsonify }},
1028
+ "themeRepository": "bamr87/zer0-mistakes"
1029
+ }
1030
+ </script>
1031
+
1032
+ <script defer src="{{ '/assets/js/setup-wizard.js' | relative_url }}"></script>
1033
+ <script defer src="{{ '/assets/js/site-builder.js' | relative_url }}"></script>