Diff -- /var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/TEMPLATES/webstls_optimization.html
Diff

/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/TEMPLATES/webstls_optimization.html

added on local at 2026-07-01 13:48:06

Added
+1629
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 3837d8d94bf2
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1<style>
2 /* Per-row action buttons in the Your experiments table. Each lives
3 in its own form (POST + confirm) so we cannot use the standard
4 .btn classes (those add too much weight for inline table actions).
5 This is a smaller pill specifically tuned for table rows. */
6 .exp-row-action {
7 display: inline-flex;
8 align-items: center;
9 gap: 4px;
10 padding: 5px 11px;
11 font-size: 11px;
12 font-weight: 600;
13 line-height: 1.2;
14 border-radius: 6px;
15 border: 1px solid var(--col-border);
16 background: var(--col-surface-2);
17 color: var(--col-text-2);
18 text-decoration: none;
19 cursor: pointer;
20 font-family: inherit;
21 transition: background 0.12s, border-color 0.12s, color 0.12s;
22 white-space: nowrap;
23 }
24 .exp-row-action:hover {
25 background: var(--col-surface-3);
26 border-color: var(--col-accent);
27 color: var(--col-text);
28 }
29 .exp-row-action.is-primary { background: rgba(124,58,237,0.15); border-color: rgba(124,58,237,0.40); color: var(--col-accent-bright); }
30 .exp-row-action.is-primary:hover { background: rgba(124,58,237,0.25); border-color: rgba(124,58,237,0.70); }
31 .exp-row-action.is-success { background: rgba(34,197,94,0.12); border-color: rgba(34,197,94,0.35); color: #4ade80; }
32 .exp-row-action.is-success:hover { background: rgba(34,197,94,0.20); border-color: rgba(34,197,94,0.65); }
33 .exp-row-action.is-warning { background: rgba(245,158,11,0.12); border-color: rgba(245,158,11,0.35); color: #fbbf24; }
34 .exp-row-action.is-warning:hover { background: rgba(245,158,11,0.20); border-color: rgba(245,158,11,0.65); }
35 .exp-row-action.is-danger { background: rgba(239,68,68,0.10); border-color: rgba(239,68,68,0.30); color: #f87171; }
36 .exp-row-action.is-danger:hover { background: rgba(239,68,68,0.18); border-color: rgba(239,68,68,0.60); }
37 /* Edit = cyan/teal accent (a different brand-family color than the
38 primary purple so it does not compete with Details). */
39 .exp-row-action.is-edit { background: rgba(6,182,212,0.13); border-color: rgba(6,182,212,0.40); color: #22d3ee; }
40 .exp-row-action.is-edit:hover { background: rgba(6,182,212,0.22); border-color: rgba(6,182,212,0.70); }
41 /* Reset = soft amber. Same family as Pause but visually distinct
42 because Reset is in a different position in the lifecycle. */
43 .exp-row-action.is-reset { background: rgba(251,191,36,0.13); border-color: rgba(251,191,36,0.40); color: #fcd34d; }
44 .exp-row-action.is-reset:hover { background: rgba(251,191,36,0.22); border-color: rgba(251,191,36,0.70); }
45 .exp-actions-cell { display: flex; gap: 6px; align-items: center; flex-wrap: nowrap; }
46
47 /* Experiments table column sizing. Auto-layout (no fixed columns)
48 plus a min-width on Name + nowrap on the Actions cell gives Name
49 the leftover horizontal space while keeping Actions compact. */
50 .exp-table .exp-col-name { min-width: 220px; }
51 .exp-table .exp-col-actions { width: 1%; white-space: nowrap; }
52 .exp-table td.exp-actions-host { white-space: nowrap; }
53
54</style>
55
56<div class="page-head">
57 <div>
58 <span class="page-eyebrow"><span class="dot"></span> Optimization Suite</span>
59 <h1 class="page-title">Test and improve sales automatically</h1>
60 <p class="page-subtitle">A/B + MVT, automated price testing, buyer pricing surveys, promotional rules. Same toolkit DTC (direct-to-consumer) brands use — without the consultant invoice.</p>
61 <span class="opt-explainer" style="display:inline-flex;align-items:center;gap:6px;padding:6px 12px;margin-top:10px;background:var(--col-surface-2);border:1px solid var(--col-border);border-radius:6px;font-size:12px;color:var(--col-text-2);font-weight:600" data-kpi-detail='{
62 "eyebrow":"A/B and MVT . what these terms mean",
63 "description":"Two related ways to test ideas on your storefront before committing to one. <strong>A/B</strong> shows half of your visitors the current version and half a new version of one thing -- a headline, a button color, a price. The winner is whichever variant converts better. <strong>MVT</strong> (multivariate testing) tests several things at once, in combination -- different headlines paired with different images paired with different button copy -- and finds the best combination instead of testing each in isolation.",
64 "methodology":"<strong>A/B test:</strong> visitors are bucketed by a sticky cookie at first page load, 50/50. We track conversion per bucket and run a Bayesian model to estimate the probability that B beats A. When that probability crosses 95%, we surface a Call winner pill so you can ship the winning variant. <strong>MVT:</strong> visitors are bucketed across every combination of the variables you are testing. With 3 variables and 2 options each, that is 8 cells. We need ~250 conversions per cell for reliable results, so MVT requires more traffic than a single A/B.",
65 "kpis":[
66 {"label":"A/B","value":"Two variants","sub":"50/50 split, one thing changes","color":"#a78bfa"},
67 {"label":"MVT","value":"Many combinations","sub":"finds best mix of changes","color":"#a78bfa"},
68 {"label":"When to use","value":"A/B for clear hypotheses, MVT for fishing","sub":"different tools, same idea"}
69 ],
70 "interpretation":"Rule of thumb: A/B when you have a strong belief about the change (new headline should beat the old one) and want to confirm it. MVT when you are not sure which combination wins and have enough traffic to fan out across more cells. A/B finishes faster and is easier to interpret; MVT explores more of the design space but needs significantly more visitors to be conclusive.",
71 "insights":[
72 {"label":"Trap","value":"Calling an A/B at less than 90% confidence usually flips with more data. The pill is conservative on purpose -- it waits until the signal is real.","tone":"warn"},
73 {"label":"Tip","value":"Start with A/B tests on your highest-traffic surface (usually the home hero). Move to MVT once you have a baseline and want to compound several proven changes together.","tone":"info"}
74 ],
75 "drilldowns":[
76 {"label":"Start a new experiment","href":"#ab"},
77 {"label":"Why we do not personalize prices","href":"#"}
78 ]
79 }'>
80 <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3"/><circle cx="12" cy="17" r="0.5" fill="currentColor"/></svg>
81 What is A/B + MVT?
82 </span>
83 </div>
84 <div class="page-actions">
85 [if:$mode_new]<a href="/optimization.cgi" class="btn btn-secondary">&larr; Back</a>[/if]
86 [if:$mode_detail]<a href="/optimization.cgi" class="btn btn-secondary">&larr; Back to experiments</a>[/if]
87 [if:$mode_list]
88 [if:$not_tutorial_mode]<a href="/optimization.cgi?tutorial=1" class="btn btn-secondary"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>Sample Data View</a>[/if]
89 [if:$tutorial_mode]<a href="/optimization.cgi" class="btn btn-secondary"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>Exit sample view</a>[/if]
90 <a href="/optimization.cgi?new=1" class="btn btn-primary">
91 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
92 New experiment
93 </a>
94 [/if]
95 </div>
96 </div>
97
98 [if:$mode_list][if:$tutorial_mode]
99 <div class="tutorial-banner">
100 <div class="tutorial-banner-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></div>
101 <div class="tutorial-banner-body">
102 <div class="tutorial-banner-title">Tutorial &middot; Sample data only</div>
103 <div class="tutorial-banner-text">Every experiment, price test, suggestion and history row below is example data &mdash; none of it is from your store. Use this to see what Optimization reads like when you have real activity.</div>
104 </div>
105 <a href="/optimization.cgi" class="btn btn-secondary btn-sm">Exit sample view</a>
106 </div>
107
108 <div class="tutorial-intro">
109 <div class="tutorial-intro-eyebrow">A walkthrough of the Optimization Suite</div>
110 <h2 class="tutorial-intro-title">A/B tests, price tests, and peer recommendations.</h2>
111 <p class="tutorial-intro-text">The Optimization page is where you discover what actually converts buyers on your storefront. Four tabs each test something different: <strong>A/B + MVT</strong> tests headline or image variants, <strong>Price tests</strong> auto-adjust product prices to find the revenue-per-visitor sweet spot, <strong>Pricing surveys</strong> ask buyers what they would pay (Van Westendorp / Gabor-Granger), and <strong>Promotions</strong> tracks coupons and sales (full UI is at <a href="/promotions.cgi" class="text-brand fw-700">/promotions.cgi</a>).</p>
112 <p class="tutorial-intro-text">A/B data is recorded into <code>ab_test_events</code> as visitors land on each variant; the Bayesian model computes <strong>P(B beats A)</strong> from <code>conversions / exposures</code> per variant. When confidence crosses 95% the pill flips to <em>Call winner</em> and you can promote variant B in one click. Price tests behave similarly with a Thompson-sampling bandit over <code>price_tests.test_rules</code>; they keep sampling every tier to detect drift.</p>
113 <p class="tutorial-intro-text tutorial-intro-foot">Click <a href="/optimization.cgi" class="text-brand fw-700">Exit sample view</a> to see your real experiments, or <a href="/optimization.cgi?new=1" class="text-brand fw-700">create your first one</a>.</p>
114 </div>
115 [/if][/if]
116
117 [if:$mode_detail]
118 <!-- ============ EXPERIMENT DETAIL VIEW ============
119 Per-experiment deep dive: winner state, per-variant stats,
120 action buttons. The CGI computes everything via
121 MODS::AffSoft::Experiments->compute_stats and passes named
122 tvars so this template stays declarative. -->
123 <div class="module mb-3">
124 <div class="module-head">
125 <div class="left">
126 <div class="module-icon" style="background:rgba(124,58,237,0.15);color:var(--col-violet-bright)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19 12a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"/></svg></div>
127 <div>
128 <div class="module-title">$detail_name</div>
129 <div class="module-sub">$detail_test_type test &middot; $detail_surface &middot; success metric: $detail_metric_label</div>
130 </div>
131 </div>
132 <div>
133 [if:$detail_is_draft]<span class="pill">Draft</span>[/if]
134 [if:$detail_is_running]<span class="pill brand">Running</span>[/if]
135 [if:$detail_is_paused]<span class="pill warning">Paused</span>[/if]
136 [if:$detail_is_complete]<span class="pill success">Complete</span>[/if]
137 </div>
138 </div>
139
140 <div class="module-body">
141
142 <!-- Locked winner callout: only shows once the test is
143 Completed. Takes priority over the live winner_called /
144 too_early / below_min messages because the test is no
145 longer bucketing new visitors. -->
146 [if:$detail_has_locked]
147 <div class="mb-3" style="background:linear-gradient(135deg, rgba(34,197,94,0.14), rgba(124,58,237,0.12));border:2px solid rgba(34,197,94,0.55);border-radius:14px;padding:22px 26px;display:flex;align-items:center;gap:18px;flex-wrap:wrap">
148 <div style="display:grid;place-items:center;width:54px;height:54px;border-radius:50%;background:var(--col-success);color:#fff;flex-shrink:0">
149 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" width="28" height="28"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
150 </div>
151 <div style="flex:1;min-width:260px">
152 <div style="font-family:var(--font-display);font-size:24px;font-weight:800;color:var(--col-success);line-height:1.1;margin-bottom:6px;letter-spacing:0.5px">
153 Winner locked in: Variant $detail_locked_winner
154 </div>
155 <div style="font-size:14px;color:var(--col-text-2);line-height:1.6">
156 This experiment is <strong style="color:var(--col-text)">Completed</strong>. When you marked it complete, we compared the conversion rates from collected visitors and locked in <strong style="color:var(--col-text)">Variant $detail_locked_winner</strong> as the winner (final confidence: <strong style="color:var(--col-text)">$detail_conf_pct</strong>). It no longer buckets new visitors. To run a fresh test on this surface, click Re-run from the Completed section, or create a new experiment.
157 </div>
158 </div>
159 </div>
160 [/if]
161
162 <!-- Winner callout: big, clear, color-coded by state. -->
163 [if:!$detail_has_locked]
164 [if:$detail_winner_called]
165 <div class="mb-3" style="background:rgba(34,197,94,0.10);border:2px solid rgba(34,197,94,0.45);border-radius:14px;padding:22px 26px;display:flex;align-items:center;gap:18px;flex-wrap:wrap">
166 <div style="display:grid;place-items:center;width:48px;height:48px;border-radius:50%;background:var(--col-success);color:#fff;flex-shrink:0">
167 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" width="26" height="26"><polyline points="20 6 9 17 4 12"/></svg>
168 </div>
169 <div style="flex:1;min-width:260px">
170 <div style="font-family:var(--font-display);font-size:22px;font-weight:800;color:var(--col-success);line-height:1.1;margin-bottom:6px">
171 Variant $detail_leader_label is winning &mdash; ship it
172 </div>
173 <div style="font-size:14px;color:var(--col-text-2);line-height:1.55">
174 Confidence is at <strong style="color:var(--col-text)">$detail_conf_pct</strong> with a <strong style="color:var(--col-text)">$detail_lift_pct</strong> lift in $detail_metric_label. The leading variant converts at <strong style="color:var(--col-text)">$detail_leader_rate</strong> vs the other at <strong style="color:var(--col-text)">$detail_loser_rate</strong>. Once you promote the winner, you can stop the experiment and start a new one on a different surface.
175 </div>
176 </div>
177 </div>
178 [/if]
179
180 [if:$detail_too_early]
181 <div class="mb-3" style="background:rgba(96,165,250,0.10);border:1px solid rgba(96,165,250,0.35);border-radius:14px;padding:22px 26px;display:flex;align-items:center;gap:18px;flex-wrap:wrap">
182 <div style="display:grid;place-items:center;width:48px;height:48px;border-radius:50%;background:rgba(96,165,250,0.25);color:#60a5fa;flex-shrink:0">
183 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" width="26" height="26"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
184 </div>
185 <div style="flex:1;min-width:260px">
186 <div style="font-family:var(--font-display);font-size:20px;font-weight:700;color:#60a5fa;line-height:1.2;margin-bottom:6px">
187 Trending: Variant $detail_leader_label is ahead
188 </div>
189 <div style="font-size:13px;color:var(--col-text-2);line-height:1.55">
190 Confidence is at <strong style="color:var(--col-text)">$detail_conf_pct</strong> with a <strong style="color:var(--col-text)">$detail_lift_pct</strong> lift in $detail_metric_label. That is below our 95% threshold so the lead might still flip with more data &mdash; keep the test running. Variant $detail_leader_label converts at <strong style="color:var(--col-text)">$detail_leader_rate</strong>; the other at <strong style="color:var(--col-text)">$detail_loser_rate</strong>.
191 </div>
192 </div>
193 </div>
194 [/if]
195
196 [if:$detail_below_min]
197 <div class="mb-3" style="background:var(--col-surface-2);border:1px solid var(--col-border);border-radius:14px;padding:22px 26px;display:flex;align-items:center;gap:18px;flex-wrap:wrap">
198 <div style="display:grid;place-items:center;width:48px;height:48px;border-radius:50%;background:var(--col-surface-3);color:var(--col-text-2);flex-shrink:0">
199 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="26" height="26"><path d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>
200 </div>
201 <div style="flex:1;min-width:260px">
202 <div style="font-family:var(--font-display);font-size:20px;font-weight:700;color:var(--col-text);line-height:1.2;margin-bottom:6px">
203 Not enough data yet
204 </div>
205 <div style="font-size:13px;color:var(--col-text-2);line-height:1.55">
206 You have $detail_traffic visitors split across two variants so far. We wait for at least 25 in each arm before declaring a leader &mdash; small samples produce noisy results that often flip. Keep the test running, or drive more traffic to your storefront from a campaign / social post / marketplace link.
207 </div>
208 </div>
209 </div>
210 [/if]
211 [/if]
212
213 <!-- Per-variant cards: content side-by-side, stats below each. -->
214 <div class="dash-grid-2" style="gap:18px">
215 [loop:@detail_variants]
216 <div style="background:[if:$loop1.is_locked_winner]rgba(34,197,94,0.10)[/if][if:!$loop1.is_locked_winner][if:$loop1.is_leader]rgba(34,197,94,0.06)[/if][if:!$loop1.is_leader]var(--col-surface-2)[/if][/if];border-radius:12px;border:[if:$loop1.is_locked_winner]2px[/if][if:!$loop1.is_locked_winner]1px[/if] solid [if:$loop1.is_locked_winner]rgba(34,197,94,0.55)[/if][if:!$loop1.is_locked_winner][if:$loop1.is_leader]rgba(34,197,94,0.30)[/if][if:!$loop1.is_leader]var(--col-border)[/if][/if];padding:16px">
217 <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
218 <div class="text-xs fw-700" style="letter-spacing:1.5px;text-transform:uppercase;color:[if:$loop1.is_locked_winner]var(--col-success)[/if][if:!$loop1.is_locked_winner][if:$loop1.is_leader]var(--col-success)[/if][if:!$loop1.is_leader]var(--col-text-3)[/if][/if]">$loop1.label</div>
219 [if:$loop1.is_locked_winner]<span class="pill success">&#9733; Winner</span>[/if]
220 [if:!$loop1.is_locked_winner][if:$loop1.is_leader]<span class="pill success">Leading</span>[/if][/if]
221 </div>
222 [if:$loop1.has_image]
223 <div style="aspect-ratio:1.6;background:url('$loop1.image_url') center/cover;border-radius:8px;margin-bottom:10px;border:1px solid var(--col-border)"></div>
224 [/if]
225 [if:!$loop1.has_image][if:!$loop1.has_design]
226 <div style="aspect-ratio:1.6;background:var(--col-surface-3);border-radius:8px;margin-bottom:10px;display:grid;place-items:center;color:var(--col-text-3);font-size:12px">No image variant &mdash; text only</div>
227 [/if][/if]
228 [if:$loop1.has_design]
229 <div style="background:var(--col-surface-3);border-radius:8px;margin-bottom:10px;padding:14px;display:flex;flex-direction:column;gap:8px">
230 [if:$loop1.has_layout_pick]<div style="display:flex;justify-content:space-between;font-size:12px"><span class="text-dim" style="letter-spacing:1px;text-transform:uppercase">Layout</span><strong style="color:var(--col-text)">$loop1.layout_name</strong></div>[/if]
231 [if:$loop1.show_layout_inherit]<div style="display:flex;justify-content:space-between;font-size:12px"><span class="text-dim" style="letter-spacing:1px;text-transform:uppercase">Layout</span><em style="color:var(--col-text-2)">Storefront's saved layout</em></div>[/if]
232 [if:$loop1.has_theme_pick]<div style="display:flex;justify-content:space-between;font-size:12px"><span class="text-dim" style="letter-spacing:1px;text-transform:uppercase">Theme</span><strong style="color:var(--col-text)">$loop1.theme_name</strong></div>[/if]
233 [if:$loop1.show_theme_inherit]<div style="display:flex;justify-content:space-between;font-size:12px"><span class="text-dim" style="letter-spacing:1px;text-transform:uppercase">Theme</span><em style="color:var(--col-text-2)">Storefront's saved theme</em></div>[/if]
234 </div>
235 [/if]
236 <div class="text-sm fw-600" style="color:var(--col-text);margin-bottom:10px;min-height:42px;line-height:1.4">$loop1.text</div>
237 <div class="dash-grid-3" style="gap:10px">
238 <div>
239 <div class="text-xs text-dim" style="letter-spacing:1px;text-transform:uppercase">Visitors</div>
240 <div class="font-mono fw-700 text-sm">$loop1.exposures</div>
241 </div>
242 <div>
243 <div class="text-xs text-dim" style="letter-spacing:1px;text-transform:uppercase">$detail_metric_label</div>
244 <div class="font-mono fw-700 text-sm">$loop1.conversions</div>
245 </div>
246 <div>
247 <div class="text-xs text-dim" style="letter-spacing:1px;text-transform:uppercase">Conv. rate</div>
248 <div class="font-mono fw-700 text-sm" style="color:[if:$loop1.is_leader]var(--col-success)[/if][if:!$loop1.is_leader]var(--col-text)[/if]">$loop1.rate</div>
249 </div>
250 </div>
251 </div>
252 [/loop]
253 </div>
254
255 <!-- Summary KPIs across the bottom. -->
256 <div class="divider"></div>
257 <div class="dash-grid-3">
258 <div>
259 <div class="text-xs text-dim" style="letter-spacing:1.5px;text-transform:uppercase">Lift</div>
260 <div style="font-family:var(--font-display);font-size:24px;font-weight:700;color:[if:$detail_winner_called]var(--col-success)[/if][if:!$detail_winner_called]var(--col-text)[/if]">$detail_lift_pct</div>
261 <div class="text-xs text-dim">B over A on $detail_metric_label</div>
262 </div>
263 <div>
264 <div class="text-xs text-dim" style="letter-spacing:1.5px;text-transform:uppercase">Confidence</div>
265 <div style="font-family:var(--font-display);font-size:24px;font-weight:700;color:#fff">$detail_conf_pct</div>
266 <div class="text-xs text-dim">[if:$detail_winner_called]Above 95% threshold[/if][if:!$detail_winner_called]Still climbing toward 95%[/if]</div>
267 </div>
268 <div>
269 <div class="text-xs text-dim" style="letter-spacing:1.5px;text-transform:uppercase">Total visitors</div>
270 <div style="font-family:var(--font-display);font-size:24px;font-weight:700;color:#fff">$detail_traffic</div>
271 <div class="text-xs text-dim">across both variants</div>
272 </div>
273 </div>
274
275 <!-- Action bar: Start/Pause/Resume + Edit + Reset + Delete -->
276 <div class="divider"></div>
277 <div style="display:flex;flex-wrap:wrap;gap:10px">
278 [if:$detail_is_draft]
279 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0">
280 <input type="hidden" name="act" value="start">
281 <input type="hidden" name="id" value="$detail_id">
282 <button type="submit" class="btn btn-success">Start experiment</button>
283 </form>
284 [/if]
285 [if:$detail_is_running]
286 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0">
287 <input type="hidden" name="act" value="pause">
288 <input type="hidden" name="id" value="$detail_id">
289 <button type="submit" class="btn btn-warning">Pause</button>
290 </form>
291 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0"
292 data-confirm-title="Mark complete"
293 data-confirm-message="$detail_complete_confirm"
294 data-confirm-label="Complete"
295 data-confirm-style="success">
296 <input type="hidden" name="act" value="complete">
297 <input type="hidden" name="id" value="$detail_id">
298 <button type="submit" class="btn btn-success">Mark complete</button>
299 </form>
300 [/if]
301 [if:$detail_is_paused]
302 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0">
303 <input type="hidden" name="act" value="start">
304 <input type="hidden" name="id" value="$detail_id">
305 <button type="submit" class="btn btn-success">Resume</button>
306 </form>
307 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0"
308 data-confirm-title="Mark complete"
309 data-confirm-message="$detail_complete_confirm"
310 data-confirm-label="Complete"
311 data-confirm-style="success">
312 <input type="hidden" name="act" value="complete">
313 <input type="hidden" name="id" value="$detail_id">
314 <button type="submit" class="btn btn-success">Mark complete</button>
315 </form>
316 [/if]
317 [if:$detail_is_complete]
318 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0"
319 data-confirm-title="Archive experiment"
320 data-confirm-message="The experiment moves to the Archived section at the bottom of the page so it does not clutter your active or completed list. All configuration, visitor data, and the winner pick are preserved. You can Unarchive it later to bring it back."
321 data-confirm-label="Archive"
322 data-confirm-style="primary">
323 <input type="hidden" name="act" value="archive">
324 <input type="hidden" name="id" value="$detail_id">
325 <button type="submit" class="btn btn-secondary">Archive</button>
326 </form>
327 [/if]
328
329 <a href="/optimization.cgi?edit=$detail_id" class="btn btn-secondary">Edit experiment</a>
330
331 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0"
332 data-confirm-title="Reset stats"
333 data-confirm-message="The variants and configuration stay exactly the same, but every collected visitor exposure and conversion event is deleted. The status returns to Draft so you can deliberately re-start the test once you are ready."
334 data-confirm-label="Reset stats"
335 data-confirm-style="warning">
336 <input type="hidden" name="act" value="reset_stats">
337 <input type="hidden" name="id" value="$detail_id">
338 <button type="submit" class="btn btn-secondary">Reset stats</button>
339 </form>
340
341 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0;margin-left:auto"
342 data-confirm-title="Delete experiment"
343 data-confirm-message="The experiment configuration and every visitor exposure / conversion event tied to it are removed permanently. This cannot be undone. If you might want this history later, use Archive instead -- it keeps the row out of your active list without erasing anything."
344 data-confirm-label="Delete permanently"
345 data-confirm-style="danger">
346 <input type="hidden" name="act" value="delete">
347 <input type="hidden" name="id" value="$detail_id">
348 <button type="submit" class="btn btn-danger">Delete</button>
349 </form>
350 </div>
351 </div>
352 </div>
353 [/if]
354
355 [if:$saved_flash]
356 <div class="banner success mb-3">
357 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>
358 <div class="text-xs">Experiment saved as a draft. Start it when you are ready to begin collecting data.</div>
359 </div>
360 [/if]
361
362 [if:$mode_new]
363 <!-- ============ NEW EXPERIMENT BUILDER ============
364 Creates a draft row in ab_tests. The draft does not collect
365 data until the seller hits Start on the list view. Image
366 variants upload via /upload_image.cgi (existing endpoint)
367 which returns a URL stored in the variants JSON. -->
368 <div class="module mb-3">
369 <div class="module-head">
370 <div class="left">
371 <div class="module-icon" style="background:rgba(124,58,237,0.15);color:var(--col-violet-bright)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></div>
372 <div>
373 [if:$is_edit]
374 <div class="module-title">Edit experiment</div>
375 <div class="module-sub">Saving changes wipes the visitor data collected so far and reverts the row to a fresh draft.</div>
376 [/if]
377 [if:!$is_edit]
378 <div class="module-title">New experiment</div>
379 <div class="module-sub">Define a control + at least one challenger. Save as draft, start it later.</div>
380 [/if]
381 </div>
382 </div>
383 </div>
384 <div class="module-body">
385 [if:$is_edit]
386 <!-- Loud warning so the seller does not edit a running test
387 expecting the data to be preserved. -->
388 <div class="mb-3" style="background:rgba(245,158,11,0.12);border:1px solid rgba(245,158,11,0.40);border-radius:10px;padding:14px 18px;display:flex;gap:12px;align-items:flex-start">
389 <svg viewBox="0 0 24 24" fill="none" stroke="#fbbf24" stroke-width="2" width="20" height="20" style="flex-shrink:0;margin-top:1px"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
390 <div style="font-size:13px;color:var(--col-text-2);line-height:1.55">
391 <strong style="color:#fbbf24">Editing this experiment resets it.</strong>
392 The variants you typed before are about to be replaced. Any visitors already bucketed and any conversion events collected will be wiped (those visitors saw the OLD variants, so the data cannot be fairly combined with the new ones). The status flips back to <strong style="color:var(--col-text)">Draft</strong> and you will need to <strong style="color:var(--col-text)">Start</strong> the test again. If you just want to clear the collected numbers without changing the variants, use <strong style="color:var(--col-text)">Reset stats</strong> on the experiment row instead.
393 </div>
394 </div>
395 [/if]
396 [if:!$has_storefront]
397 <div class="banner warning mb-3"><div class="text-sm">You need a storefront before you can run experiments. <a href="/storefront.cgi" class="text-brand">Create one &rarr;</a></div></div>
398 [/if]
399 [if:$has_storefront]
400 <form method="POST" action="/optimization_action.cgi" id="exp-form" enctype="multipart/form-data">
401 <input type="hidden" name="act" value="save">
402 [if:$is_edit]<input type="hidden" name="id" value="$edit_id">[/if]
403 <input type="hidden" name="storefront_id" value="$store_id">
404
405 <div class="form-group mb-3">
406 <label class="form-label">Experiment name <span class="req">*</span></label>
407 <input class="input" type="text" name="name" value="$edit_name" maxlength="160" required placeholder="e.g. Hero headline test">
408 <div class="form-help">Private name to help you find this test later. Buyers do not see it.</div>
409 </div>
410
411 <div class="dash-grid-2 mb-3" style="gap:14px">
412 <div class="form-group" style="margin-bottom:0">
413 <label class="form-label">Test type</label>
414 <select class="select" name="test_type">
415 <option value="ab">A/B &mdash; two variants, one element changing</option>
416 <option value="mvt">MVT &mdash; multiple variants, combinations of changes</option>
417 </select>
418 </div>
419 <div class="form-group" style="margin-bottom:0">
420 <label class="form-label">What is changing?</label>
421 <select class="select" name="surface" id="surface-select">
422 <optgroup label="Element-level">
423 <option value="hero_title">Hero headline (text)</option>
424 <option value="hero_subtitle">Hero subtitle (text)</option>
425 <option value="hero_image">Hero image (visual)</option>
426 <option value="hero_video">Hero video (visual)</option>
427 <option value="cta_label">CTA button label (text)</option>
428 <option value="product_tile">Product tile (single model: title + image)</option>
429 <option value="section_heading">Section heading (text)</option>
430 <option value="custom">Custom HTML override</option>
431 </optgroup>
432 <optgroup label="Page design">
433 <option value="layout">Page layout (swap whole storefront structure)</option>
434 <option value="theme">Color theme (swap whole palette)</option>
435 <option value="layout_and_theme">Layout AND theme (full design MVT)</option>
436 </optgroup>
437 </select>
438 <div class="form-help">Element-level tests change one piece of the page. Page-design tests compare entire storefront looks.</div>
439 </div>
440 </div>
441
442 <!-- Product picker (only relevant when surface = product_tile).
443 Toggled by the JS at the bottom of this form. -->
444 <div class="form-group mb-3" id="target-model-group" style="display:none">
445 <label class="form-label">Product to test <span class="req">*</span></label>
446 [if:$has_user_models]
447 <select class="select" name="target_model_id" id="target-model-select">
448 [loop:@user_models]<option value="$loop1.id">$loop1.title</option>[/loop]
449 </select>
450 <div class="form-help">The tile for this model in the catalog grid is what variants A and B replace. Other products are unaffected.</div>
451 [/if]
452 [if:!$has_user_models]
453 <div class="text-xs text-warning">You need at least one published model to run a product tile test. <a href="/upload_model.cgi" class="text-brand">Upload one &rarr;</a></div>
454 [/if]
455 </div>
456
457 <div class="dash-grid-2 mb-3" style="gap:14px">
458 <div class="form-group" style="margin-bottom:0">
459 <label class="form-label">Success event</label>
460 <select class="select" name="primary_metric">
461 <option value="conversion_rate">Conversion (paid order)</option>
462 <option value="add_to_cart">Add to cart</option>
463 <option value="click">Click on tested element</option>
464 <option value="signup">Email signup</option>
465 </select>
466 </div>
467 <div class="form-group" style="margin-bottom:0">
468 <label class="form-label">Confidence threshold</label>
469 <select class="select" name="confidence_threshold">
470 <option value="0.95">95% (recommended)</option>
471 <option value="0.90">90% (faster, slightly less reliable)</option>
472 <option value="0.99">99% (slower, very reliable)</option>
473 </select>
474 </div>
475 </div>
476
477 <div class="divider"></div>
478
479 <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
480 <div class="pe-section-label" style="font-size:11px;letter-spacing:1.5px;text-transform:uppercase;color:var(--col-text-3);font-weight:700;margin:0">Variants</div>
481 <button type="button" id="add-variant-btn" class="btn btn-secondary" style="display:none;font-size:12px;padding:6px 12px">+ Add variant (MVT)</button>
482 </div>
483
484 <!-- Variants. Loop renders one card per variant in edit_variants
485 (saved variants A..F come from the row; new tests start with
486 A + B). The card body contains every input shape we need
487 (text, image, layout, theme); the JS at the bottom toggles
488 which inputs are visible based on the chosen surface. -->
489 <div id="variants-container">
490 [loop:@edit_variants]
491 <div class="variant-card module mb-3" data-vid="$loop1.vid"
492 style="background:var(--col-surface-2);[if:!$loop1.is_control]border-color:rgba(124,58,237,0.30);[/if]">
493 <div class="module-body">
494 <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
495 <div class="text-xs mb-0" style="letter-spacing:1.5px;text-transform:uppercase;color:[if:$loop1.is_control]var(--col-text-3)[/if][if:!$loop1.is_control]var(--col-violet-bright)[/if]">$loop1.label</div>
496 [if:!$loop1.is_control]
497 <button type="button" class="remove-variant-btn" data-vid="$loop1.vid" style="display:none;background:none;border:1px solid var(--col-border);color:var(--col-text-3);font-size:11px;padding:4px 10px;border-radius:6px;cursor:pointer">Remove</button>
498 [/if]
499 </div>
500
501 <!-- Optional human-readable label that shows in the dashboard. -->
502 <div class="form-group v-name-group" style="margin-bottom:10px">
503 <label class="form-label">Variant name (optional)</label>
504 <input class="input" type="text" name="$loop1.name_field" value="$loop1.name" maxlength="120" placeholder="e.g. Bigger image + bolder CTA">
505 </div>
506
507 <!-- Text / image inputs: relevant for hero_title, hero_subtitle,
508 hero_image, cta_label, product_tile, section_heading, custom -->
509 <div class="v-text-group">
510 <div class="form-group">
511 <label class="form-label">Text content</label>
512 <textarea class="input" name="$loop1.text_field" rows="3" placeholder="[if:$loop1.is_control]The current copy on this surface, or leave blank to keep what is already live[/if][if:!$loop1.is_control]The new copy you want to test against the control[/if]">$loop1.text</textarea>
513 </div>
514 </div>
515 <div class="v-image-group">
516 [if:$loop1.has_image]
517 <div class="form-group" style="margin-bottom:8px">
518 <div class="text-xs text-dim mb-1" style="letter-spacing:1px;text-transform:uppercase">Current image</div>
519 <div style="width:120px;height:80px;background:url('$loop1.image_url') center/cover;border:1px solid var(--col-border);border-radius:6px"></div>
520 <input type="hidden" name="$loop1.image_existing_field" value="$loop1.image_url">
521 </div>
522 [/if]
523 <div class="form-group" style="margin-bottom:0">
524 <label class="form-label">[if:$loop1.has_image]Replace image (optional)[/if][if:!$loop1.has_image]Image (optional)[/if]</label>
525 <input class="input" type="file" name="$loop1.image_field" accept="image/*">
526 <div class="form-help">Used when the tested element is an image, product tile, or hero video.</div>
527 </div>
528 </div>
529
530 <!-- Layout picker: shown when surface is layout / layout_and_theme.
531 Value 0 means "use the storefront's saved layout" -- only useful
532 for the control variant. -->
533 <div class="v-layout-group" style="display:none">
534 <div class="form-group">
535 <label class="form-label">Page layout</label>
536 <select class="select v-layout-select" name="$loop1.layout_id_field" data-saved="$loop1.layout_id">
537 <option value="0">[if:$loop1.is_control]Use my saved layout (control)[/if][if:!$loop1.is_control]&mdash; pick a layout &mdash;[/if]</option>
538 [loop:@tvars.layout_options]<option value="$loop2.id">$loop2.name &mdash; $loop2.tagline</option>[/loop]
539 </select>
540 </div>
541 </div>
542
543 <!-- Theme picker: shown when surface is theme / layout_and_theme.
544 We give built-in themes and the seller's saved custom themes
545 in one dropdown using a "kind:id" value so the action handler
546 can route to the right column (theme_id vs custom_theme_id). -->
547 <div class="v-theme-group" style="display:none">
548 <div class="form-group">
549 <label class="form-label">Color theme</label>
550 <select class="select v-theme-select" data-vid="$loop1.vid"
551 data-saved-builtin="$loop1.theme_id"
552 data-saved-custom="$loop1.custom_theme_id">
553 <option value="builtin:0">[if:$loop1.is_control]Use my saved theme (control)[/if][if:!$loop1.is_control]&mdash; pick a theme &mdash;[/if]</option>
554 <optgroup label="Built-in themes">
555 [loop:@tvars.theme_options]<option value="builtin:$loop2.id">$loop2.name</option>[/loop]
556 </optgroup>
557 [if:$has_custom_themes]
558 <optgroup label="My saved custom themes">
559 [loop:@tvars.custom_theme_options]<option value="custom:$loop2.id">$loop2.name</option>[/loop]
560 </optgroup>
561 [/if]
562 </select>
563 <!-- Hidden numeric fields that actually post. The JS keeps
564 them synced with the unified dropdown above. -->
565 <input type="hidden" name="$loop1.theme_id_field" class="v-theme-id-input" value="$loop1.theme_id">
566 <input type="hidden" name="$loop1.custom_theme_id_field" class="v-custom-theme-id-input" value="$loop1.custom_theme_id">
567 </div>
568 </div>
569
570 </div>
571 </div>
572 [/loop]
573 </div>
574
575 <div class="flex gap-2">
576 <button type="submit" class="btn btn-primary">Save as draft</button>
577 <a href="/optimization.cgi" class="btn btn-secondary">Cancel</a>
578 </div>
579 </form>
580
581 <script>
582 (function () {
583 // In edit mode, seed each select to the stored value before
584 // wiring the toggle. The CGI passes $edit_* tvars that are
585 // empty strings when creating a fresh experiment.
586 var EDIT = {
587 test_type: "$edit_test_type",
588 surface: "$edit_surface",
589 primary_metric: "$edit_primary_metric",
590 target_model_id: "$edit_target_model_id"
591 };
592 function setSelect(name, value) {
593 if (!value) return;
594 var el = document.querySelector('select[name="' + name + '"]');
595 if (el) el.value = value;
596 }
597 setSelect('test_type', EDIT.test_type);
598 setSelect('surface', EDIT.surface);
599 setSelect('primary_metric', EDIT.primary_metric);
600 setSelect('target_model_id', EDIT.target_model_id);
601
602 // Restore each variant card's saved layout + theme picks.
603 // The hidden inputs carry the canonical numbers (theme_id /
604 // custom_theme_id); the unified dropdown is what the seller
605 // touches and we keep them in sync via the change handler
606 // wired further down.
607 document.querySelectorAll('.v-layout-select').forEach(function (sel) {
608 var saved = sel.getAttribute('data-saved');
609 if (saved && saved !== '0') sel.value = saved;
610 });
611 document.querySelectorAll('.v-theme-select').forEach(function (sel) {
612 var b = sel.getAttribute('data-saved-builtin') || '0';
613 var c = sel.getAttribute('data-saved-custom') || '0';
614 if (c !== '0') sel.value = 'custom:' + c;
615 else if (b !== '0') sel.value = 'builtin:' + b;
616 else sel.value = 'builtin:0';
617 });
618
619 // Theme picker -> hidden inputs. "builtin:N" sets theme_id=N,
620 // "custom:N" sets custom_theme_id=N; the other one is zeroed.
621 document.querySelectorAll('.v-theme-select').forEach(function (sel) {
622 sel.addEventListener('change', function () {
623 var card = sel.closest('.variant-card');
624 if (!card) return;
625 var binp = card.querySelector('.v-theme-id-input');
626 var cinp = card.querySelector('.v-custom-theme-id-input');
627 var parts = (sel.value || 'builtin:0').split(':');
628 var kind = parts[0], num = parts[1] || '0';
629 if (kind === 'custom') { if (binp) binp.value = '0'; if (cinp) cinp.value = num; }
630 else { if (cinp) cinp.value = '0'; if (binp) binp.value = num; }
631 });
632 });
633
634 // Show / hide field groups based on the selected surface.
635 // - product_tile: text, image, target_model picker
636 // - hero_image / hero_video / product_tile: image is primary
637 // - hero_title / hero_subtitle / cta_label / section_heading / custom: text only
638 // - layout: layout picker per variant
639 // - theme: theme picker per variant
640 // - layout_and_theme: both pickers per variant
641 var sel = document.getElementById('surface-select');
642 var tmGrp = document.getElementById('target-model-group');
643 var picker = document.getElementById('target-model-select');
644 var tt = document.querySelector('select[name="test_type"]');
645 var addBtn = document.getElementById('add-variant-btn');
646 if (!sel) return;
647
648 var TEXT_SURFACES = { hero_title:1, hero_subtitle:1, cta_label:1, section_heading:1, custom:1 };
649 var IMAGE_SURFACES = { hero_image:1, hero_video:1, product_tile:1 };
650 var LAYOUT_SURFACES = { layout:1, layout_and_theme:1 };
651 var THEME_SURFACES = { theme:1, layout_and_theme:1 };
652
653 function sync() {
654 var s = sel.value;
655 var isProductTile = (s === 'product_tile');
656 if (tmGrp) tmGrp.style.display = isProductTile ? '' : 'none';
657 if (picker) picker.required = isProductTile;
658
659 // Cards: hide text/image groups for design surfaces; show
660 // layout/theme groups for design surfaces. Product tile keeps
661 // both text + image visible (variants tweak title + tile image).
662 document.querySelectorAll('.variant-card').forEach(function (card) {
663 var tg = card.querySelector('.v-text-group');
664 var ig = card.querySelector('.v-image-group');
665 var lg = card.querySelector('.v-layout-group');
666 var thg = card.querySelector('.v-theme-group');
667 if (tg) tg.style.display = (TEXT_SURFACES[s] || s === 'product_tile') ? '' : 'none';
668 if (ig) ig.style.display = (IMAGE_SURFACES[s]) ? '' : 'none';
669 if (lg) lg.style.display = LAYOUT_SURFACES[s] ? '' : 'none';
670 if (thg) thg.style.display = THEME_SURFACES[s] ? '' : 'none';
671 });
672
673 syncAddBtn();
674 }
675 sel.addEventListener('change', sync);
676
677 // MVT: allow up to 6 variants. The "+ Add variant" button is
678 // visible whenever test_type === 'mvt' (or whenever the seller
679 // has more than 2 variants already, because they MIGHT be a
680 // MVT that the user is downgrading to A/B and the extra rows
681 // are still loaded).
682 function syncAddBtn() {
683 if (!addBtn) return;
684 var n = document.querySelectorAll('.variant-card').length;
685 addBtn.style.display = (tt && tt.value === 'mvt' && n < 6) ? '' : 'none';
686 // Remove buttons only visible on MVT and not for control.
687 document.querySelectorAll('.remove-variant-btn').forEach(function (b) {
688 b.style.display = (tt && tt.value === 'mvt') ? '' : 'none';
689 });
690 }
691 if (tt) tt.addEventListener('change', syncAddBtn);
692
693 // Build a new variant card by cloning the last one + bumping
694 // its vid. Cheaper than asking the server for an empty card.
695 var LETTERS = ['A','B','C','D','E','F'];
696 function nextVid() {
697 for (var i = 0; i < LETTERS.length; i++) {
698 if (!document.querySelector('.variant-card[data-vid="' + LETTERS[i] + '"]')) {
699 return LETTERS[i];
700 }
701 }
702 return null;
703 }
704 if (addBtn) {
705 addBtn.addEventListener('click', function () {
706 var newVid = nextVid();
707 if (!newVid) return;
708 var last = document.querySelector('.variant-card[data-vid="B"]')
709 || document.querySelector('.variant-card');
710 if (!last) return;
711 var clone = last.cloneNode(true);
712 clone.setAttribute('data-vid', newVid);
713 // Re-label.
714 var lbl = clone.querySelector('.text-xs');
715 if (lbl) lbl.textContent = 'Variant ' + newVid + ' . Challenger';
716 // Rename every form field from variant_B_* -> variant_<NEW>_*.
717 clone.querySelectorAll('[name]').forEach(function (el) {
718 el.name = el.name.replace(/^variant_[A-F]_/, 'variant_' + newVid + '_');
719 // Reset the new card's values.
720 if (el.tagName === 'TEXTAREA' || (el.tagName === 'INPUT' && el.type !== 'file' && el.type !== 'hidden')) {
721 el.value = '';
722 }
723 if (el.tagName === 'INPUT' && el.type === 'hidden') {
724 el.value = '0';
725 }
726 if (el.tagName === 'SELECT') {
727 el.selectedIndex = 0;
728 }
729 });
730 // Re-attach the change listener on this clone's theme select.
731 var ts = clone.querySelector('.v-theme-select');
732 if (ts) {
733 ts.setAttribute('data-vid', newVid);
734 ts.addEventListener('change', function () {
735 var card = ts.closest('.variant-card');
736 var binp = card.querySelector('.v-theme-id-input');
737 var cinp = card.querySelector('.v-custom-theme-id-input');
738 var parts = (ts.value || 'builtin:0').split(':');
739 var kind = parts[0], num = parts[1] || '0';
740 if (kind === 'custom') { if (binp) binp.value = '0'; if (cinp) cinp.value = num; }
741 else { if (cinp) cinp.value = '0'; if (binp) binp.value = num; }
742 });
743 }
744 // Remove the "current image" preview (only the original may have one).
745 var preview = clone.querySelector('.v-image-group > .form-group:first-child');
746 if (preview && preview.querySelector('input[type=hidden]')) preview.remove();
747 // Wire up the new card's remove button.
748 var rm = clone.querySelector('.remove-variant-btn');
749 if (rm) {
750 rm.setAttribute('data-vid', newVid);
751 rm.addEventListener('click', function () {
752 clone.remove();
753 syncAddBtn();
754 });
755 }
756 document.getElementById('variants-container').appendChild(clone);
757 sync(); // make sure new card respects current surface
758 });
759 }
760
761 // Wire existing remove buttons (only present on challenger cards).
762 document.querySelectorAll('.remove-variant-btn').forEach(function (rm) {
763 rm.addEventListener('click', function () {
764 var card = rm.closest('.variant-card');
765 if (card) card.remove();
766 syncAddBtn();
767 });
768 });
769
770 sync();
771 })();
772 </script>
773 [/if]
774 </div>
775 </div>
776 [/if]
777
778 [if:$mode_list]
779 <!-- ============ YOUR EXPERIMENTS (three lifecycle sections) ===========
780 Active (drafts/running/paused) on top, Completed below it,
781 Archived at the bottom. Each has its own action set fitted to
782 the lifecycle stage. -->
783
784 [if:$has_active_experiments]
785 <div class="module mb-3">
786 <div class="module-head">
787 <div class="left">
788 <div class="module-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19 12a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"/></svg></div>
789 <div>
790 <div class="module-title">Active experiments</div>
791 <div class="module-sub">$active_count in flight &middot; drafts, running, and paused</div>
792 </div>
793 </div>
794 </div>
795 <div class="table-wrap" style="border:none;border-radius:0">
796 <table class="table exp-table">
797 <thead><tr>
798 <th class="exp-col-name">Name</th>
799 <th>Type</th>
800 <th>Surface</th>
801 <th>Status</th>
802 <th class="num">Lift</th>
803 <th class="num">Confidence</th>
804 <th class="num">Visitors</th>
805 <th class="exp-col-actions">Actions</th>
806 </tr></thead>
807 <tbody>
808 [loop:@experiments_active]
809 <tr>
810 <td>
811 <a href="/optimization.cgi?id=$loop1.id" style="color:inherit;text-decoration:none">
812 <strong style="color:var(--col-accent-bright)">$loop1.name</strong>
813 </a>
814 <div class="text-xs text-dim">$loop1.storefront</div>
815 </td>
816 <td>$loop1.test_type</td>
817 <td class="text-xs text-dim">$loop1.surface</td>
818 <td>
819 [if:$loop1.is_draft]<span class="pill">Draft</span>[/if]
820 [if:$loop1.is_running]<span class="pill brand">Running</span>[/if]
821 [if:$loop1.is_paused]<span class="pill warning">Paused</span>[/if]
822 </td>
823 <td class="num">$loop1.lift</td>
824 <td class="num">$loop1.confidence</td>
825 <td class="num text-dim">$loop1.traffic_seen</td>
826 <td class="exp-actions-host">
827 <div class="exp-actions-cell">
828 <a href="/optimization.cgi?id=$loop1.id" class="exp-row-action is-primary">View report &rarr;</a>
829
830 [if:$loop1.is_draft]
831 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0">
832 <input type="hidden" name="act" value="start">
833 <input type="hidden" name="id" value="$loop1.id">
834 <button type="submit" class="exp-row-action is-success">Start</button>
835 </form>
836 [/if]
837 [if:$loop1.is_running]
838 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0">
839 <input type="hidden" name="act" value="pause">
840 <input type="hidden" name="id" value="$loop1.id">
841 <button type="submit" class="exp-row-action is-warning">Pause</button>
842 </form>
843 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0"
844 data-confirm-title="Mark complete"
845 data-confirm-message="$loop1.complete_confirm"
846 data-confirm-label="Complete"
847 data-confirm-style="success">
848 <input type="hidden" name="act" value="complete">
849 <input type="hidden" name="id" value="$loop1.id">
850 <button type="submit" class="exp-row-action is-success">Complete</button>
851 </form>
852 [/if]
853 [if:$loop1.is_paused]
854 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0">
855 <input type="hidden" name="act" value="start">
856 <input type="hidden" name="id" value="$loop1.id">
857 <button type="submit" class="exp-row-action is-success">Resume</button>
858 </form>
859 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0"
860 data-confirm-title="Mark complete"
861 data-confirm-message="$loop1.complete_confirm"
862 data-confirm-label="Complete"
863 data-confirm-style="success">
864 <input type="hidden" name="act" value="complete">
865 <input type="hidden" name="id" value="$loop1.id">
866 <button type="submit" class="exp-row-action is-success">Complete</button>
867 </form>
868 [/if]
869
870 <a href="/optimization.cgi?edit=$loop1.id" class="exp-row-action is-edit">Edit</a>
871
872 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0"
873 data-confirm-title="Reset stats"
874 data-confirm-message="The variants and configuration stay exactly the same, but every collected visitor exposure and conversion event is deleted. The status returns to Draft so you can deliberately re-start the test once you are ready."
875 data-confirm-label="Reset stats"
876 data-confirm-style="warning">
877 <input type="hidden" name="act" value="reset_stats">
878 <input type="hidden" name="id" value="$loop1.id">
879 <button type="submit" class="exp-row-action is-reset">Reset stats</button>
880 </form>
881
882 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0"
883 data-confirm-title="Delete experiment"
884 data-confirm-message="The experiment configuration and every visitor exposure / conversion event tied to it are removed permanently. This cannot be undone. If you might want this history later, use Archive instead -- it keeps the row out of your active list without erasing anything."
885 data-confirm-label="Delete permanently"
886 data-confirm-style="danger">
887 <input type="hidden" name="act" value="delete">
888 <input type="hidden" name="id" value="$loop1.id">
889 <button type="submit" class="exp-row-action is-danger">Delete</button>
890 </form>
891 </div>
892 </td>
893 </tr>
894 [/loop]
895 </tbody>
896 </table>
897 </div>
898 </div>
899 [/if]
900
901 [if:$has_completed_experiments]
902 <div class="module mb-3">
903 <div class="module-head">
904 <div class="left">
905 <div class="module-icon" style="background:rgba(34,197,94,0.15);color:#4ade80"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg></div>
906 <div>
907 <div class="module-title">Completed experiments</div>
908 <div class="module-sub">$completed_count tests called &middot; archive when you no longer need them</div>
909 </div>
910 </div>
911 </div>
912 <div class="table-wrap" style="border:none;border-radius:0">
913 <table class="table exp-table">
914 <thead><tr>
915 <th class="exp-col-name">Name</th>
916 <th>Type</th>
917 <th>Surface</th>
918 <th class="num">Lift</th>
919 <th class="num">Confidence</th>
920 <th class="num">Visitors</th>
921 <th class="exp-col-actions">Actions</th>
922 </tr></thead>
923 <tbody>
924 [loop:@experiments_completed]
925 <tr>
926 <td>
927 <a href="/optimization.cgi?id=$loop1.id" style="color:inherit;text-decoration:none">
928 <strong style="color:var(--col-accent-bright)">$loop1.name</strong>
929 </a>
930 <div class="text-xs text-dim">$loop1.storefront</div>
931 </td>
932 <td>$loop1.test_type</td>
933 <td class="text-xs text-dim">$loop1.surface</td>
934 <td class="num">$loop1.lift</td>
935 <td class="num">$loop1.confidence</td>
936 <td class="num text-dim">$loop1.traffic_seen</td>
937 <td class="exp-actions-host">
938 <div class="exp-actions-cell">
939 <a href="/optimization.cgi?id=$loop1.id" class="exp-row-action is-primary">View report &rarr;</a>
940
941 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0">
942 <input type="hidden" name="act" value="start">
943 <input type="hidden" name="id" value="$loop1.id">
944 <button type="submit" class="exp-row-action is-success">Re-run</button>
945 </form>
946
947 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0"
948 data-confirm-title="Archive experiment"
949 data-confirm-message="The experiment moves to the Archived section at the bottom of the page so it does not clutter your active or completed list. All configuration, visitor data, and the winner pick are preserved. You can Unarchive it later to bring it back."
950 data-confirm-label="Archive"
951 data-confirm-style="primary">
952 <input type="hidden" name="act" value="archive">
953 <input type="hidden" name="id" value="$loop1.id">
954 <button type="submit" class="exp-row-action">Archive</button>
955 </form>
956
957 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0"
958 data-confirm-title="Delete experiment"
959 data-confirm-message="The experiment configuration and every visitor exposure / conversion event tied to it are removed permanently. This cannot be undone. If you might want this history later, use Archive instead -- it keeps the row out of your active list without erasing anything."
960 data-confirm-label="Delete permanently"
961 data-confirm-style="danger">
962 <input type="hidden" name="act" value="delete">
963 <input type="hidden" name="id" value="$loop1.id">
964 <button type="submit" class="exp-row-action is-danger">Delete</button>
965 </form>
966 </div>
967 </td>
968 </tr>
969 [/loop]
970 </tbody>
971 </table>
972 </div>
973 </div>
974 [/if]
975
976 [if:$has_archived_experiments]
977 <div class="module mb-3" style="opacity:0.85">
978 <div class="module-head">
979 <div class="left">
980 <div class="module-icon" style="background:rgba(100,116,139,0.15);color:#94a3b8"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/></svg></div>
981 <div>
982 <div class="module-title">Archived experiments</div>
983 <div class="module-sub">$archived_count parked &middot; history only, not running</div>
984 </div>
985 </div>
986 </div>
987 <div class="table-wrap" style="border:none;border-radius:0">
988 <table class="table exp-table">
989 <thead><tr>
990 <th class="exp-col-name">Name</th>
991 <th>Type</th>
992 <th>Surface</th>
993 <th class="num">Lift</th>
994 <th class="num">Confidence</th>
995 <th class="num">Visitors</th>
996 <th class="exp-col-actions">Actions</th>
997 </tr></thead>
998 <tbody>
999 [loop:@experiments_archived]
1000 <tr>
1001 <td>
1002 <a href="/optimization.cgi?id=$loop1.id" style="color:inherit;text-decoration:none">
1003 <strong style="color:var(--col-text-2)">$loop1.name</strong>
1004 </a>
1005 <div class="text-xs text-dim">$loop1.storefront</div>
1006 </td>
1007 <td class="text-dim">$loop1.test_type</td>
1008 <td class="text-xs text-dim">$loop1.surface</td>
1009 <td class="num text-dim">$loop1.lift</td>
1010 <td class="num text-dim">$loop1.confidence</td>
1011 <td class="num text-dim">$loop1.traffic_seen</td>
1012 <td class="exp-actions-host">
1013 <div class="exp-actions-cell">
1014 <a href="/optimization.cgi?id=$loop1.id" class="exp-row-action is-primary">View report &rarr;</a>
1015
1016 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0">
1017 <input type="hidden" name="act" value="unarchive">
1018 <input type="hidden" name="id" value="$loop1.id">
1019 <button type="submit" class="exp-row-action">Unarchive</button>
1020 </form>
1021
1022 <form method="POST" action="/optimization_action.cgi" style="display:inline;margin:0"
1023 data-confirm-title="Delete experiment"
1024 data-confirm-message="The experiment configuration and every visitor exposure / conversion event tied to it are removed permanently. This cannot be undone. If you might want this history later, use Archive instead -- it keeps the row out of your active list without erasing anything."
1025 data-confirm-label="Delete permanently"
1026 data-confirm-style="danger">
1027 <input type="hidden" name="act" value="delete">
1028 <input type="hidden" name="id" value="$loop1.id">
1029 <button type="submit" class="exp-row-action is-danger">Delete</button>
1030 </form>
1031 </div>
1032 </td>
1033 </tr>
1034 [/loop]
1035 </tbody>
1036 </table>
1037 </div>
1038 </div>
1039 [/if]
1040 [/if]
1041
1042 [if:$mode_list]
1043 [if:!$has_experiments]
1044 <!-- ============ ONBOARDING ============
1045 Shown to users who do not yet have any real experiments. The
1046 demo content below this section is labelled "Example" so the
1047 user understands it is a preview, not their actual data. -->
1048 <div class="module mb-3" style="border-color:rgba(124,58,237,0.30);background:linear-gradient(135deg, rgba(124,58,237,0.06) 0%, var(--col-surface-1) 65%)">
1049 <div class="module-head">
1050 <div class="left">
1051 <div class="module-icon" style="background:rgba(124,58,237,0.18);color:var(--col-violet-bright)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div>
1052 <div>
1053 <div class="module-title">Welcome to the Optimization Suite</div>
1054 <div class="module-sub">No experiments yet &middot; here is how to launch your first one in under 2 minutes</div>
1055 </div>
1056 </div>
1057 <a href="/optimization.cgi?new=1" class="btn btn-primary">
1058 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
1059 Start your first experiment
1060 </a>
1061 </div>
1062 <div class="module-body">
1063 <p class="text-sm" style="color:var(--col-text-2);line-height:1.65;margin:0 0 16px">
1064 An experiment shows half your visitors one version of your storefront and the other half a different version, then measures which one converts buyers better. The Suite handles the bucketing, tracking, and the statistical math so you can focus on the ideas.
1065 </p>
1066
1067 <div class="dash-grid" style="grid-template-columns:repeat(4,1fr);gap:14px">
1068
1069 <div style="background:var(--col-surface-2);border:1px solid var(--col-border);border-radius:10px;padding:14px">
1070 <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
1071 <span style="width:24px;height:24px;border-radius:50%;background:var(--col-accent);color:#fff;display:grid;place-items:center;font-weight:700;font-size:12px">1</span>
1072 <div class="text-xs fw-700" style="color:var(--col-text);letter-spacing:0.5px;text-transform:uppercase">Pick a target</div>
1073 </div>
1074 <div class="text-xs" style="color:var(--col-text-2);line-height:1.55">
1075 Click <strong>+ New experiment</strong>. Choose what element you want to test &mdash; hero headline, hero image, CTA button label, a specific product tile, etc.
1076 </div>
1077 </div>
1078
1079 <div style="background:var(--col-surface-2);border:1px solid var(--col-border);border-radius:10px;padding:14px">
1080 <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
1081 <span style="width:24px;height:24px;border-radius:50%;background:var(--col-accent);color:#fff;display:grid;place-items:center;font-weight:700;font-size:12px">2</span>
1082 <div class="text-xs fw-700" style="color:var(--col-text);letter-spacing:0.5px;text-transform:uppercase">Write 2 variants</div>
1083 </div>
1084 <div class="text-xs" style="color:var(--col-text-2);line-height:1.55">
1085 Variant A is the control (your current version). Variant B is the challenger &mdash; the new copy or image you want to try. For image surfaces, upload a file.
1086 </div>
1087 </div>
1088
1089 <div style="background:var(--col-surface-2);border:1px solid var(--col-border);border-radius:10px;padding:14px">
1090 <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
1091 <span style="width:24px;height:24px;border-radius:50%;background:var(--col-accent);color:#fff;display:grid;place-items:center;font-weight:700;font-size:12px">3</span>
1092 <div class="text-xs fw-700" style="color:var(--col-text);letter-spacing:0.5px;text-transform:uppercase">Save + Start</div>
1093 </div>
1094 <div class="text-xs" style="color:var(--col-text-2);line-height:1.55">
1095 Save as a draft, review it, then hit <strong>Start &rarr;</strong> in the experiments table. From that moment, half your visitors see B and half see A.
1096 </div>
1097 </div>
1098
1099 <div style="background:var(--col-surface-2);border:1px solid var(--col-border);border-radius:10px;padding:14px">
1100 <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
1101 <span style="width:24px;height:24px;border-radius:50%;background:var(--col-success);color:#fff;display:grid;place-items:center;font-weight:700;font-size:12px">4</span>
1102 <div class="text-xs fw-700" style="color:var(--col-text);letter-spacing:0.5px;text-transform:uppercase">Ship the winner</div>
1103 </div>
1104 <div class="text-xs" style="color:var(--col-text-2);line-height:1.55">
1105 Lift and Confidence columns fill in as visitors land. When confidence crosses 95%, promote the winning variant and start the next test.
1106 </div>
1107 </div>
1108
1109 </div>
1110
1111 <div style="margin-top:16px;padding:12px 14px;background:var(--col-surface-2);border-left:3px solid var(--col-violet-bright);border-radius:6px">
1112 <div class="text-xs" style="color:var(--col-text-2);line-height:1.55">
1113 <strong style="color:var(--col-text)">Want to see what this looks like with activity?</strong>
1114 Click the <a href="/optimization.cgi?tutorial=1" class="text-brand fw-700">Sample Data View</a> button up top to walk through a populated version of every tab (A/B, Price tests, Pricing surveys, Promotions).
1115 </div>
1116 </div>
1117 </div>
1118 </div>
1119
1120 [/if]
1121
1122 <div class="banner danger mb-3">
1123 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z"/></svg>
1124 <div class="text-xs"><strong>Pricing transparency:</strong> WebSTLs supports global price tests only. We do not offer per‑user, per‑location, or per‑device pricing. Price changes during a buyer's session are blocked by default.</div>
1125 </div>
1126
1127 [if:$tutorial_mode]
1128 <div class="tabs" data-tab-group>
1129 <div class="tab active" data-target="ab">A/B + MVT</div>
1130 <div class="tab" data-target="price">Price tests</div>
1131 <div class="tab" data-target="vw">Pricing surveys</div>
1132 <div class="tab" data-target="promo">Promotions</div>
1133 </div>
1134
1135 <!-- A/B + MVT -->
1136 <div data-pane="ab">
1137 <div class="dash-grid mb-3">
1138 <div class="module">
1139 <div class="module-head">
1140 <div class="left">
1141 <div class="module-icon" style="background:rgba(124,58,237,0.15);color:var(--col-violet-bright)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg></div>
1142 <div><div class="module-title">Hero variant · Storefront home</div><div class="module-sub">2,841 visitors · 7 days running · A/B</div></div>
1143 </div>
1144 <span class="pill success">Call winner</span>
1145 </div>
1146 <div class="module-body">
1147 <div class="dash-grid-2" style="gap:18px">
1148 <div style="background:var(--col-surface-2);border-radius:10px;border:1px solid var(--col-border);padding:14px">
1149 <div class="text-xs text-dim mb-2" style="letter-spacing:1.5px;text-transform:uppercase">Variant A · Control</div>
1150 <div style="aspect-ratio:1.6;background:linear-gradient(140deg,#0d1224,#1a2540);border-radius:8px;display:grid;place-items:center;margin-bottom:10px">
1151 <div style="text-align:center"><div style="font-family:var(--font-display);font-size:18px;color:#fff">Premium Resin Minis</div><div class="text-xs text-dim">Pre‑supported · ready to print</div></div>
1152 </div>
1153 <div class="flex justify-between"><span class="text-xs text-dim">Conversion</span><span class="font-mono fw-700 text-sm">3.42%</span></div>
1154 <div class="progress mt-1"><div class="progress-fill" style="width:48%"></div></div>
1155 <div class="flex justify-between text-xs text-dim mt-1"><span>1,419 visitors</span><span>49 purchases</span></div>
1156 </div>
1157 <div style="background:rgba(34,197,94,0.06);border-radius:10px;border:1px solid rgba(34,197,94,0.3);padding:14px">
1158 <div class="text-xs mb-2" style="letter-spacing:1.5px;text-transform:uppercase;color:var(--col-success)">Variant B · Winning ★</div>
1159 <div style="aspect-ratio:1.6;background:linear-gradient(140deg,#1a1142,#7c3aed);border-radius:8px;display:grid;place-items:center;margin-bottom:10px;position:relative">
1160 <div style="position:absolute;inset:0;background-image:radial-gradient(rgba(255,255,255,0.06) 1px, transparent 1px);background-size:20px 20px"></div>
1161 <div style="text-align:center;position:relative"><div style="font-family:var(--font-display);font-size:18px;color:#fff">Print fast. Sell faster.</div><div class="text-xs" style="color:#cbd5e1">Battle‑ready in 6 hours of cure time</div></div>
1162 </div>
1163 <div class="flex justify-between"><span class="text-xs text-dim">Conversion</span><span class="font-mono fw-700 text-sm text-success">5.18%</span></div>
1164 <div class="progress mt-1"><div class="progress-fill" style="width:74%;background:var(--grad-success)"></div></div>
1165 <div class="flex justify-between text-xs text-dim mt-1"><span>1,422 visitors</span><span>74 purchases</span></div>
1166 </div>
1167 </div>
1168
1169 <div class="divider"></div>
1170
1171 <div class="dash-grid-3">
1172 <div>
1173 <div class="text-xs text-dim" style="letter-spacing:1.5px;text-transform:uppercase">Lift</div>
1174 <div style="font-family:var(--font-display);font-size:24px;font-weight:700;color:var(--col-success)">+51.5%</div>
1175 <div class="text-xs text-dim">B over A</div>
1176 </div>
1177 <div>
1178 <div class="text-xs text-dim" style="letter-spacing:1.5px;text-transform:uppercase">Confidence</div>
1179 <div style="font-family:var(--font-display);font-size:24px;font-weight:700;color:#fff">96.4%</div>
1180 <div class="text-xs text-success">Above 95% threshold</div>
1181 </div>
1182 <div>
1183 <div class="text-xs text-dim" style="letter-spacing:1.5px;text-transform:uppercase">Projected lift /mo</div>
1184 <div style="font-family:var(--font-display);font-size:24px;font-weight:700;color:#fff">+\$1,184</div>
1185 <div class="text-xs text-dim">est. at current traffic</div>
1186 </div>
1187 </div>
1188
1189 <div class="flex gap-2 mt-3">
1190 <button class="btn btn-success">✓ Promote variant B</button>
1191 <button class="btn btn-secondary">Continue running</button>
1192 <button class="btn btn-ghost">Archive</button>
1193 </div>
1194 </div>
1195 </div>
1196
1197 <div class="module">
1198 <div class="module-head"><div class="left"><div class="module-icon" style="background:rgba(124,58,237,0.15);color:var(--col-violet-bright)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg></div><div><div class="module-title">Quick experiment ideas</div><div class="module-sub">From peer winning patterns</div></div></div></div>
1199 <div class="module-body" style="display:flex;flex-direction:column;gap:10px">
1200 <div style="padding:12px;border-radius:10px;background:var(--col-surface-2);border:1px solid var(--col-border)">
1201 <div class="text-sm fw-600" style="color:#fff">Add a "Free model of the week" CTA above the fold</div>
1202 <div class="text-xs text-dim mt-1">Peers in your segment see +14% email captures</div>
1203 <button class="btn btn-secondary btn-sm mt-2">Set up test →</button>
1204 </div>
1205 <div style="padding:12px;border-radius:10px;background:var(--col-surface-2);border:1px solid var(--col-border)">
1206 <div class="text-sm fw-600" style="color:#fff">Test 4‑col grid vs 3‑col on catalog</div>
1207 <div class="text-xs text-dim mt-1">Higher density correlates with +6% AOV at \$5–15 price points</div>
1208 <button class="btn btn-secondary btn-sm mt-2">Set up test →</button>
1209 </div>
1210 <div style="padding:12px;border-radius:10px;background:var(--col-surface-2);border:1px solid var(--col-border)">
1211 <div class="text-sm fw-600" style="color:#fff">Bundle suggestion on PDP</div>
1212 <div class="text-xs text-dim mt-1">Show "Often bought with" — peer median +9% UPT</div>
1213 <button class="btn btn-secondary btn-sm mt-2">Set up test →</button>
1214 </div>
1215 </div>
1216 </div>
1217 </div>
1218
1219 <!-- Test history table -->
1220 <div class="module">
1221 <div class="module-head"><div class="left"><div class="module-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></div><div><div class="module-title">Experiment history</div></div></div></div>
1222 <div class="table-wrap" style="border:none;border-radius:0">
1223 <table class="table">
1224 <thead><tr><th>Name</th><th>Type</th><th>Status</th><th class="num">Lift</th><th class="num">Confidence</th><th class="num">Ended</th></tr></thead>
1225 <tbody>
1226 <tr><td><strong>Hero variant</strong></td><td>A/B</td><td><span class="pill success">Winning</span></td><td class="num text-success">+51.5%</td><td class="num">96.4%</td><td class="num text-dim">running</td></tr>
1227 <tr><td><strong>Bundle CTA copy</strong></td><td>A/B</td><td><span class="pill brand">Running</span></td><td class="num text-secondary">+8.4%</td><td class="num">71%</td><td class="num text-dim">running</td></tr>
1228 <tr><td><strong>Catalog grid layout</strong></td><td>MVT</td><td><span class="pill">Complete</span></td><td class="num text-success">+12.0%</td><td class="num">98.1%</td><td class="num text-dim">12d ago</td></tr>
1229 <tr><td><strong>Free shipping banner</strong></td><td>A/B</td><td><span class="pill">Complete</span></td><td class="num text-danger">−4.2%</td><td class="num">92%</td><td class="num text-dim">28d ago</td></tr>
1230 <tr><td><strong>Pricing card design</strong></td><td>A/B</td><td><span class="pill">Aborted</span></td><td class="num">—</td><td class="num">—</td><td class="num text-dim">42d ago</td></tr>
1231 </tbody>
1232 </table>
1233 </div>
1234 </div>
1235 </div>
1236
1237 <!-- Price tests -->
1238 <div data-pane="price" class="hide">
1239 <div class="module">
1240 <div class="module-head"><div class="left"><div class="module-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg></div><div><div class="module-title">Active price tests</div><div class="module-sub">Global · creator‑defined rules · audit logged</div></div></div></div>
1241 <div class="module-body" style="display:flex;flex-direction:column;gap:14px">
1242 <div style="padding:16px;border-radius:12px;background:var(--col-surface-2);border:1px solid var(--col-border)">
1243 <div class="flex justify-between items-center mb-2">
1244 <div>
1245 <div class="text-sm fw-600" style="color:#fff">Dragon Lord MK‑II</div>
1246 <div class="text-xs text-dim">Range \$9–\$15 · Step \$1 · Adjusts every 7d if conv. moves &gt; 5%</div>
1247 </div>
1248 <span class="pill brand">Auto‑adjusting</span>
1249 </div>
1250 <div style="height:8px;background:var(--col-surface-3);border-radius:999px;position:relative;overflow:hidden;margin:14px 0">
1251 <div style="position:absolute;top:0;left:30%;right:25%;bottom:0;background:var(--grad-brand);border-radius:999px"></div>
1252 <div style="position:absolute;top:-4px;left:50%;width:16px;height:16px;border-radius:50%;background:#fff;border:3px solid var(--col-accent);transform:translateX(-50%)"></div>
1253 </div>
1254 <div class="flex justify-between text-xs">
1255 <span class="text-dim">\$9.00</span>
1256 <span class="text-brand fw-700">Currently \$12.00</span>
1257 <span class="text-dim">\$15.00</span>
1258 </div>
1259 <div class="text-xs text-dim mt-2">Last adjusted 4d ago: \$11 → \$12 after +8% conv. lift held</div>
1260 </div>
1261
1262 <div style="padding:16px;border-radius:12px;background:var(--col-surface-2);border:1px solid var(--col-border)">
1263 <div class="flex justify-between items-center mb-2">
1264 <div>
1265 <div class="text-sm fw-600" style="color:#fff">Bridge Terrain Pack</div>
1266 <div class="text-xs text-dim">Range \$15–\$22 · Adjustment paused (low traffic)</div>
1267 </div>
1268 <span class="pill warning">Paused</span>
1269 </div>
1270 <div style="height:8px;background:var(--col-surface-3);border-radius:999px;position:relative;overflow:hidden;margin:14px 0">
1271 <div style="position:absolute;top:0;left:0;width:42%;height:100%;background:var(--grad-brand);border-radius:999px;opacity:0.5"></div>
1272 <div style="position:absolute;top:-4px;left:42%;width:16px;height:16px;border-radius:50%;background:#fff;border:3px solid var(--col-accent);transform:translateX(-50%);opacity:0.7"></div>
1273 </div>
1274 <div class="flex justify-between text-xs">
1275 <span class="text-dim">\$15.00</span>
1276 <span class="text-secondary fw-700">Currently \$18.00</span>
1277 <span class="text-dim">\$22.00</span>
1278 </div>
1279 <div class="text-xs text-dim mt-2">Resumes when traffic ≥ 50 visitors/day for 5 consecutive days</div>
1280 </div>
1281 </div>
1282 </div>
1283 </div>
1284
1285 <!-- Pricing surveys (Van Westendorp methodology under the hood) -->
1286 <div data-pane="vw" class="hide">
1287 <div class="dash-grid">
1288 <div class="module">
1289 <div class="module-head"><div class="left"><div class="module-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h4M9 11V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v6m-6 0h6m4 0h2a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-2"/></svg></div><div><div class="module-title">Survey results · Dragon Lord MK‑II</div><div class="module-sub">847 responses · sent to email + storefront</div></div></div></div>
1290 <div class="module-body">
1291 <div style="background:var(--col-surface-2);border-radius:12px;padding:18px;border:1px solid var(--col-border)">
1292 <div class="text-sm fw-600 mb-2" style="color:#fff">Acceptable price range (PSM intersection)</div>
1293 <div style="height:12px;background:var(--col-surface-3);border-radius:999px;position:relative;overflow:hidden">
1294 <div style="position:absolute;top:0;left:30%;right:38%;bottom:0;background:var(--grad-success);border-radius:999px"></div>
1295 </div>
1296 <div class="flex justify-between text-xs text-dim mt-1"><span>\$5</span><span>\$10</span><span>\$15</span><span>\$20</span></div>
1297 <div class="text-center mt-2" style="font-family:var(--font-display);font-size:22px;font-weight:700;color:var(--col-success)">\$11.50 – \$14.00</div>
1298 <div class="text-xs text-dim text-center">Optimal Price Point: \$12.20 · Indifference Price Point: \$12.80</div>
1299 </div>
1300
1301 <div class="dash-grid-2 mt-3" style="gap:12px">
1302 <div style="padding:12px;border-radius:10px;background:var(--col-surface-2);border:1px solid var(--col-border)">
1303 <div class="text-xs text-dim">Too cheap (quality concern)</div>
1304 <div class="font-mono fw-700 text-md" style="color:#fff">below \$7</div>
1305 </div>
1306 <div style="padding:12px;border-radius:10px;background:var(--col-surface-2);border:1px solid var(--col-border)">
1307 <div class="text-xs text-dim">Too expensive</div>
1308 <div class="font-mono fw-700 text-md" style="color:#fff">above \$16</div>
1309 </div>
1310 </div>
1311 </div>
1312 </div>
1313
1314 <div class="module">
1315 <div class="module-head"><div class="left"><div class="module-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div><div><div class="module-title">Run a new survey</div></div></div></div>
1316 <div class="module-body">
1317 <div class="form-group">
1318 <label class="form-label">Product</label>
1319 <select class="select"><option>Dragon Lord MK‑II</option><option>Skirmisher Set Vol.4</option><option>Bridge Terrain Pack</option></select>
1320 </div>
1321 <div class="form-group">
1322 <label class="form-label">Send to</label>
1323 <label class="checkbox mb-1"><input type="checkbox" checked /> Email subscribers (4,218)</label>
1324 <label class="checkbox mb-1"><input type="checkbox" checked /> Storefront visitors (post‑checkout)</label>
1325 <label class="checkbox"><input type="checkbox" /> Patreon members</label>
1326 </div>
1327 <div class="form-group">
1328 <label class="form-label">Survey type</label>
1329 <select class="select"><option>Price sensitivity / Van Westendorp (recommended)</option><option>Willingness to pay / Gabor‑Granger</option><option>Custom 5‑question</option></select>
1330 </div>
1331 <button class="btn btn-primary btn-block">Launch survey</button>
1332 </div>
1333 </div>
1334 </div>
1335 </div>
1336
1337 <!-- Promotions -->
1338 <div data-pane="promo" class="hide">
1339 <div class="module">
1340 <div class="module-head">
1341 <div class="left"><div class="module-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m20.59 13.41-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82Z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg></div><div><div class="module-title">Active promotions</div><div class="module-sub">Manual + auto‑triggered when sales velocity drops</div></div></div>
1342 <button class="btn btn-secondary btn-sm">New promotion</button>
1343 </div>
1344 <div class="table-wrap" style="border:none;border-radius:0">
1345 <table class="table">
1346 <thead><tr><th>Name</th><th>Trigger</th><th>Discount</th><th>Uses</th><th>Status</th><th>Ends</th></tr></thead>
1347 <tbody>
1348 <tr><td><strong>SUMMER10</strong><div class="text-xs text-dim">Code · whole store</div></td><td>Manual</td><td>10% off</td><td>184 / 500</td><td><span class="pill success">Active</span></td><td class="text-dim">in 12d</td></tr>
1349 <tr><td><strong>Bridge Terrain rescue</strong><div class="text-xs text-dim">Auto · single product</div></td><td>Velocity &lt;3 sales/wk</td><td>15% off</td><td>22 / ∞</td><td><span class="pill brand">Auto‑triggered</span></td><td class="text-dim">7d after trigger</td></tr>
1350 <tr><td><strong>Newsletter signup</strong><div class="text-xs text-dim">Code · gated</div></td><td>Email opt‑in</td><td>\$5 off first order</td><td>412 / ∞</td><td><span class="pill success">Active</span></td><td class="text-dim">never</td></tr>
1351 <tr><td><strong>BUNDLE3</strong><div class="text-xs text-dim">Auto · cart</div></td><td>Cart ≥ 3 items</td><td>BOGO 50%</td><td>78 / ∞</td><td><span class="pill success">Active</span></td><td class="text-dim">30d after sale</td></tr>
1352 </tbody>
1353 </table>
1354 </div>
1355 </div>
1356 </div>
1357 [/if]
1358
1359 [if:!$tutorial_mode]
1360 <!-- Real-mode footer: pointers to the other optimization surfaces
1361 whose full UIs live on their own pages. Replaces the hardcoded
1362 sample tabs that used to render here. -->
1363 <div class="dash-grid mb-3">
1364 <div class="module">
1365 <div class="module-head">
1366 <div class="left">
1367 <div class="module-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.59 13.41 13.42 20.58a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg></div>
1368 <div>
1369 <div class="module-title">Promotions</div>
1370 <div class="module-sub">Coupons, sales, bundles &middot; live UI</div>
1371 </div>
1372 </div>
1373 <a href="/promotions.cgi" class="btn btn-secondary btn-sm">Open promotions &rarr;</a>
1374 </div>
1375 <div class="module-body">
1376 <div class="text-xs text-dim" style="line-height:1.55">Create coupon codes, time-limited sales, bundles and shareable promo links on the Promotions page. Caps by redemptions, by date, or by per-buyer limit.</div>
1377 </div>
1378 </div>
1379 <div class="module">
1380 <div class="module-head">
1381 <div class="left">
1382 <div class="module-icon" style="background:rgba(245,158,11,0.15);color:#fbbf24"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg></div>
1383 <div>
1384 <div class="module-title">Price tests &amp; surveys</div>
1385 <div class="module-sub">Bandit-style price tests + Van Westendorp Price Sensitivity Meter surveys</div>
1386 </div>
1387 </div>
1388 </div>
1389 <div class="module-body">
1390 <div class="text-xs text-dim" style="line-height:1.55">Use <strong>price tests</strong> to explore a price range automatically (per-model bandit; current round is rule-based, full Thompson sampling lands with the price-test worker). Use <strong>surveys</strong> to ask buyers directly via Van Westendorp's four-question Price Sensitivity Meter and see the four canonical intersection points (PMC, PME, OPP, IPP).</div>
1391 </div>
1392 </div>
1393 </div>
1394
1395 [if:$has_opt_flash]
1396 <div class="banner $opt_flash_kind mb-3">
1397 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/></svg>
1398 <div class="text-sm fw-600">$opt_flash_msg</div>
1399 </div>
1400 [/if]
1401
1402 [if:$px_not_ready]
1403 <div class="banner warn mb-3" id="price">
1404 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/></svg>
1405 <div class="text-sm fw-600">Price tests + surveys require a schema migration. Run the migration in installation_instructions.html (section "Pricing experiments") and reload this page.</div>
1406 </div>
1407 [/if]
1408
1409 [if:$px_ready]
1410 <!-- =========================== PRICE TESTS =========================== -->
1411 <h2 id="price" style="font-size:18px;margin:24px 0 14px;color:var(--col-text)">Price tests</h2>
1412 <div class="module mb-3">
1413 <div class="module-body">
1414 [if:$has_price_tests]
1415 <table class="bil-itable" style="width:100%;margin-bottom:14px">
1416 <thead><tr>
1417 <th>Model</th><th class="num">Current</th><th class="num">Range</th><th class="num">Step</th><th>Status</th><th></th>
1418 </tr></thead>
1419 <tbody>
1420 [loop:@price_tests]
1421 <tr>
1422 <td>$loop1.model_title</td>
1423 <td class="num">$loop1.current_display</td>
1424 <td class="num">$loop1.min_display &mdash; $loop1.max_display</td>
1425 <td class="num">$loop1.step_display</td>
1426 <td>
1427 [if:$loop1.is_draft]<span class="pill open">Draft</span>[/if]
1428 [if:$loop1.is_running]<span class="pill paid">Running</span>[/if]
1429 [if:$loop1.is_paused]<span class="pill failed">Paused</span>[/if]
1430 [if:$loop1.is_complete]<span class="pill refunded">Complete</span>[/if]
1431 </td>
1432 <td style="text-align:right">
1433 [if:$loop1.is_draft]
1434 <form method="POST" action="/optimization_action.cgi" style="display:inline">
1435 <input type="hidden" name="act" value="set_test_status">
1436 <input type="hidden" name="test_id" value="$loop1.id">
1437 <input type="hidden" name="status" value="running">
1438 <button type="submit" class="btn btn-primary btn-sm" style="font-size:11px;padding:4px 9px">Start</button>
1439 </form>
1440 [/if]
1441 [if:$loop1.is_running]
1442 <form method="POST" action="/optimization_action.cgi" style="display:inline">
1443 <input type="hidden" name="act" value="set_test_status">
1444 <input type="hidden" name="test_id" value="$loop1.id">
1445 <input type="hidden" name="status" value="paused">
1446 <button type="submit" class="btn btn-secondary btn-sm" style="font-size:11px;padding:4px 9px">Pause</button>
1447 </form>
1448 [/if]
1449 [if:$loop1.is_paused]
1450 <form method="POST" action="/optimization_action.cgi" style="display:inline">
1451 <input type="hidden" name="act" value="set_test_status">
1452 <input type="hidden" name="test_id" value="$loop1.id">
1453 <input type="hidden" name="status" value="running">
1454 <button type="submit" class="btn btn-primary btn-sm" style="font-size:11px;padding:4px 9px">Resume</button>
1455 </form>
1456 [/if]
1457 <form method="POST" action="/optimization_action.cgi" style="display:inline"
1458 data-confirm-title="Delete this price test?"
1459 data-confirm-message="The test and its full history will be removed. The model returns to its base price."
1460 data-confirm-label="Delete test"
1461 data-confirm-style="danger">
1462 <input type="hidden" name="act" value="delete_price_test">
1463 <input type="hidden" name="test_id" value="$loop1.id">
1464 <button type="submit" class="btn btn-danger btn-sm" style="font-size:11px;padding:4px 9px">Delete</button>
1465 </form>
1466 </td>
1467 </tr>
1468 [/loop]
1469 </tbody>
1470 </table>
1471 [/if]
1472 [if:!$has_price_tests]
1473 <div class="text-sm text-dim" style="padding:8px 0">No price tests yet. Create one below to start exploring a price range automatically.</div>
1474 [/if]
1475
1476 <details>
1477 <summary style="cursor:pointer;font-weight:600;color:var(--col-accent);user-select:none">+ Create a price test</summary>
1478 <form method="POST" action="/optimization_action.cgi" style="margin-top:14px;padding:14px;border:1px solid var(--col-border);border-radius:8px;background:rgba(255,255,255,0.02);display:flex;flex-direction:column;gap:10px">
1479 <input type="hidden" name="act" value="create_price_test">
1480 <label class="text-xs text-dim">
1481 Model
1482 <select class="input" name="model_id" required style="margin-top:4px">
1483 <option value="">Pick a model...</option>
1484 [loop:@user_models]
1485 <option value="$loop1.id">$loop1.title</option>
1486 [/loop]
1487 </select>
1488 </label>
1489 <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px">
1490 <label class="text-xs text-dim">
1491 Min price (USD)
1492 <input class="input" type="number" name="min_price_dollars" min="0.01" step="0.01" required style="margin-top:4px">
1493 </label>
1494 <label class="text-xs text-dim">
1495 Max price (USD)
1496 <input class="input" type="number" name="max_price_dollars" min="0.01" step="0.01" required style="margin-top:4px">
1497 </label>
1498 <label class="text-xs text-dim">
1499 Step (USD)
1500 <input class="input" type="number" name="step_dollars" min="0.50" step="0.50" value="1.00" style="margin-top:4px">
1501 </label>
1502 </div>
1503 <div class="text-xs text-dim">Created in draft status. Click Start when you are ready. The runner adjusts price within the range based on conversion telemetry.</div>
1504 <div>
1505 <button type="submit" class="btn btn-primary btn-sm">Create test</button>
1506 </div>
1507 </form>
1508 </details>
1509 </div>
1510 </div>
1511
1512 <!-- =========================== SURVEYS =========================== -->
1513 <h2 id="survey" style="font-size:18px;margin:24px 0 14px;color:var(--col-text)">Van Westendorp surveys</h2>
1514 <div class="module mb-3">
1515 <div class="module-body">
1516 [if:$has_surveys]
1517 <table class="bil-itable" style="width:100%;margin-bottom:14px">
1518 <thead><tr>
1519 <th>Survey</th><th>Model</th><th class="num">Responses</th><th>Status</th><th>Public URL</th><th></th>
1520 </tr></thead>
1521 <tbody>
1522 [loop:@surveys]
1523 <tr>
1524 <td>$loop1.title</td>
1525 <td class="text-xs">$loop1.model_title</td>
1526 <td class="num">$loop1.response_count</td>
1527 <td>
1528 [if:$loop1.is_open]<span class="pill paid">Open</span>[/if]
1529 [if:$loop1.is_closed]<span class="pill void">Closed</span>[/if]
1530 </td>
1531 <td class="text-xs" style="word-break:break-all;font-family:var(--font-mono,monospace);color:var(--col-text-2);max-width:300px">
1532 <a href="$loop1.public_url" target="_blank" rel="noopener" class="text-brand">$loop1.public_url</a>
1533 </td>
1534 <td style="text-align:right;white-space:nowrap">
1535 <a href="$loop1.results_href" class="btn btn-secondary btn-sm" style="font-size:11px;padding:4px 9px">Results</a>
1536 [if:$loop1.is_open]
1537 <form method="POST" action="/optimization_action.cgi" style="display:inline"
1538 data-confirm-title="Close this survey?"
1539 data-confirm-message="Existing responses are kept, but the public URL stops accepting new submissions."
1540 data-confirm-label="Close survey"
1541 data-confirm-style="warning">
1542 <input type="hidden" name="act" value="close_survey">
1543 <input type="hidden" name="survey_id" value="$loop1.id">
1544 <button type="submit" class="btn btn-secondary btn-sm" style="font-size:11px;padding:4px 9px">Close</button>
1545 </form>
1546 [/if]
1547 <form method="POST" action="/optimization_action.cgi" style="display:inline"
1548 data-confirm-title="Delete this survey?"
1549 data-confirm-message="The survey and ALL of its responses are removed. This cannot be undone."
1550 data-confirm-label="Delete survey"
1551 data-confirm-style="danger">
1552 <input type="hidden" name="act" value="delete_survey">
1553 <input type="hidden" name="survey_id" value="$loop1.id">
1554 <button type="submit" class="btn btn-danger btn-sm" style="font-size:11px;padding:4px 9px">Delete</button>
1555 </form>
1556 </td>
1557 </tr>
1558 [/loop]
1559 </tbody>
1560 </table>
1561 [/if]
1562 [if:!$has_surveys]
1563 <div class="text-sm text-dim" style="padding:8px 0">No surveys yet. Van Westendorp's four-question Price Sensitivity Meter is the fastest way to find a defensible price band for a new model.</div>
1564 [/if]
1565
1566 [if:$show_vw_results]
1567 <div style="margin:14px 0 18px;padding:14px;border:1px solid var(--col-border);border-radius:8px;background:rgba(124,58,237,0.05)">
1568 <div class="text-xs text-dim" style="letter-spacing:1.5px;text-transform:uppercase;margin-bottom:8px">Results &middot; $vw_n responses</div>
1569 <div style="display:grid;grid-template-columns:repeat(4, minmax(0, 1fr));gap:12px">
1570 <div>
1571 <div class="text-xs text-dim">PMC (point of marginal cheapness)</div>
1572 <div style="font-size:20px;font-weight:700;color:#fff;font-family:var(--font-mono,monospace);margin-top:4px">$vw_pmc</div>
1573 <div class="text-xs text-dim" style="margin-top:2px">below this = too cheap doubts</div>
1574 </div>
1575 <div>
1576 <div class="text-xs text-dim">OPP (optimum price)</div>
1577 <div style="font-size:20px;font-weight:700;color:#4ade80;font-family:var(--font-mono,monospace);margin-top:4px">$vw_opp</div>
1578 <div class="text-xs text-dim" style="margin-top:2px">cheap = too-expensive crossover</div>
1579 </div>
1580 <div>
1581 <div class="text-xs text-dim">IPP (indifference price)</div>
1582 <div style="font-size:20px;font-weight:700;color:#a78bfa;font-family:var(--font-mono,monospace);margin-top:4px">$vw_ipp</div>
1583 <div class="text-xs text-dim" style="margin-top:2px">bargain = expensive crossover</div>
1584 </div>
1585 <div>
1586 <div class="text-xs text-dim">PME (point of marginal expensiveness)</div>
1587 <div style="font-size:20px;font-weight:700;color:#fff;font-family:var(--font-mono,monospace);margin-top:4px">$vw_pme</div>
1588 <div class="text-xs text-dim" style="margin-top:2px">above this = won't consider</div>
1589 </div>
1590 </div>
1591 <div class="text-xs text-dim" style="margin-top:12px;line-height:1.55">The acceptable price band is roughly PMC to PME. OPP is the most defensible single price; IPP is where the typical buyer's "bargain" perception meets their "expensive" perception &mdash; many sellers anchor list price at OPP and offer discounts down to IPP.</div>
1592 </div>
1593 [/if]
1594
1595 <details>
1596 <summary style="cursor:pointer;font-weight:600;color:var(--col-accent);user-select:none">+ Create a survey</summary>
1597 <form method="POST" action="/optimization_action.cgi" style="margin-top:14px;padding:14px;border:1px solid var(--col-border);border-radius:8px;background:rgba(255,255,255,0.02);display:flex;flex-direction:column;gap:10px">
1598 <input type="hidden" name="act" value="create_survey">
1599 <input type="hidden" name="survey_type" value="van_westendorp">
1600 <label class="text-xs text-dim">
1601 Model
1602 <select class="input" name="model_id" required style="margin-top:4px">
1603 <option value="">Pick a model...</option>
1604 [loop:@user_models]
1605 <option value="$loop1.id">$loop1.title</option>
1606 [/loop]
1607 </select>
1608 </label>
1609 <label class="text-xs text-dim">
1610 Survey title (shown to respondents)
1611 <input class="input" type="text" name="title" maxlength="191" placeholder="What would you pay for [model]?" required style="margin-top:4px">
1612 </label>
1613 <div class="text-xs text-dim">The four standard Van Westendorp questions are pre-filled. After creating, you will get a public URL to share with buyers (email, storefront link, social).</div>
1614 <div>
1615 <button type="submit" class="btn btn-primary btn-sm">Create survey</button>
1616 </div>
1617 </form>
1618 </details>
1619 </div>
1620 </div>
1621 [/if]
1622
1623 [/if]
1624 [/if]
1625
1626<!-- Confirm popups for any form with data-confirm-title are handled
1627 globally by /assets/javascript/confirm.js (loaded in the wrapper). -->
1628
1629<div class="opt-foot-spacer" data-end="optimization">&nbsp;</div>
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help