openvibers/openvibe.design Commits
Create: Research first says Off, the gallery as a door, and ?kind= opens a kind (#289)
Changes
@@@ -325,6 +325,14 @@ html.ar-fit .ar-shot--desktop .ar-shot-frame, html.ar-fit .ar-shot--desktop::bef@keyframes dvStep { 50% { transform: translateX(calc(5px * var(--ov-move, 1))); opacity: .6; } }/* Ideas to start from: those of the kind chosen above, each with its glyph. */.dv-examples { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }++/* The gallery's door under the examples (11-12 review: it had been a pink link run into the heading's text). */++.dv-create-door { display: flex; align-items: center; gap: 12px; margin-top: 18px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 14px;++ background: var(--bg-card, var(--bg-secondary)); color: var(--text-primary); text-decoration: none; transition: border-color .15s; }++.dv-create-door:hover, .dv-create-door:focus-visible { border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }++.dv-create-door-ic { display: grid; place-items: center; flex: none; width: 40px; height: 40px; border-radius: 12px; color: var(--accent-light, var(--accent)); background: color-mix(in srgb, var(--accent) 14%, transparent); }++.dv-create-door b { display: block; font-size: .95rem; }++.dv-create-door small { display: block; margin-top: 2px; color: var(--text-secondary); font-size: .84rem; line-height: 1.4; }++.dv-create-door-go { margin-left: auto; color: var(--text-muted); font-size: 1.1rem; }.dv-examples button { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; border: 1px solid var(--border); border-radius: 99px; padding: 0 14px 0 11px; background: var(--bg-card); color: var(--text-secondary); font: inherit; font-size: .86rem; cursor: pointer; transition: color .15s, border-color .15s, transform .12s; animation: dvIn .4s cubic-bezier(.2, .9, .3, 1.1) both; }.dv-examples button svg { width: 16px; height: 16px; flex: none; color: var(--accent-light, var(--accent)); }.dv-examples button:hover, .dv-examples button:focus-visible { color: var(--text-primary); border-color: var(--accent); }@@@ -102,6 +102,9 @@const tick = q('input[name="research"]'); if (tick) tick.checked = !last.research.id && !last.research.ref;}} catch { fitFrames(); fitExamples(); }++ // A link can open the composer on a kind (/create?kind=element: Bot's "Make one with AI"); it wins over the++ // last one kept in this browser, because the person just asked for it.++ try { const asked = new URLSearchParams(location.search).get('kind'); if (asked && q(`input[name="kind"][value="${CSS.escape(asked)}"]`)) setKind(asked); } catch { /* an odd query keeps the default */ }fitModels(); fitResearch();const mods = all('.dv-mod');const closeMods = (except) => mods.forEach((m) => { if (m !== except) m.open = false; });@@@ -47,7 +47,9 @@ function researchModule({ options = null, recent = [], signedIn = false } = {})const depths = Array.isArray(o.depths) ? o.depths : [];const priced = depths.some((d) => d.price_usd != null);if (!signedIn) {-- return `<details class="dv-mod" data-mod="research"><summary><span class="dv-mod-k">Research first</span><b>Deep dive</b></summary>++ // Like every module, the summary says its value, and the value is Off until a dive is chosen: "Research first++ // Deep dive" read as if one were already on (the 11-12 review).++ return `<details class="dv-mod" data-mod="research"><summary><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.5l1.9 5.1 5.1 1.9-5.1 1.9L12 17.5l-1.9-5.1L5 10.5l5.1-1.9z"/></svg><span class="dv-mod-k">Research first</span><b>Off</b></summary><div class="dv-mod-sheet" role="group" aria-label="Research first"><header><b>Research first</b><small>An expert design research run: we search everywhere, capture the best sites at phone and computer sizes, and tell you what works and why. It goes into the brief for every take.</small></header><p>${kit.button({ href: '/auth/login?next=%2Fcreate', label: 'Sign in to start a deep dive', kind: 'quiet', icon: 'spark' })}</p><p class="dv-fine">A deep dive is priced on OpenVibe Exchange and held on your Balance before it runs. <a href="https://openvibe.exchange/deep-dive">Read about deep dives</a>.</p></div></details>`;@@@ -56,7 +58,7 @@ function researchModule({ options = null, recent = [], signedIn = false } = {}): `<p class="dv-fine">The depths are not readable right now. <a href="https://openvibe.exchange/deep-dive">Start a deep dive on OpenVibe Exchange</a>, then attach it here.</p>`;const done = (recent || []).filter((r) => r && r.id);const recentRows = done.length ? `<label class="dv-start"><span>Or attach a dive you already ran</span><select name="research_id"><option value="">None</option>${done.map((r) => `<option value="${esc(r.id)}">${esc(String(r.question || 'Deep dive').slice(0, 80))}${r.status === 'running' ? ' (still running)' : ''} · ${esc(day(r.createdAt))}</option>`).join('')}</select></label>` : '';-- return `<details class="dv-mod" data-mod="research"><summary><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.5l1.9 5.1 5.1 1.9-5.1 1.9L12 17.5l-1.9-5.1L5 10.5l5.1-1.9z"/></svg><span class="dv-mod-k">Research first</span><b data-mod-v>${priced ? 'Off' : 'Deep dive'}</b></summary>++ return `<details class="dv-mod" data-mod="research"><summary><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.5l1.9 5.1 5.1 1.9-5.1 1.9L12 17.5l-1.9-5.1L5 10.5l5.1-1.9z"/></svg><span class="dv-mod-k">Research first</span><b data-mod-v>Off</b></summary><div class="dv-mod-sheet" role="group" aria-label="Research first"><header><b>Research first</b><small>A deep dive's synthesis (patterns, do and don't, reference tokens as inspiration, the rules it cites) becomes part of the generation brief. Each take says which research points it follows. A site's assets and code are never copied.</small></header><label class="dv-opt dv-opt--check"><input type="checkbox" name="research" value="new" data-research="on"><span><b>Start a deep dive for this design</b><small>The takes wait for its report, then are drawn from it.</small></span></label><div class="dv-deep" data-research-new hidden>@@@ -120,7 +120,6 @@ ${models.length ? `<p class="dv-group-h">Or choose the models yourself<small>Upconst kindName = Object.fromEntries(kinds.map((k) => [k.id, k]));const lookName = Object.fromEntries(looksAll.map((t) => [t.id, t.name]));const body = `<section class="dv-create"><h1>What will you design?</h1>--<p class="dv-create-gallery"><a href="/gallery">Elements and packs gallery →</a> Free controls people made with AI: install one on your channel or remix it.</p><form method="post" action="/create" class="dv-composer" data-dv-composer${options ? ' data-api="/api/create"' : ''}>${kindRow}<label class="dv-visually-hidden" for="dv-prompt">Describe your design</label>@@@ -128,6 +127,7 @@ ${kindRow}<div class="dv-tray">${frameMod}${lookMod}${whoMod}${startMod}${researchMod}<button type="submit" class="dv-go"><span data-go-label>Create three takes</span>${ARROW}</button></div><p class="dv-cost" data-dv-cost role="status" hidden></p></form><div class="dv-examples" aria-label="Try an example">${kinds.map((k) => (k.examples || []).map((x) => `<button type="button" data-example="${esc(x)}" data-kind="${esc(k.id)}">${k.glyph}<span>${esc(x)}</span></button>`).join('')).join('')}</div>++<a class="dv-create-door" href="/gallery"><span class="dv-create-door-ic"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="7" height="7" rx="2"/><rect x="13" y="4" width="7" height="7" rx="2"/><rect x="4" y="13" width="7" height="7" rx="2"/><rect x="13" y="13" width="7" height="7" rx="2"/></svg></span><span><b>Elements and packs</b><small>Free controls and stream buttons people made with AI: try one, install it, remix it.</small></span><span class="dv-create-door-go" aria-hidden="true">→</span></a>${recent.length ? `<section class="dv-recent"><h2>Your recent designs</h2><ul>${recent.map((d) => { const k = kindName[d.kind] || MAKE[d.kind] || MAKES[0]; const bits = [k.name, lookName[d.look] || lookName[d.themeId], d.model, d.status === 'running' ? 'Drawing now' : d.status === 'failed' ? 'Did not finish' : ago(d.createdAt)].filter(Boolean);return `<li><a class="dv-rc" href="${esc(d.url)}"><span class="dv-rc-k" aria-hidden="true">${d.thumbnail ? `<img src="${esc(d.thumbnail)}" alt="" loading="lazy" decoding="async">` : k.glyph}</span><span class="dv-rc-t"><b>${esc(d.prompt)}</b><small>${esc(bits.join(' · '))}</small></span></a></li>`; }).join('')}</ul></section>` : ''}</section>`;return layout({ title: 'Create a design', path: '/create', body, active: '/create', styles: ['css/deepdive.css'], scripts: ['js/design.js'] });
@@ -325,6 +325,14 @@ html.ar-fit .ar-shot--desktop .ar-shot-frame, html.ar-fit .ar-shot--desktop::bef
@@ -325,6 +325,14 @@ html.ar-fit .ar-shot--desktop .ar-shot-frame, html.ar-fit .ar-shot--desktop::bef
@keyframes dvStep { 50% { transform: translateX(calc(5px * var(--ov-move, 1))); opacity: .6; } }
@keyframes dvStep { 50% { transform: translateX(calc(5px * var(--ov-move, 1))); opacity: .6; } }
/* Ideas to start from: those of the kind chosen above, each with its glyph. */
/* Ideas to start from: those of the kind chosen above, each with its glyph. */
.dv-examples { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
.dv-examples { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
+/* The gallery's door under the examples (11-12 review: it had been a pink link run into the heading's text). */
+.dv-create-door { display: flex; align-items: center; gap: 12px; margin-top: 18px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 14px;
+ background: var(--bg-card, var(--bg-secondary)); color: var(--text-primary); text-decoration: none; transition: border-color .15s; }
+.dv-create-door:hover, .dv-create-door:focus-visible { border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }
+.dv-create-door-ic { display: grid; place-items: center; flex: none; width: 40px; height: 40px; border-radius: 12px; color: var(--accent-light, var(--accent)); background: color-mix(in srgb, var(--accent) 14%, transparent); }
+.dv-create-door b { display: block; font-size: .95rem; }
+.dv-create-door small { display: block; margin-top: 2px; color: var(--text-secondary); font-size: .84rem; line-height: 1.4; }
+.dv-create-door-go { margin-left: auto; color: var(--text-muted); font-size: 1.1rem; }
.dv-examples button { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; border: 1px solid var(--border); border-radius: 99px; padding: 0 14px 0 11px; background: var(--bg-card); color: var(--text-secondary); font: inherit; font-size: .86rem; cursor: pointer; transition: color .15s, border-color .15s, transform .12s; animation: dvIn .4s cubic-bezier(.2, .9, .3, 1.1) both; }
.dv-examples button { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; border: 1px solid var(--border); border-radius: 99px; padding: 0 14px 0 11px; background: var(--bg-card); color: var(--text-secondary); font: inherit; font-size: .86rem; cursor: pointer; transition: color .15s, border-color .15s, transform .12s; animation: dvIn .4s cubic-bezier(.2, .9, .3, 1.1) both; }
.dv-examples button svg { width: 16px; height: 16px; flex: none; color: var(--accent-light, var(--accent)); }
.dv-examples button svg { width: 16px; height: 16px; flex: none; color: var(--accent-light, var(--accent)); }
.dv-examples button:hover, .dv-examples button:focus-visible { color: var(--text-primary); border-color: var(--accent); }
.dv-examples button:hover, .dv-examples button:focus-visible { color: var(--text-primary); border-color: var(--accent); }
@@ -102,6 +102,9 @@
@@ -102,6 +102,9 @@
const tick = q('input[name="research"]'); if (tick) tick.checked = !last.research.id && !last.research.ref;
const tick = q('input[name="research"]'); if (tick) tick.checked = !last.research.id && !last.research.ref;
}
}
} catch { fitFrames(); fitExamples(); }
} catch { fitFrames(); fitExamples(); }
+ // A link can open the composer on a kind (/create?kind=element: Bot's "Make one with AI"); it wins over the
+ // last one kept in this browser, because the person just asked for it.
+ try { const asked = new URLSearchParams(location.search).get('kind'); if (asked && q(`input[name="kind"][value="${CSS.escape(asked)}"]`)) setKind(asked); } catch { /* an odd query keeps the default */ }
fitModels(); fitResearch();
fitModels(); fitResearch();
const mods = all('.dv-mod');
const mods = all('.dv-mod');
const closeMods = (except) => mods.forEach((m) => { if (m !== except) m.open = false; });
const closeMods = (except) => mods.forEach((m) => { if (m !== except) m.open = false; });
@@ -47,7 +47,9 @@ function researchModule({ options = null, recent = [], signedIn = false } = {})
@@ -47,7 +47,9 @@ function researchModule({ options = null, recent = [], signedIn = false } = {})
const depths = Array.isArray(o.depths) ? o.depths : [];
const depths = Array.isArray(o.depths) ? o.depths : [];
const priced = depths.some((d) => d.price_usd != null);
const priced = depths.some((d) => d.price_usd != null);
if (!signedIn) {
if (!signedIn) {
- return `<details class="dv-mod" data-mod="research"><summary><span class="dv-mod-k">Research first</span><b>Deep dive</b></summary>
+ // Like every module, the summary says its value, and the value is Off until a dive is chosen: "Research first
+ // Deep dive" read as if one were already on (the 11-12 review).
+ return `<details class="dv-mod" data-mod="research"><summary><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.5l1.9 5.1 5.1 1.9-5.1 1.9L12 17.5l-1.9-5.1L5 10.5l5.1-1.9z"/></svg><span class="dv-mod-k">Research first</span><b>Off</b></summary>
<div class="dv-mod-sheet" role="group" aria-label="Research first"><header><b>Research first</b><small>An expert design research run: we search everywhere, capture the best sites at phone and computer sizes, and tell you what works and why. It goes into the brief for every take.</small></header>
<div class="dv-mod-sheet" role="group" aria-label="Research first"><header><b>Research first</b><small>An expert design research run: we search everywhere, capture the best sites at phone and computer sizes, and tell you what works and why. It goes into the brief for every take.</small></header>
<p>${kit.button({ href: '/auth/login?next=%2Fcreate', label: 'Sign in to start a deep dive', kind: 'quiet', icon: 'spark' })}</p>
<p>${kit.button({ href: '/auth/login?next=%2Fcreate', label: 'Sign in to start a deep dive', kind: 'quiet', icon: 'spark' })}</p>
<p class="dv-fine">A deep dive is priced on OpenVibe Exchange and held on your Balance before it runs. <a href="https://openvibe.exchange/deep-dive">Read about deep dives</a>.</p></div></details>`;
<p class="dv-fine">A deep dive is priced on OpenVibe Exchange and held on your Balance before it runs. <a href="https://openvibe.exchange/deep-dive">Read about deep dives</a>.</p></div></details>`;
@@ -56,7 +58,7 @@ function researchModule({ options = null, recent = [], signedIn = false } = {})
@@ -56,7 +58,7 @@ function researchModule({ options = null, recent = [], signedIn = false } = {})
: `<p class="dv-fine">The depths are not readable right now. <a href="https://openvibe.exchange/deep-dive">Start a deep dive on OpenVibe Exchange</a>, then attach it here.</p>`;
: `<p class="dv-fine">The depths are not readable right now. <a href="https://openvibe.exchange/deep-dive">Start a deep dive on OpenVibe Exchange</a>, then attach it here.</p>`;
const done = (recent || []).filter((r) => r && r.id);
const done = (recent || []).filter((r) => r && r.id);
const recentRows = done.length ? `<label class="dv-start"><span>Or attach a dive you already ran</span><select name="research_id"><option value="">None</option>${done.map((r) => `<option value="${esc(r.id)}">${esc(String(r.question || 'Deep dive').slice(0, 80))}${r.status === 'running' ? ' (still running)' : ''} · ${esc(day(r.createdAt))}</option>`).join('')}</select></label>` : '';
const recentRows = done.length ? `<label class="dv-start"><span>Or attach a dive you already ran</span><select name="research_id"><option value="">None</option>${done.map((r) => `<option value="${esc(r.id)}">${esc(String(r.question || 'Deep dive').slice(0, 80))}${r.status === 'running' ? ' (still running)' : ''} · ${esc(day(r.createdAt))}</option>`).join('')}</select></label>` : '';
- return `<details class="dv-mod" data-mod="research"><summary><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.5l1.9 5.1 5.1 1.9-5.1 1.9L12 17.5l-1.9-5.1L5 10.5l5.1-1.9z"/></svg><span class="dv-mod-k">Research first</span><b data-mod-v>${priced ? 'Off' : 'Deep dive'}</b></summary>
+ return `<details class="dv-mod" data-mod="research"><summary><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.5l1.9 5.1 5.1 1.9-5.1 1.9L12 17.5l-1.9-5.1L5 10.5l5.1-1.9z"/></svg><span class="dv-mod-k">Research first</span><b data-mod-v>Off</b></summary>
<div class="dv-mod-sheet" role="group" aria-label="Research first"><header><b>Research first</b><small>A deep dive's synthesis (patterns, do and don't, reference tokens as inspiration, the rules it cites) becomes part of the generation brief. Each take says which research points it follows. A site's assets and code are never copied.</small></header>
<div class="dv-mod-sheet" role="group" aria-label="Research first"><header><b>Research first</b><small>A deep dive's synthesis (patterns, do and don't, reference tokens as inspiration, the rules it cites) becomes part of the generation brief. Each take says which research points it follows. A site's assets and code are never copied.</small></header>
<label class="dv-opt dv-opt--check"><input type="checkbox" name="research" value="new" data-research="on"><span><b>Start a deep dive for this design</b><small>The takes wait for its report, then are drawn from it.</small></span></label>
<label class="dv-opt dv-opt--check"><input type="checkbox" name="research" value="new" data-research="on"><span><b>Start a deep dive for this design</b><small>The takes wait for its report, then are drawn from it.</small></span></label>
<div class="dv-deep" data-research-new hidden>
<div class="dv-deep" data-research-new hidden>
@@ -120,7 +120,6 @@ ${models.length ? `<p class="dv-group-h">Or choose the models yourself<small>Up
@@ -120,7 +120,6 @@ ${models.length ? `<p class="dv-group-h">Or choose the models yourself<small>Up
const kindName = Object.fromEntries(kinds.map((k) => [k.id, k]));
const kindName = Object.fromEntries(kinds.map((k) => [k.id, k]));
const lookName = Object.fromEntries(looksAll.map((t) => [t.id, t.name]));
const lookName = Object.fromEntries(looksAll.map((t) => [t.id, t.name]));
const body = `<section class="dv-create"><h1>What will you design?</h1>
const body = `<section class="dv-create"><h1>What will you design?</h1>
-<p class="dv-create-gallery"><a href="/gallery">Elements and packs gallery →</a> Free controls people made with AI: install one on your channel or remix it.</p>
<form method="post" action="/create" class="dv-composer" data-dv-composer${options ? ' data-api="/api/create"' : ''}>
<form method="post" action="/create" class="dv-composer" data-dv-composer${options ? ' data-api="/api/create"' : ''}>
${kindRow}
${kindRow}
<label class="dv-visually-hidden" for="dv-prompt">Describe your design</label>
<label class="dv-visually-hidden" for="dv-prompt">Describe your design</label>
@@ -128,6 +127,7 @@ ${kindRow}
@@ -128,6 +127,7 @@ ${kindRow}
<div class="dv-tray">${frameMod}${lookMod}${whoMod}${startMod}${researchMod}<button type="submit" class="dv-go"><span data-go-label>Create three takes</span>${ARROW}</button></div>
<div class="dv-tray">${frameMod}${lookMod}${whoMod}${startMod}${researchMod}<button type="submit" class="dv-go"><span data-go-label>Create three takes</span>${ARROW}</button></div>
<p class="dv-cost" data-dv-cost role="status" hidden></p></form>
<p class="dv-cost" data-dv-cost role="status" hidden></p></form>
<div class="dv-examples" aria-label="Try an example">${kinds.map((k) => (k.examples || []).map((x) => `<button type="button" data-example="${esc(x)}" data-kind="${esc(k.id)}">${k.glyph}<span>${esc(x)}</span></button>`).join('')).join('')}</div>
<div class="dv-examples" aria-label="Try an example">${kinds.map((k) => (k.examples || []).map((x) => `<button type="button" data-example="${esc(x)}" data-kind="${esc(k.id)}">${k.glyph}<span>${esc(x)}</span></button>`).join('')).join('')}</div>
+<a class="dv-create-door" href="/gallery"><span class="dv-create-door-ic"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="7" height="7" rx="2"/><rect x="13" y="4" width="7" height="7" rx="2"/><rect x="4" y="13" width="7" height="7" rx="2"/><rect x="13" y="13" width="7" height="7" rx="2"/></svg></span><span><b>Elements and packs</b><small>Free controls and stream buttons people made with AI: try one, install it, remix it.</small></span><span class="dv-create-door-go" aria-hidden="true">→</span></a>
${recent.length ? `<section class="dv-recent"><h2>Your recent designs</h2><ul>${recent.map((d) => { const k = kindName[d.kind] || MAKE[d.kind] || MAKES[0]; const bits = [k.name, lookName[d.look] || lookName[d.themeId], d.model, d.status === 'running' ? 'Drawing now' : d.status === 'failed' ? 'Did not finish' : ago(d.createdAt)].filter(Boolean);
${recent.length ? `<section class="dv-recent"><h2>Your recent designs</h2><ul>${recent.map((d) => { const k = kindName[d.kind] || MAKE[d.kind] || MAKES[0]; const bits = [k.name, lookName[d.look] || lookName[d.themeId], d.model, d.status === 'running' ? 'Drawing now' : d.status === 'failed' ? 'Did not finish' : ago(d.createdAt)].filter(Boolean);
return `<li><a class="dv-rc" href="${esc(d.url)}"><span class="dv-rc-k" aria-hidden="true">${d.thumbnail ? `<img src="${esc(d.thumbnail)}" alt="" loading="lazy" decoding="async">` : k.glyph}</span><span class="dv-rc-t"><b>${esc(d.prompt)}</b><small>${esc(bits.join(' · '))}</small></span></a></li>`; }).join('')}</ul></section>` : ''}</section>`;
return `<li><a class="dv-rc" href="${esc(d.url)}"><span class="dv-rc-k" aria-hidden="true">${d.thumbnail ? `<img src="${esc(d.thumbnail)}" alt="" loading="lazy" decoding="async">` : k.glyph}</span><span class="dv-rc-t"><b>${esc(d.prompt)}</b><small>${esc(bits.join(' · '))}</small></span></a></li>`; }).join('')}</ul></section>` : ''}</section>`;
return layout({ title: 'Create a design', path: '/create', body, active: '/create', styles: ['css/deepdive.css'], scripts: ['js/design.js'] });
return layout({ title: 'Create a design', path: '/create', body, active: '/create', styles: ['css/deepdive.css'], scripts: ['js/design.js'] });