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