@bongos/core 1.20.58 → 1.20.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +198 -108
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +10 -0
- package/clients/bongos-client/index.cjs +10 -0
- package/clients/bongos-client/index.d.ts +15 -2
- package/clients/bongos-client/index.mjs +10 -0
- package/docs/adr/0358-a-hall-wears-a-look-and-its-night-takes-a-tint.md +52 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +317 -6
- package/docs/api-reference.md +8 -3
- package/docs/architecture.md +1 -0
- package/docs/copy-inventory.md +339 -330
- package/docs/copy-registry.json +524 -439
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +4 -1
- package/docs/page-readings.json +729 -678
- package/migrations/core_268_builders_main_discipline.sql +35 -0
- package/modules/copy-desk/artist-stats.js +56 -0
- package/modules/copy-desk/module.json +1 -1
- package/modules/copy-desk/page-status.js +39 -0
- package/modules/copy-desk/routes/copy-desk.js +7 -16
- package/modules/copy-desk/tests/copy_no_cms.mjs +10 -1
- package/modules/economy/credits-by-craft.js +208 -0
- package/modules/economy/reward.js +8 -0
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/commands.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/fleet.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/hall-render.js +2 -2
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/profile.js +1 -1
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +5 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +8 -0
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/tweak-editor.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/ideas/ideator-stats.js +149 -0
- package/modules/ideas/module.json +2 -1
- package/modules/ideas/routes/inbox.js +7 -0
- package/modules/lifecycle/activity.js +5 -2
- package/modules/lifecycle/db-analytics.js +4 -2
- package/modules/lifecycle/db-tasks.js +49 -1
- package/modules/lifecycle/db.js +3 -2
- package/modules/lifecycle/page-tweak-reads.js +23 -5
- package/modules/provisioning/look.js +169 -0
- package/modules/provisioning/migrations/provisioning_034_project_logo.sql +35 -0
- package/modules/provisioning/module.json +2 -1
- package/modules/provisioning/provisioning.js +4 -3
- package/modules/provisioning/routes/look.js +204 -0
- package/modules/provisioning/routes/provisioning.js +5 -59
- package/modules/provisioning/settings-push.js +76 -0
- package/modules/public-landing/public/assets/cosmos.css +1 -1
- package/modules/public-landing/public/projects.html +499 -28
- package/modules/public-landing/public/projects.probes.json +2 -2
- package/modules/public-landing/public/projects.states.json +6 -3
- package/modules/ui-design/kit/serve.js +50 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/fitness.js +4 -0
- package/scripts/gds/run-unit-tests.js +5 -0
- package/src/bongos/db.js +44 -6
- package/src/bongos/role-stats.js +155 -0
- package/src/bongos/routes/builders.js +15 -2
- package/src/bongos/routes/me.js +11 -3
- package/src/bongos/routes.js +4 -1
- package/src/bongos/serve-internal.js +6 -0
- package/src/branding.js +26 -1
- package/src/hall-look.js +99 -0
- package/src/module-api.js +1 -1
- package/src/night-tint.js +26 -0
- package/tests/copy_desk_page_reads.mjs +1 -1
- package/tests/currency_label.mjs +7 -4
- package/tests/fixtures/page-ledger.mjs +1 -1
- package/tests/hall_tokens.mjs +10 -1
- package/tests/hall_tweak_editor.mjs +1 -1
- package/tests/look_night_tint.mjs +169 -0
- package/tests/main_discipline.mjs +81 -0
- package/tests/nav_permission_atoms.mjs +1 -1
- package/tests/profile_role_stats.mjs +416 -0
- package/tests/projects_hub.mjs +5 -4
- package/tests/projects_hub_app_status.mjs +1 -1
- package/tests/projects_hub_app_step.mjs +8 -7
- package/tests/projects_hub_look.mjs +255 -0
- package/tests/projects_hub_module_picker.mjs +2 -2
- package/tests/provision_settings_apply.mjs +12 -4
- package/tests/provisioning_look.mjs +376 -0
- package/tests/provisioning_settings.mjs +10 -7
- package/tests/provisioning_settings_apply.mjs +4 -4
- package/tests/provisioning_settings_env.mjs +4 -0
- package/tests/role_stats_db.mjs +237 -0
- package/tests/tweak_replaces_artist_review.mjs +1 -1
- package/tests/wizard_draft_resume.mjs +2 -2
- package/tests/wizard_front_door.mjs +2 -2
- package/tests/wizard_intent_resume.mjs +16 -15
|
@@ -357,6 +357,45 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
357
357
|
.appOpt span span{display:block;font-size:14px;font-weight:400;color:var(--ink-soft);line-height:1.5;max-width:62ch;}
|
|
358
358
|
.appOpt.soon{cursor:default;}
|
|
359
359
|
.appOpt.soon b{color:var(--ink-soft);}
|
|
360
|
+
/* a look (task 1004421, ADR 0358): the picker beside two mini halls. The halls are
|
|
361
|
+
painted from the chosen look's own values through --mh-* properties lookKit sets
|
|
362
|
+
on each one; nothing here names a colour, and the page's own tokens are the
|
|
363
|
+
fallback before a choice is painted. */
|
|
364
|
+
.lookGrid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,300px);gap:28px;align-items:start;}
|
|
365
|
+
@media (max-width:760px){.lookGrid{grid-template-columns:minmax(0,1fr);}}
|
|
366
|
+
.lookSw{flex:none;display:inline-flex;gap:3px;margin-top:3px;}
|
|
367
|
+
.lookSw i{display:block;width:14px;height:14px;border-radius:50%;border:1px solid var(--rule);}
|
|
368
|
+
.lookSw.custom i{background:conic-gradient(var(--sea),var(--accent),var(--terracotta),var(--sea));}
|
|
369
|
+
.lookAccent{margin:14px 0 0;}
|
|
370
|
+
.lookAccent input[type=color]{display:block;width:56px;height:36px;padding:2px;border:1px solid var(--rule);border-radius:8px;background:var(--bg-card);cursor:pointer;}
|
|
371
|
+
.addrOpt.lookTint{color:var(--ink-soft);}
|
|
372
|
+
.lookTint b{font-weight:500;color:var(--ink);}
|
|
373
|
+
.lookLogo{margin:18px 0 0;}
|
|
374
|
+
.lookLogo input[type=file]{display:block;max-width:100%;min-height:32px;padding:4px 0;font:inherit;font-size:14px;color:var(--ink-soft);}
|
|
375
|
+
.lookLogo input[type=file]::file-selector-button{min-height:32px;margin-right:12px;padding:0 16px;font:inherit;font-size:14px;color:var(--ink);background:none;border:1px solid var(--rule);border-radius:999px;cursor:pointer;}
|
|
376
|
+
.lookLogoRow{display:flex;align-items:center;gap:12px;margin:10px 0 0;}
|
|
377
|
+
.lookLogoImg{width:44px;height:44px;border-radius:10px;object-fit:contain;background:var(--bg-deep);}
|
|
378
|
+
.lookLogoName{flex:1;min-width:0;font-size:14px;color:var(--ink-soft);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
379
|
+
.lookPrev{display:grid;gap:14px;}
|
|
380
|
+
.lookPrevHead{margin:0;font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);text-transform:uppercase;color:var(--ink-faint);}
|
|
381
|
+
.lookFig{margin:0;display:grid;gap:6px;}
|
|
382
|
+
.lookFig figcaption{font-size:12.5px;color:var(--ink-faint);}
|
|
383
|
+
.mh{border-radius:12px;overflow:hidden;border:1px solid var(--rule);background:var(--mh-bg,var(--bg));color:var(--mh-ink,var(--ink));font-size:12px;}
|
|
384
|
+
.mh .mhBar{display:flex;align-items:center;gap:8px;padding:9px 12px;border-bottom:1px solid var(--mh-rule,var(--rule));}
|
|
385
|
+
.mh .mhMark{display:inline-flex;width:14px;height:14px;border-radius:50%;border:1px solid var(--mh-ink,var(--ink));}
|
|
386
|
+
.mh .mhMark.logo{border:0;border-radius:4px;width:18px;height:18px;}
|
|
387
|
+
.mh .mhMark img{width:100%;height:100%;object-fit:contain;}
|
|
388
|
+
.mh .mhName{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
389
|
+
.mh .mhBody{display:grid;grid-template-columns:40px minmax(0,1fr);}
|
|
390
|
+
.mh .mhRail{display:grid;align-content:start;gap:9px;padding:12px 10px;border-right:1px solid var(--mh-rule,var(--rule));}
|
|
391
|
+
.mh .mhRail i{display:block;height:6px;border-radius:3px;background:var(--mh-rule,var(--rule));}
|
|
392
|
+
.mh .mhRail i.on{background:var(--mh-accent,var(--accent));}
|
|
393
|
+
.mh .mhMain{display:grid;gap:8px;padding:12px;}
|
|
394
|
+
.mh .mhH{font-size:17px;font-weight:300;letter-spacing:-.02em;}
|
|
395
|
+
.mh .mhCard{display:grid;gap:7px;padding:10px 12px;border-radius:9px;background:var(--mh-card,var(--bg-card));}
|
|
396
|
+
.mh .mhLn{display:block;height:6px;border-radius:3px;background:var(--mh-rule,var(--rule));}
|
|
397
|
+
.mh .mhLn.s{width:60%;}
|
|
398
|
+
.mh .mhBtn{justify-self:start;padding:4px 11px;border-radius:999px;background:var(--mh-accent,var(--accent));color:var(--mh-on,var(--accent-on));font-weight:600;}
|
|
360
399
|
/* the Render setup (task 1004352): shown only while Render is the choice. Every
|
|
361
400
|
list in it is a native radio group, and nothing in it is pre-picked for the owner. */
|
|
362
401
|
/* the app's setup status (task 1004353): the same block in the wizard's done panel and
|
|
@@ -1086,6 +1125,24 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1086
1125
|
</details>
|
|
1087
1126
|
</div>
|
|
1088
1127
|
|
|
1128
|
+
<!-- its look (task 1004421, ADR 0358; spec D8): the same picker the create wizard's
|
|
1129
|
+
Look step uses — colours for light and dark, the night tint, a logo. Unlike the
|
|
1130
|
+
planet and the card above, this one IS pushed to the project: the hall wears it,
|
|
1131
|
+
so a save restarts the project once, and the receipt says so. Fetched once per
|
|
1132
|
+
project, never on the once-a-minute tick, so a choice in progress stands. -->
|
|
1133
|
+
<div class="card mSec" id="mLook">
|
|
1134
|
+
<h2>Its look</h2>
|
|
1135
|
+
<div id="mLookBody">
|
|
1136
|
+
<div id="mLookPick"><p class="finePrint">Reading the project…</p></div>
|
|
1137
|
+
<div class="actions" id="mLookActions" hidden><button type="button" class="btn small" id="mLookSave">Save the look</button></div>
|
|
1138
|
+
<div class="projMsg" id="mLookMsg" role="status" aria-live="polite"></div>
|
|
1139
|
+
</div>
|
|
1140
|
+
<details class="finePrint" id="mLookWhat">
|
|
1141
|
+
<summary>What the choice changes</summary>
|
|
1142
|
+
<p>How your project’s own hall looks: its colours in light and dark mode, whether its dark mode takes a tint of your colour or stays pure black, and the logo at the top of the hall and in the browser tab. Saving restarts your project once to put it on. Until you choose, the hall keeps the colours it has today.</p>
|
|
1143
|
+
</details>
|
|
1144
|
+
</div>
|
|
1145
|
+
|
|
1089
1146
|
<!-- live since task 1003042: the teardown control, for a project in ANY
|
|
1090
1147
|
state that still holds something. The server used to refuse a
|
|
1091
1148
|
'requested' or an 'error' project outright — and force-teardown reused
|
|
@@ -1143,7 +1200,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1143
1200
|
<li data-step="7"><span class="snum">5</span><span class="slbl">Home</span></li>
|
|
1144
1201
|
<li data-step="8"><span class="snum">6</span><span class="slbl">Address</span></li>
|
|
1145
1202
|
<li data-step="9"><span class="snum">7</span><span class="slbl">App</span></li>
|
|
1146
|
-
<li data-step="10"><span class="snum">8</span><span class="slbl">
|
|
1203
|
+
<li data-step="10"><span class="snum">8</span><span class="slbl">Look</span></li>
|
|
1204
|
+
<li data-step="11"><span class="snum">9</span><span class="slbl">Create</span></li>
|
|
1147
1205
|
</ol>
|
|
1148
1206
|
|
|
1149
1207
|
<div class="createSplit">
|
|
@@ -1568,14 +1626,30 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1568
1626
|
</form>
|
|
1569
1627
|
</section>
|
|
1570
1628
|
|
|
1571
|
-
<!-- step 8 ·
|
|
1629
|
+
<!-- step 8 · a look (task 1004421, ADR 0358): colours for light and dark, the
|
|
1630
|
+
night tint and a logo, so builders in several projects always know which hall
|
|
1631
|
+
they are in. lookKit.buildLookPicker fills it — the same picker the manage
|
|
1632
|
+
page's "Its look" uses — and the choice is sent right after Create (sendLook). -->
|
|
1633
|
+
<section class="panel" id="panel-look" aria-labelledby="look-h">
|
|
1634
|
+
<h1 id="look-h">Give it a look</h1>
|
|
1635
|
+
<p class="lede">Colours for light and dark mode, and a logo, so builders who work in several projects always know which hall they’re in.</p>
|
|
1636
|
+
<form data-next="11">
|
|
1637
|
+
<div class="lookPick" id="wizLook"></div>
|
|
1638
|
+
<div class="nav">
|
|
1639
|
+
<button type="button" class="btn ghost" data-back="9">← Back</button>
|
|
1640
|
+
<button type="submit" class="btn">Next →</button>
|
|
1641
|
+
</div>
|
|
1642
|
+
</form>
|
|
1643
|
+
</section>
|
|
1644
|
+
|
|
1645
|
+
<!-- step 9 · review + create -->
|
|
1572
1646
|
<section class="panel" id="panel-review" aria-labelledby="rev-h">
|
|
1573
1647
|
<h1 id="rev-h">Ready to roll</h1>
|
|
1574
1648
|
<p class="lede">One tap files the request and the platform starts standing your project up — server, database, address. Here's the beat you're about to drop:</p>
|
|
1575
1649
|
<ul class="review" id="reviewList"></ul>
|
|
1576
1650
|
<div class="alert" id="createAlert" role="alert"></div>
|
|
1577
1651
|
<div class="nav">
|
|
1578
|
-
<button type="button" class="btn ghost" data-back="
|
|
1652
|
+
<button type="button" class="btn ghost" data-back="10">← Back</button>
|
|
1579
1653
|
<button type="button" class="btn big" id="doCreate">Create my project</button>
|
|
1580
1654
|
</div>
|
|
1581
1655
|
<p class="finePrint">This files the request; the control plane does the real standup and nothing touches servers from this page. You'll get the few steps only you can do right after.</p>
|
|
@@ -1589,6 +1663,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
1589
1663
|
</div>
|
|
1590
1664
|
<p class="lede" id="doneLede"></p>
|
|
1591
1665
|
<p class="hint" id="doneRender" role="status" hidden></p>
|
|
1666
|
+
<p class="hint" id="doneLook" role="status" hidden></p>
|
|
1592
1667
|
<div id="doneApp" hidden></div>
|
|
1593
1668
|
<!-- the STABLE live region: the strip inside is rebuilt every 5s tick,
|
|
1594
1669
|
and a live region that is itself replaced never announces -->
|
|
@@ -3899,6 +3974,312 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3899
3974
|
} else { stopSky(); hideHover(); }
|
|
3900
3975
|
}
|
|
3901
3976
|
|
|
3977
|
+
/* ══ A LOOK (task 1004421, BV2.PS06; ADR 0358) — colours for light and dark, the
|
|
3978
|
+
night tint, a logo. One picker, two homes: the create wizard's Look step and
|
|
3979
|
+
the manage page's "Its look" (spec D8). It only ever describes a choice; the
|
|
3980
|
+
platform stores it (PATCH …/look, PUT/DELETE …/logo) and the hall restarts once
|
|
3981
|
+
to wear it.
|
|
3982
|
+
LOOKS is the style library's thirteen per look (modules/ui-design/styles/
|
|
3983
|
+
<name>/pack.json), copied for the previews — tests/projects_hub_look.mjs pins every
|
|
3984
|
+
value to its pack. NIGHT holds src/night-tint.js's shares, pinned the same way.
|
|
3985
|
+
lookAdjustAccent is modules/provisioning/look.js's own text, pinned character for
|
|
3986
|
+
character by tests/provisioning_look.mjs: the preview shows exactly the colour
|
|
3987
|
+
the server will keep. ══ */
|
|
3988
|
+
var lookKit = (function () {
|
|
3989
|
+
var LOOKS = {
|
|
3990
|
+
'chrome-world': { title: 'The chrome world', summary: 'Salmon on pale grey. The platform’s own look.',
|
|
3991
|
+
colors: { bg: '#edecee', bgCard: '#fbfafc', bgDeep: '#e2e1e4', bgFrame: '#f6f5f7', ink: '#17181a', inkSoft: '#55565a', inkFaint: '#65656a', rule: '#c6c6c8', ruleSoft: '#dcdbdd', accent: '#fb9569', accentDeep: '#a3481a', sea: '#5b6fd0', terracotta: '#c9512c' } },
|
|
3992
|
+
expedition: { title: 'The expedition', summary: 'Lamp yellow on warm paper, like a field journal.',
|
|
3993
|
+
colors: { bg: '#f4f3ef', bgCard: '#ffffff', bgDeep: '#e9e7e0', bgFrame: '#eceae3', ink: '#141412', inkSoft: '#55544f', inkFaint: '#66645b', rule: '#d9d7cf', ruleSoft: '#e8e6df', accent: '#ffd214', accentDeep: '#7f6209', sea: '#427d97', terracotta: '#b4592f' } },
|
|
3994
|
+
grove: { title: 'The grove', summary: 'Tan and sea green on pale sage.',
|
|
3995
|
+
colors: { bg: '#e1eadb', bgCard: '#f6f9f3', bgDeep: '#cad8c2', bgFrame: '#eaf0e5', ink: '#0f3226', inkSoft: '#2f5240', inkFaint: '#3f5a48', rule: '#9db69a', ruleSoft: '#d0ddcb', accent: '#e3c39c', accentDeep: '#6d4b34', sea: '#3d8562', terracotta: '#b07a4e' } },
|
|
3996
|
+
blueprint: { title: 'The blueprint', summary: 'Cyanotype blue on cool grey-blue paper.',
|
|
3997
|
+
colors: { bg: '#e6ebf0', bgCard: '#f7fafc', bgDeep: '#d3dae2', bgFrame: '#edf1f5', ink: '#10161d', inkSoft: '#464f5a', inkFaint: '#566069', rule: '#aeb9c4', ruleSoft: '#cfd7de', accent: '#4f9fd6', accentDeep: '#17527e', sea: '#3f7f6a', terracotta: '#b04a2c' } }
|
|
3998
|
+
};
|
|
3999
|
+
var ORDER = ['chrome-world', 'expedition', 'grove', 'blueprint', 'custom'];
|
|
4000
|
+
/* the dark tier the hall's sheet paints, and the tint's shares of the accent */
|
|
4001
|
+
var NIGHT = { bg: '#000000', card: '#101012', bgShare: 6, cardShare: 5 };
|
|
4002
|
+
var LOGO_TYPES = ['image/png', 'image/jpeg', 'image/webp'];
|
|
4003
|
+
var LOGO_MAX = 256 * 1024;
|
|
4004
|
+
|
|
4005
|
+
function lookAdjustAccent(hex) {
|
|
4006
|
+
var m = /^#?([0-9a-f]{6})$/i.exec(String(hex || '').trim());
|
|
4007
|
+
if (!m) return null;
|
|
4008
|
+
var n = parseInt(m[1], 16);
|
|
4009
|
+
var rgb = [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
4010
|
+
function mix(a, p, b) { return [0, 1, 2].map(function (i) { return a[i] * p + b[i] * (1 - p); }); }
|
|
4011
|
+
function lum(c) {
|
|
4012
|
+
var v = c.map(function (x) { x /= 255; return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); });
|
|
4013
|
+
return 0.2126 * v[0] + 0.7152 * v[1] + 0.0722 * v[2];
|
|
4014
|
+
}
|
|
4015
|
+
function ratio(a, b) { var x = lum(a), y = lum(b); return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05); }
|
|
4016
|
+
function reads(c) {
|
|
4017
|
+
var ink = mix(c, 0.45, [36, 14, 0]);
|
|
4018
|
+
var darks = [[0, 0, 0], [16, 16, 18], mix(c, 0.06, [0, 0, 0]), mix(c, 0.05, [16, 16, 18])];
|
|
4019
|
+
var info = mix([91, 111, 208], 0.8, [255, 255, 255]);
|
|
4020
|
+
return ratio([42, 19, 5], c) >= 4.6 && ratio(ink, [237, 236, 238]) >= 4.6 && ratio(ink, [251, 250, 252]) >= 4.6 &&
|
|
4021
|
+
darks.every(function (d) { return ratio(c, d) >= 4.6; }) && ratio(info, mix(info, 0.14, darks[3])) >= 4.5;
|
|
4022
|
+
}
|
|
4023
|
+
function toHex(c) { return '#' + c.map(function (x) { var s = Math.round(x).toString(16); return s.length < 2 ? '0' + s : s; }).join(''); }
|
|
4024
|
+
var r = rgb[0] / 255, g = rgb[1] / 255, b = rgb[2] / 255;
|
|
4025
|
+
var max = Math.max(r, g, b), min = Math.min(r, g, b), l = (max + min) / 2, h = 0, s = 0, d = max - min;
|
|
4026
|
+
if (d) {
|
|
4027
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
4028
|
+
h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
4029
|
+
h /= 6;
|
|
4030
|
+
}
|
|
4031
|
+
function hsl(L) {
|
|
4032
|
+
function f(p, q, t) { t = (t + 1) % 1; return t < 1 / 6 ? p + (q - p) * 6 * t : t < 1 / 2 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p; }
|
|
4033
|
+
if (!s) return [L * 255, L * 255, L * 255];
|
|
4034
|
+
var q = L < 0.5 ? L * (1 + s) : L + s - L * s, p = 2 * L - q;
|
|
4035
|
+
return [f(p, q, h + 1 / 3) * 255, f(p, q, h) * 255, f(p, q, h - 1 / 3) * 255];
|
|
4036
|
+
}
|
|
4037
|
+
if (reads(rgb)) return { hex: toHex(rgb), adjusted: false };
|
|
4038
|
+
for (var step = 1; step <= 200; step++) {
|
|
4039
|
+
var tries = [l + step / 200, l - step / 200];
|
|
4040
|
+
for (var t = 0; t < 2; t++) {
|
|
4041
|
+
if (tries[t] < 0 || tries[t] > 1) continue;
|
|
4042
|
+
var c = hsl(tries[t]).map(Math.round);
|
|
4043
|
+
if (reads(c)) return { hex: toHex(c), adjusted: true };
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
return null;
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
/* srgb mix of two #rrggbb, p% of the first — what color-mix(in srgb) paints */
|
|
4050
|
+
function mixHex(a, p, b) {
|
|
4051
|
+
var x = parseInt(a.slice(1), 16), y = parseInt(b.slice(1), 16), out = '#';
|
|
4052
|
+
[16, 8, 0].forEach(function (sh) {
|
|
4053
|
+
var v = Math.round(((x >> sh) & 255) * p / 100 + ((y >> sh) & 255) * (100 - p) / 100).toString(16);
|
|
4054
|
+
out += v.length < 2 ? '0' + v : v;
|
|
4055
|
+
});
|
|
4056
|
+
return out;
|
|
4057
|
+
}
|
|
4058
|
+
/* the thirteen a choice paints: a library look's own, or the chrome world with the
|
|
4059
|
+
custom accent (already nudged) — what src/hall-look.js resolves on the hall */
|
|
4060
|
+
function colorsFor(choice) {
|
|
4061
|
+
var base = LOOKS[choice.look] || LOOKS['chrome-world'];
|
|
4062
|
+
var out = {};
|
|
4063
|
+
Object.keys(base.colors).forEach(function (k) { out[k] = base.colors[k]; });
|
|
4064
|
+
if (choice.look === 'custom') {
|
|
4065
|
+
var a = lookAdjustAccent(choice.accent);
|
|
4066
|
+
if (a) out.accent = a.hex;
|
|
4067
|
+
}
|
|
4068
|
+
return out;
|
|
4069
|
+
}
|
|
4070
|
+
function el(tag, cls, text) {
|
|
4071
|
+
var e = document.createElement(tag);
|
|
4072
|
+
if (cls) e.className = cls;
|
|
4073
|
+
if (text != null) e.textContent = text;
|
|
4074
|
+
return e;
|
|
4075
|
+
}
|
|
4076
|
+
/* one mini hall: a rail, a heading, a card and the accent's door */
|
|
4077
|
+
function miniHall(mode, name) {
|
|
4078
|
+
var box = el('div', 'mh ' + mode);
|
|
4079
|
+
box.setAttribute('aria-hidden', 'true');
|
|
4080
|
+
var bar = el('div', 'mhBar');
|
|
4081
|
+
var mark = el('span', 'mhMark');
|
|
4082
|
+
bar.appendChild(mark);
|
|
4083
|
+
bar.appendChild(el('span', 'mhName', name));
|
|
4084
|
+
box.appendChild(bar);
|
|
4085
|
+
var body = el('div', 'mhBody');
|
|
4086
|
+
var rail = el('div', 'mhRail');
|
|
4087
|
+
for (var i = 0; i < 3; i++) rail.appendChild(el('i', i === 0 ? 'on' : ''));
|
|
4088
|
+
body.appendChild(rail);
|
|
4089
|
+
var main = el('div', 'mhMain');
|
|
4090
|
+
main.appendChild(el('span', 'mhH', 'genesis'));
|
|
4091
|
+
var card = el('div', 'mhCard');
|
|
4092
|
+
card.appendChild(el('i', 'mhLn'));
|
|
4093
|
+
card.appendChild(el('i', 'mhLn s'));
|
|
4094
|
+
card.appendChild(el('span', 'mhBtn', 'use this plan'));
|
|
4095
|
+
main.appendChild(card);
|
|
4096
|
+
body.appendChild(main);
|
|
4097
|
+
box.appendChild(body);
|
|
4098
|
+
return { box: box, mark: mark };
|
|
4099
|
+
}
|
|
4100
|
+
function paintHall(h, c, mode, tint, logoUrl) {
|
|
4101
|
+
var dark = mode === 'dark';
|
|
4102
|
+
var p = {
|
|
4103
|
+
'--mh-bg': dark ? (tint ? mixHex(c.accent, NIGHT.bgShare, NIGHT.bg) : NIGHT.bg) : c.bg,
|
|
4104
|
+
'--mh-card': dark ? (tint ? mixHex(c.accent, NIGHT.cardShare, NIGHT.card) : NIGHT.card) : c.bgCard,
|
|
4105
|
+
'--mh-rule': dark ? 'rgba(255, 255, 255, .16)' : c.ruleSoft,
|
|
4106
|
+
'--mh-ink': dark ? '#ffffff' : c.ink,
|
|
4107
|
+
'--mh-soft': dark ? '#c0c0c0' : c.inkSoft,
|
|
4108
|
+
'--mh-accent': c.accent,
|
|
4109
|
+
/* the hall's own --accent-on: the dark ink every accent fill carries */
|
|
4110
|
+
'--mh-on': '#2a1305'
|
|
4111
|
+
};
|
|
4112
|
+
Object.keys(p).forEach(function (k) { h.box.style.setProperty(k, p[k]); });
|
|
4113
|
+
while (h.mark.firstChild) h.mark.removeChild(h.mark.firstChild);
|
|
4114
|
+
if (logoUrl) { var img = el('img'); img.src = logoUrl; img.alt = ''; h.mark.appendChild(img); h.mark.classList.add('logo'); }
|
|
4115
|
+
else h.mark.classList.remove('logo');
|
|
4116
|
+
}
|
|
4117
|
+
|
|
4118
|
+
/* The picker. `model` = { look, nightTint, accent, logo: { type, b64, name } | null,
|
|
4119
|
+
logoUrl } and is edited in place; onChange runs after every edit. `idp` keeps two
|
|
4120
|
+
pickers' ids apart. Returns { repaint } for a model changed from outside. */
|
|
4121
|
+
function buildLookPicker(host, model, onChange, idp, projectName) {
|
|
4122
|
+
while (host.firstChild) host.removeChild(host.firstChild);
|
|
4123
|
+
var grid = el('div', 'lookGrid');
|
|
4124
|
+
var left = el('div', 'lookLeft');
|
|
4125
|
+
var set = el('fieldset', 'appOpts lookOpts');
|
|
4126
|
+
set.appendChild(el('legend', null, 'Pick a look'));
|
|
4127
|
+
var radios = {};
|
|
4128
|
+
ORDER.forEach(function (key) {
|
|
4129
|
+
var lab = el('label', 'appOpt lookOpt');
|
|
4130
|
+
var r = el('input');
|
|
4131
|
+
r.type = 'radio'; r.name = idp + 'Look'; r.value = key; r.id = idp + 'Look-' + key;
|
|
4132
|
+
radios[key] = r;
|
|
4133
|
+
r.addEventListener('change', function () { if (r.checked) { model.look = key; changed(); } });
|
|
4134
|
+
lab.appendChild(r);
|
|
4135
|
+
var sw = el('span', 'lookSw');
|
|
4136
|
+
sw.setAttribute('aria-hidden', 'true');
|
|
4137
|
+
var c = key === 'custom' ? null : LOOKS[key].colors;
|
|
4138
|
+
(c ? [c.bg, c.accent, c.sea] : ['']).forEach(function (v) {
|
|
4139
|
+
var dot = el('i'); if (v) dot.style.background = v; sw.appendChild(dot);
|
|
4140
|
+
});
|
|
4141
|
+
if (key === 'custom') sw.classList.add('custom');
|
|
4142
|
+
lab.appendChild(sw);
|
|
4143
|
+
var txt = el('span');
|
|
4144
|
+
txt.appendChild(el('b', null, key === 'custom' ? 'Make your own' : LOOKS[key].title));
|
|
4145
|
+
txt.appendChild(el('span', null, key === 'custom' ? 'Pick one accent colour. We use it in light and dark mode.' : LOOKS[key].summary));
|
|
4146
|
+
lab.appendChild(txt);
|
|
4147
|
+
set.appendChild(lab);
|
|
4148
|
+
});
|
|
4149
|
+
left.appendChild(set);
|
|
4150
|
+
|
|
4151
|
+
var acc = el('div', 'lookAccent');
|
|
4152
|
+
var accLab = el('label', 'wizField');
|
|
4153
|
+
accLab.appendChild(el('span', null, 'Your accent colour'));
|
|
4154
|
+
var accIn = el('input');
|
|
4155
|
+
accIn.type = 'color'; accIn.id = idp + 'Accent';
|
|
4156
|
+
accIn.addEventListener('input', function () { model.accent = accIn.value; changed(); });
|
|
4157
|
+
accLab.appendChild(accIn);
|
|
4158
|
+
acc.appendChild(accLab);
|
|
4159
|
+
var accNote = el('div', 'hint');
|
|
4160
|
+
accNote.id = idp + 'AccentNote'; accNote.setAttribute('role', 'status');
|
|
4161
|
+
acc.appendChild(accNote);
|
|
4162
|
+
left.appendChild(acc);
|
|
4163
|
+
|
|
4164
|
+
var tintLab = el('label', 'addrOpt lookTint');
|
|
4165
|
+
var tintIn = el('input');
|
|
4166
|
+
tintIn.type = 'checkbox'; tintIn.id = idp + 'Tint';
|
|
4167
|
+
tintIn.addEventListener('change', function () { model.nightTint = !!tintIn.checked; changed(); });
|
|
4168
|
+
tintLab.appendChild(tintIn);
|
|
4169
|
+
var tintTxt = el('span');
|
|
4170
|
+
tintTxt.appendChild(el('b', null, 'Tint dark mode'));
|
|
4171
|
+
tintTxt.appendChild(el('span', null, ' Dark backgrounds take a hint of your colour, so halls look different at night too. Untick for pure black.'));
|
|
4172
|
+
tintLab.appendChild(tintTxt);
|
|
4173
|
+
left.appendChild(tintLab);
|
|
4174
|
+
|
|
4175
|
+
var logo = el('div', 'lookLogo');
|
|
4176
|
+
var logoLab = el('label', 'wizField');
|
|
4177
|
+
logoLab.appendChild(el('span', null, 'Logo (optional) — PNG, JPEG or WebP, up to 256 KB'));
|
|
4178
|
+
var file = el('input');
|
|
4179
|
+
file.type = 'file'; file.accept = LOGO_TYPES.join(','); file.id = idp + 'Logo';
|
|
4180
|
+
logoLab.appendChild(file);
|
|
4181
|
+
logo.appendChild(logoLab);
|
|
4182
|
+
var logoRow = el('div', 'lookLogoRow');
|
|
4183
|
+
var logoImg = el('img', 'lookLogoImg'); logoImg.alt = 'Your logo';
|
|
4184
|
+
var logoName = el('span', 'lookLogoName');
|
|
4185
|
+
var logoDrop = el('button', 'btn ghost small', 'Remove logo');
|
|
4186
|
+
logoDrop.type = 'button'; logoDrop.id = idp + 'LogoDrop';
|
|
4187
|
+
logoRow.appendChild(logoImg); logoRow.appendChild(logoName); logoRow.appendChild(logoDrop);
|
|
4188
|
+
logo.appendChild(logoRow);
|
|
4189
|
+
var logoNote = el('div', 'hint');
|
|
4190
|
+
logoNote.setAttribute('role', 'status');
|
|
4191
|
+
logo.appendChild(logoNote);
|
|
4192
|
+
logo.appendChild(el('p', 'finePrint', 'It shows at the top of your hall and in the browser tab.'));
|
|
4193
|
+
left.appendChild(logo);
|
|
4194
|
+
file.addEventListener('change', function () {
|
|
4195
|
+
var f = file.files && file.files[0];
|
|
4196
|
+
logoNote.className = 'hint'; logoNote.textContent = '';
|
|
4197
|
+
if (!f) return;
|
|
4198
|
+
if (LOGO_TYPES.indexOf(f.type) === -1) { logoNote.className = 'hint err'; logoNote.textContent = 'A logo has to be a PNG, JPEG or WebP image.'; file.value = ''; return; }
|
|
4199
|
+
if (f.size > LOGO_MAX) { logoNote.className = 'hint err'; logoNote.textContent = 'That image is ' + Math.round(f.size / 1024) + ' KB — a logo can be at most 256 KB.'; file.value = ''; return; }
|
|
4200
|
+
var rd = new FileReader();
|
|
4201
|
+
rd.onload = function () {
|
|
4202
|
+
var url = String(rd.result || '');
|
|
4203
|
+
model.logo = { type: f.type, b64: url.slice(url.indexOf(',') + 1), name: f.name };
|
|
4204
|
+
model.logoUrl = '';
|
|
4205
|
+
changed();
|
|
4206
|
+
};
|
|
4207
|
+
rd.readAsDataURL(f);
|
|
4208
|
+
});
|
|
4209
|
+
logoDrop.addEventListener('click', function () { model.logo = null; model.logoUrl = ''; model.logoDropped = true; file.value = ''; changed(); });
|
|
4210
|
+
|
|
4211
|
+
left.appendChild(el('p', 'finePrint', 'Whatever you pick, we keep text readable in both modes.'));
|
|
4212
|
+
grid.appendChild(left);
|
|
4213
|
+
|
|
4214
|
+
var prev = el('div', 'lookPrev');
|
|
4215
|
+
var prevHead = el('p', 'lookPrevHead');
|
|
4216
|
+
prev.appendChild(prevHead);
|
|
4217
|
+
var halls = {};
|
|
4218
|
+
['light', 'dark'].forEach(function (mode) {
|
|
4219
|
+
var fig = el('figure', 'lookFig');
|
|
4220
|
+
halls[mode] = miniHall(mode, projectName ? projectName() : 'Your project');
|
|
4221
|
+
fig.appendChild(halls[mode].box);
|
|
4222
|
+
fig.appendChild(el('figcaption', null, mode === 'light' ? 'Light mode' : 'Dark mode'));
|
|
4223
|
+
prev.appendChild(fig);
|
|
4224
|
+
});
|
|
4225
|
+
grid.appendChild(prev);
|
|
4226
|
+
host.appendChild(grid);
|
|
4227
|
+
|
|
4228
|
+
function repaint() {
|
|
4229
|
+
Object.keys(radios).forEach(function (k) { radios[k].checked = model.look === k; });
|
|
4230
|
+
var custom = model.look === 'custom';
|
|
4231
|
+
acc.hidden = !custom;
|
|
4232
|
+
var a = lookAdjustAccent(model.accent) || { hex: '#b48cf2', adjusted: false };
|
|
4233
|
+
if (accIn.value !== a.hex && !a.adjusted) accIn.value = a.hex;
|
|
4234
|
+
accNote.className = 'hint';
|
|
4235
|
+
accNote.textContent = custom && a.adjusted ? 'Adjusted to ' + a.hex + ' so text stays readable in both modes — the preview shows it.' : ' ';
|
|
4236
|
+
tintIn.checked = model.nightTint !== false;
|
|
4237
|
+
var src = model.logo ? 'data:' + model.logo.type + ';base64,' + model.logo.b64 : (model.logoUrl || '');
|
|
4238
|
+
logoRow.hidden = !src;
|
|
4239
|
+
if (src) { logoImg.src = src; logoName.textContent = model.logo ? model.logo.name : 'Your current logo'; }
|
|
4240
|
+
var c = colorsFor(model);
|
|
4241
|
+
prevHead.textContent = 'Preview — ' + (custom ? 'your own look' : LOOKS[model.look] ? LOOKS[model.look].title.toLowerCase() : 'its colours today');
|
|
4242
|
+
/* only a chosen look tints the night (ADR 0358 D2): a project on its own colours stays black */
|
|
4243
|
+
var tints = model.nightTint !== false && (custom || !!LOOKS[model.look]);
|
|
4244
|
+
['light', 'dark'].forEach(function (mode) { paintHall(halls[mode], c, mode, tints, src); });
|
|
4245
|
+
if (projectName) halls.light.box.querySelector('.mhName').textContent = halls.dark.box.querySelector('.mhName').textContent = projectName() || 'Your project';
|
|
4246
|
+
}
|
|
4247
|
+
function changed() { repaint(); if (onChange) onChange(model); }
|
|
4248
|
+
repaint();
|
|
4249
|
+
return { repaint: repaint };
|
|
4250
|
+
}
|
|
4251
|
+
|
|
4252
|
+
/* Send a choice to the platform: the look first, then the logo (PUT or DELETE).
|
|
4253
|
+
Resolves to { message, adjusted } or rejects with the server's own sentence. */
|
|
4254
|
+
function sendLook(instanceId, model) {
|
|
4255
|
+
var base = API + '/provisioning/instances/' + encodeURIComponent(instanceId);
|
|
4256
|
+
function req(method, path, body) {
|
|
4257
|
+
return fetch(base + path, {
|
|
4258
|
+
method: method, credentials: 'same-origin',
|
|
4259
|
+
headers: body ? { 'Content-Type': 'application/json' } : {},
|
|
4260
|
+
body: body ? JSON.stringify(body) : undefined
|
|
4261
|
+
}).then(function (res) {
|
|
4262
|
+
return res.json().catch(function () { return {}; }).then(function (data) {
|
|
4263
|
+
if (!res.ok) throw new Error((data.error && data.error.message) || 'The look could not be saved.');
|
|
4264
|
+
return data;
|
|
4265
|
+
});
|
|
4266
|
+
});
|
|
4267
|
+
}
|
|
4268
|
+
var body = { look: model.look, night_tint: model.nightTint === false ? 'off' : 'on' };
|
|
4269
|
+
if (model.look === 'custom') body.accent = model.accent;
|
|
4270
|
+
return req('PATCH', '/look', body).then(function (first) {
|
|
4271
|
+
var then = model.logo ? req('PUT', '/logo', { image_b64: model.logo.b64, content_type: model.logo.type })
|
|
4272
|
+
: model.logoDropped ? req('DELETE', '/logo') : Promise.resolve(null);
|
|
4273
|
+
return then.then(function (last) {
|
|
4274
|
+
var out = last || first;
|
|
4275
|
+
return { message: out.push && out.push.message, adjusted: first.adjusted, look: out.look };
|
|
4276
|
+
});
|
|
4277
|
+
});
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
return { LOOKS: LOOKS, NIGHT: NIGHT, lookAdjustAccent: lookAdjustAccent, buildLookPicker: buildLookPicker, sendLook: sendLook };
|
|
4281
|
+
})();
|
|
4282
|
+
|
|
3902
4283
|
/* ══ THE CREATE WIZARD (task 1002763) — absorbed whole from /create, which
|
|
3903
4284
|
now 302s here. An inner scope so its ~40 helpers stay out of the hub's
|
|
3904
4285
|
namespace; it shares the hub's $, esc, API and `me`, so there is one
|
|
@@ -3916,11 +4297,12 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3916
4297
|
reinterpreted — a stale step 4 meant Address under v1 and Home under v3,
|
|
3917
4298
|
and replaying it would land on the wrong panel with the rail already
|
|
3918
4299
|
marking the skipped ones done. Bump this key on EVERY renumbering
|
|
3919
|
-
(-v6: the front door's two screens took steps 1 and 2, task 1004417
|
|
4300
|
+
(-v6: the front door's two screens took steps 1 and 2, task 1004417;
|
|
4301
|
+
-v7: the Look step took 10, so review is 11 and done 12, task 1004421). */
|
|
3920
4302
|
/* `termsAck` is the front door's one tick (task 1004417). It is the founder's
|
|
3921
4303
|
own answer, so it lives in the draft like every other one and survives the
|
|
3922
4304
|
GitHub round-trip; the server, not this page, names the terms version. */
|
|
3923
|
-
var DRAFT_KEY = 'cb-create-draft-
|
|
4305
|
+
var DRAFT_KEY = 'cb-create-draft-v7';
|
|
3924
4306
|
var CREATED_KEY = 'cb-created-instance';
|
|
3925
4307
|
/* `description` + `teamShape` are the optional About step (task 1002334) —
|
|
3926
4308
|
both legitimately stay empty, and empty is what the create request omits. */
|
|
@@ -3929,7 +4311,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3929
4311
|
request omits the field), [] = the owner switched them all off, [...] =
|
|
3930
4312
|
what they picked. Never seed it with the preselect — that would store an
|
|
3931
4313
|
answer nobody gave. */
|
|
3932
|
-
var state = { step: 0, name: '', projType: null, description: '', teamShape: null, modules: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false, appPlatform: 'later', termsAck: false };
|
|
4314
|
+
var state = { step: 0, name: '', projType: null, description: '', teamShape: null, modules: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false, appPlatform: 'later', look: null, termsAck: false };
|
|
3933
4315
|
try { Object.assign(state, JSON.parse(sessionStorage.getItem(DRAFT_KEY) || '{}')); } catch (e) {}
|
|
3934
4316
|
var authKnown = false; /* flips true when the boot's /me settles, either way */
|
|
3935
4317
|
|
|
@@ -3943,7 +4325,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
3943
4325
|
state.name = handedName.trim().slice(0, 80);
|
|
3944
4326
|
state.slug = deriveSlug(state.name); state.slugTouched = false;
|
|
3945
4327
|
state.repo = deriveRepo(state.name); state.repoTouched = false;
|
|
3946
|
-
state.step = state.step >= 1 && state.step <=
|
|
4328
|
+
state.step = state.step >= 1 && state.step <= 11 ? state.step : 1;
|
|
3947
4329
|
try { sessionStorage.setItem(DRAFT_KEY, JSON.stringify(state)); } catch (e) {}
|
|
3948
4330
|
}
|
|
3949
4331
|
} catch (e) {}
|
|
@@ -4136,18 +4518,18 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4136
4518
|
|
|
4137
4519
|
/* ── step navigation ──
|
|
4138
4520
|
Since task 1004417 the front door comes first: gate 0 · terms 1 · fork 2 ·
|
|
4139
|
-
name 3 · type 4 · about 5 · modules 6 · home 7 · address 8 · app 9 ·
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
var panels = { 0: 'panel-gate', 1: 'panel-terms', 2: 'panel-fork', 3: 'panel-name', 4: 'panel-type', 5: 'panel-detail', 6: 'panel-modules', 7: 'panel-home', 8: 'panel-address', 9: 'panel-app', 10: 'panel-
|
|
4521
|
+
name 3 · type 4 · about 5 · modules 6 · home 7 · address 8 · app 9 · look 10 ·
|
|
4522
|
+
review 11 · done 12 (the Look step joined at 10, task 1004421). The rail numbers
|
|
4523
|
+
3-11 as its 1-9 and steps aside on 1 and 2; done marks it all. About, Modules and
|
|
4524
|
+
Look are the steps Next can pass with nothing changed — Look has a default. */
|
|
4525
|
+
var panels = { 0: 'panel-gate', 1: 'panel-terms', 2: 'panel-fork', 3: 'panel-name', 4: 'panel-type', 5: 'panel-detail', 6: 'panel-modules', 7: 'panel-home', 8: 'panel-address', 9: 'panel-app', 10: 'panel-look', 11: 'panel-review', 12: 'panel-done' };
|
|
4144
4526
|
/* the rail at the done panel (task 1003244): "done" is the Create step
|
|
4145
4527
|
still running — the marker stays on 5 until the project actually
|
|
4146
4528
|
answers, and only then does the whole rail read finished. aria-current
|
|
4147
4529
|
tells a screen reader the same thing. */
|
|
4148
4530
|
function paintRail(step) {
|
|
4149
|
-
var landed = step ===
|
|
4150
|
-
var cur = step ===
|
|
4531
|
+
var landed = step === 12 && !!(live && live.instStatus === 'active');
|
|
4532
|
+
var cur = step === 12 ? 11 : step;
|
|
4151
4533
|
root.querySelectorAll('.steps li').forEach(function (li) {
|
|
4152
4534
|
var n = Number(li.getAttribute('data-step'));
|
|
4153
4535
|
var now = !landed && n === cur;
|
|
@@ -4158,7 +4540,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4158
4540
|
the button role, the tab stop and the data-goto the delegated handler
|
|
4159
4541
|
below reads. The done panel offers none — the project is filed, and
|
|
4160
4542
|
there is nothing behind it to change. */
|
|
4161
|
-
if (step <
|
|
4543
|
+
if (step < 12 && n < cur) {
|
|
4162
4544
|
li.setAttribute('role', 'button');
|
|
4163
4545
|
li.setAttribute('tabindex', '0');
|
|
4164
4546
|
li.setAttribute('data-goto', String(n));
|
|
@@ -4177,7 +4559,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4177
4559
|
paintRail(step);
|
|
4178
4560
|
saveDraft();
|
|
4179
4561
|
/* the done step takes the whole card — the bongo pair steps aside */
|
|
4180
|
-
root.querySelector('.createSplit').classList.toggle('wizDone', step ===
|
|
4562
|
+
root.querySelector('.createSplit').classList.toggle('wizDone', step === 12);
|
|
4181
4563
|
/* the front door (task 1004417): no rail on its two screens, and the fork
|
|
4182
4564
|
takes the whole card for the seed on its plate */
|
|
4183
4565
|
root.querySelector('.card').classList.toggle('wizFront', step === 1 || step === 2);
|
|
@@ -4197,13 +4579,14 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4197
4579
|
if (step === 7) { syncHome(); if (authKnown) ensureRepoAccess(); }
|
|
4198
4580
|
/* a resumed draft can land PAST the Home step with the probe never run —
|
|
4199
4581
|
Create would then file a repo it promised to make but never did */
|
|
4200
|
-
else if (step > 7 && step <
|
|
4582
|
+
else if (step > 7 && step < 12 && authKnown && repoAccess === null) ensureRepoAccess();
|
|
4201
4583
|
/* re-check on every entry, not just on typing: a resumed draft arrives here with
|
|
4202
4584
|
a handle nobody has asked the server about, and a handle free ten minutes ago
|
|
4203
4585
|
may not be now (task 1002898 N10) */
|
|
4204
4586
|
if (step === 8) { checkSlugAvailable(); renderAddrPanel(); }
|
|
4205
4587
|
if (step === 9) { syncAppStep(); syncRenderSetup(); }
|
|
4206
|
-
if (step === 10)
|
|
4588
|
+
if (step === 10) renderLook();
|
|
4589
|
+
if (step === 11) renderReview();
|
|
4207
4590
|
}
|
|
4208
4591
|
|
|
4209
4592
|
/* One delegated pair on the rail, so paintRail can add and drop data-goto
|
|
@@ -4411,7 +4794,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4411
4794
|
prevent, one screen before Create. */
|
|
4412
4795
|
function repaintForBundles() {
|
|
4413
4796
|
if (state.step === 6) renderModules();
|
|
4414
|
-
else if (state.step ===
|
|
4797
|
+
else if (state.step === 11) renderReview();
|
|
4415
4798
|
}
|
|
4416
4799
|
/* The row for the declared type, or null before the read lands / for a type
|
|
4417
4800
|
the table has no row for (including no type at all). */
|
|
@@ -4975,7 +5358,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4975
5358
|
: 'Once the project is up, we create your app on your Render account. Your key is used once, then thrown away.';
|
|
4976
5359
|
/* then the app's own progress, from the same read the project's page uses (task 1004353) */
|
|
4977
5360
|
var box = $('doneApp');
|
|
4978
|
-
if (box) watchApp(instanceId, box, function () { return state.step ===
|
|
5361
|
+
if (box) watchApp(instanceId, box, function () { return state.step === 12; }, 5000);
|
|
4979
5362
|
});
|
|
4980
5363
|
}).catch(function (err) {
|
|
4981
5364
|
note.className = 'hint err';
|
|
@@ -4983,6 +5366,42 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
4983
5366
|
});
|
|
4984
5367
|
body.key = '';
|
|
4985
5368
|
}
|
|
5369
|
+
/* ── the Look step (task 1004421, ADR 0358) ── the shared picker, built once on
|
|
5370
|
+
first entry and repainted after; the choice lives in the draft (state.look) like
|
|
5371
|
+
every other answer, logo included, so it survives the GitHub round-trip. */
|
|
5372
|
+
var lookPicker = null, lookPickerModel = null;
|
|
5373
|
+
/* the default: the platform's own look with the night tint on (owner D2), no logo */
|
|
5374
|
+
function defaultLook() { return { look: 'chrome-world', nightTint: true, accent: '#b48cf2', logo: null }; }
|
|
5375
|
+
function renderLook() {
|
|
5376
|
+
if (!state.look) { state.look = defaultLook(); saveDraft(); }
|
|
5377
|
+
/* rebuilt when the draft object itself was replaced (a fresh "New project" resets
|
|
5378
|
+
state wholesale), so the picker never edits an orphaned copy */
|
|
5379
|
+
if (!lookPicker || lookPickerModel !== state.look) {
|
|
5380
|
+
lookPickerModel = state.look;
|
|
5381
|
+
lookPicker = lookKit.buildLookPicker($('wizLook'), state.look, function () { saveDraft(); }, 'wiz',
|
|
5382
|
+
function () { return state.name; });
|
|
5383
|
+
} else lookPicker.repaint();
|
|
5384
|
+
}
|
|
5385
|
+
function lookRow() {
|
|
5386
|
+
var l = state.look || defaultLook();
|
|
5387
|
+
var name = l.look === 'custom'
|
|
5388
|
+
? 'Your own — ' + ((lookKit.lookAdjustAccent(l.accent) || {}).hex || '')
|
|
5389
|
+
: (lookKit.LOOKS[l.look] || lookKit.LOOKS['chrome-world']).title;
|
|
5390
|
+
return esc(name) + ', ' + (l.nightTint === false ? 'pure black at night' : 'a tinted night') +
|
|
5391
|
+
(l.logo ? ', with your logo' : ', no logo');
|
|
5392
|
+
}
|
|
5393
|
+
/* Send the look a moment after Create, like the Render key: the project exists, so a
|
|
5394
|
+
refusal here is the LOOK's, and the done panel says so in plain words. */
|
|
5395
|
+
function sendLookSetup(instanceId) {
|
|
5396
|
+
if (!instanceId) return;
|
|
5397
|
+
var note = $('doneLook');
|
|
5398
|
+
lookKit.sendLook(instanceId, state.look || defaultLook()).then(function () {
|
|
5399
|
+
note.hidden = true;
|
|
5400
|
+
}).catch(function (err) {
|
|
5401
|
+
note.hidden = false; note.className = 'hint err';
|
|
5402
|
+
note.textContent = 'Your project is being created, but its look was not saved: ' + err.message + ' You can choose it again on the project’s page.';
|
|
5403
|
+
});
|
|
5404
|
+
}
|
|
4986
5405
|
function renderReview() {
|
|
4987
5406
|
/* every road back to this step gets a live button — the create path
|
|
4988
5407
|
disables it and only the failure branch used to restore it, which
|
|
@@ -5012,7 +5431,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5012
5431
|
/* The wizard creates hosting_shape 'cloud-host', which since ADR 0345 means the
|
|
5013
5432
|
HALL is hosted by us; where the owner's APP runs is its own row (task 1004351). */
|
|
5014
5433
|
['Builders\' hall', 'Hosted by us, on the platform’s current release'],
|
|
5015
|
-
['App', appRow()]
|
|
5434
|
+
['App', appRow()],
|
|
5435
|
+
['Look', lookRow()]
|
|
5016
5436
|
];
|
|
5017
5437
|
$('reviewList').innerHTML = rows.map(function (r) {
|
|
5018
5438
|
return '<li><span class="k">' + r[0] + '</span><span class="v">' + r[1] + '</span></li>';
|
|
@@ -5159,8 +5579,9 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5159
5579
|
sessionStorage.removeItem(DRAFT_KEY);
|
|
5160
5580
|
} catch (e) {}
|
|
5161
5581
|
renderDone();
|
|
5162
|
-
show(
|
|
5582
|
+
show(12, true);
|
|
5163
5583
|
sendRenderSetup(data.instance && data.instance.id); // the key rides the new project's own setup run (task 1004352)
|
|
5584
|
+
sendLookSetup(data.instance && data.instance.id); // the look + logo, like the Render key: right after Create (task 1004421)
|
|
5164
5585
|
startProgress(data.instance && data.instance.id);
|
|
5165
5586
|
}).catch(function (err) {
|
|
5166
5587
|
if (String(err && err.message) === 'signing in') return;
|
|
@@ -5735,7 +6156,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5735
6156
|
5s tick never clobbers an open dropdown or mid-flight button) */
|
|
5736
6157
|
if (inst && inst.status !== live.instStatus) {
|
|
5737
6158
|
live.instStatus = inst.status; renderSteps();
|
|
5738
|
-
if (state.step ===
|
|
6159
|
+
if (state.step === 12) paintRail(12); /* the rail finishes only when the project answers */
|
|
5739
6160
|
}
|
|
5740
6161
|
var terminal = renderProgress(inst);
|
|
5741
6162
|
if (!terminal) pollTimer = setTimeout(tick, 5000);
|
|
@@ -5775,7 +6196,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5775
6196
|
is three-valued and null is its "nothing said" sentinel; leaving the key out
|
|
5776
6197
|
left it UNDEFINED, which is not null — so pickedModules() reached .slice()
|
|
5777
6198
|
on it and the second project's Modules step threw. */
|
|
5778
|
-
state = { step: 1, name: '', projType: null, description: '', teamShape: null, modules: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false, appPlatform: 'later', termsAck: false };
|
|
6199
|
+
state = { step: 1, name: '', projType: null, description: '', teamShape: null, modules: null, mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '', noAddress: false, appPlatform: 'later', look: null, termsAck: false };
|
|
5779
6200
|
$('newName').value = ''; $('f-owner').value = ''; $('f-repo').value = ''; $('f-slug').value = ''; $('f-domain').value = '';
|
|
5780
6201
|
$('f-noaddr').checked = false; $('f-desc').value = '';
|
|
5781
6202
|
/* Re-seed the owner from the signed-in account for the next project. */
|
|
@@ -5854,7 +6275,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5854
6275
|
if (focus && !resumeIntent) {
|
|
5855
6276
|
var draft = null;
|
|
5856
6277
|
try { draft = JSON.parse(sessionStorage.getItem(DRAFT_KEY) || 'null'); } catch (e) {}
|
|
5857
|
-
var at = draft && draft.step >= 1 && draft.step <=
|
|
6278
|
+
var at = draft && draft.step >= 1 && draft.step <= 11 ? draft.step : 0;
|
|
5858
6279
|
if (!at) { resetForNewProject(); at = 1; }
|
|
5859
6280
|
paintResumeOffer(created);
|
|
5860
6281
|
/* step 1's own focus target is its one control, the terms tick, not the
|
|
@@ -5878,7 +6299,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5878
6299
|
manifestState = created.manifest_state; manifestPending = true;
|
|
5879
6300
|
}
|
|
5880
6301
|
renderDone();
|
|
5881
|
-
show(
|
|
6302
|
+
show(12, !!focus);
|
|
5882
6303
|
startProgress(created.id);
|
|
5883
6304
|
resumePolls();
|
|
5884
6305
|
}
|
|
@@ -5895,7 +6316,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5895
6316
|
/* before identity settles, never render past step 1 (the terms, since task
|
|
5896
6317
|
1004417): deeper steps carry authenticated side effects (the Home-step repo
|
|
5897
6318
|
probe), and onAuth() re-enters to the real draft step the moment /me answers */
|
|
5898
|
-
var step = authKnown && state.step >= 1 && state.step <=
|
|
6319
|
+
var step = authKnown && state.step >= 1 && state.step <= 11 ? state.step : 1;
|
|
5899
6320
|
if (step === 1) {
|
|
5900
6321
|
/* Pre-auth, step 1 is a PLACEHOLDER, not where the visitor is — but show()
|
|
5901
6322
|
persists whatever step it renders, so it used to overwrite the draft's real
|
|
@@ -5904,7 +6325,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
5904
6325
|
step back after rendering the placeholder, in memory AND in the draft. */
|
|
5905
6326
|
var resumeStep = state.step;
|
|
5906
6327
|
show(1, false);
|
|
5907
|
-
if (!authKnown && resumeStep >= 1 && resumeStep <=
|
|
6328
|
+
if (!authKnown && resumeStep >= 1 && resumeStep <= 11) { state.step = resumeStep; saveDraft(); }
|
|
5908
6329
|
if (focus) $('termsAck').focus();
|
|
5909
6330
|
} else {
|
|
5910
6331
|
show(step, !!focus);
|
|
@@ -6701,6 +7122,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
6701
7122
|
progress survives the tick because only a different project re-renders it */
|
|
6702
7123
|
renderPick(inst);
|
|
6703
7124
|
renderCardPick(inst);
|
|
7125
|
+
renderLookCard(inst);
|
|
6704
7126
|
/* invite a builder (task 1002346): fetch-free too — the widget needs only the
|
|
6705
7127
|
slug, and the API is the wall. This read is own-scoped and 404s for anyone
|
|
6706
7128
|
who is not the owner, so reaching renderManage at all IS the ownership
|
|
@@ -7694,6 +8116,55 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
7694
8116
|
function backdropPlate(key) {
|
|
7695
8117
|
return CARD_PLATES[key] ? '/assets/world/' + CARD_PLATES[key] : '';
|
|
7696
8118
|
}
|
|
8119
|
+
/* ── its look (task 1004421, spec D8): the wizard's picker on an existing project.
|
|
8120
|
+
Read once per project (GET …/look) — the tick re-renders the page every minute and
|
|
8121
|
+
must not throw away a choice in progress — and saved through lookKit.sendLook, the
|
|
8122
|
+
wizard's own send. A project that never chose has look 'own': no look is ticked and
|
|
8123
|
+
the preview shows the chrome world it already wears. ── */
|
|
8124
|
+
var mLookFor = null, mLookModel = null, mLookPicker = null;
|
|
8125
|
+
function renderLookCard(inst) {
|
|
8126
|
+
if (mLookFor === inst.id) return;
|
|
8127
|
+
mLookFor = inst.id; mLookModel = null; mLookPicker = null;
|
|
8128
|
+
var host = $('mLookPick');
|
|
8129
|
+
$('mLookActions').hidden = true;
|
|
8130
|
+
$('mLookMsg').textContent = ''; $('mLookMsg').className = 'projMsg';
|
|
8131
|
+
while (host.firstChild) host.removeChild(host.firstChild);
|
|
8132
|
+
var wait = document.createElement('p'); wait.className = 'finePrint'; wait.textContent = 'Reading the project…';
|
|
8133
|
+
host.appendChild(wait);
|
|
8134
|
+
var forId = inst.id;
|
|
8135
|
+
fetch(API + '/provisioning/instances/' + encodeURIComponent(inst.id) + '/look', { credentials: 'same-origin' })
|
|
8136
|
+
.then(function (res) { return jsonOrEmpty(res).then(function (data) { if (!res.ok) throw apiError(data, res.status); return data; }); })
|
|
8137
|
+
.then(function (data) {
|
|
8138
|
+
if (mLookFor !== forId) return;
|
|
8139
|
+
var l = data.look || {};
|
|
8140
|
+
mLookModel = { look: l.look || 'own', nightTint: l.night_tint !== 'off', accent: l.look_accent || '#b48cf2', logo: null, logoUrl: l.logo_url || '' };
|
|
8141
|
+
mLookPicker = lookKit.buildLookPicker(host, mLookModel, function () { $('mLookMsg').textContent = ''; }, 'mLk',
|
|
8142
|
+
function () { return (manageInst && manageInst.slug) || ''; });
|
|
8143
|
+
$('mLookActions').hidden = false;
|
|
8144
|
+
})
|
|
8145
|
+
.catch(function (err) {
|
|
8146
|
+
if (mLookFor !== forId) return;
|
|
8147
|
+
wait.textContent = (err && err.message) || 'The look could not be read — reload to try again.';
|
|
8148
|
+
});
|
|
8149
|
+
}
|
|
8150
|
+
$('mLookSave').addEventListener('click', function () {
|
|
8151
|
+
if (!mLookModel || !manageInst) return;
|
|
8152
|
+
var btn = this, box = $('mLookMsg');
|
|
8153
|
+
if (mLookModel.look === 'own' && !mLookModel.logo && !mLookModel.logoDropped) { box.className = 'projMsg err'; box.textContent = 'Pick a look first.'; return; }
|
|
8154
|
+
btn.disabled = true; box.className = 'projMsg'; box.textContent = 'Saving…';
|
|
8155
|
+
lookKit.sendLook(manageId, mLookModel).then(function (r) {
|
|
8156
|
+
var l = r.look || {};
|
|
8157
|
+
mLookModel.logo = null; mLookModel.logoDropped = false; mLookModel.logoUrl = l.logo_url || '';
|
|
8158
|
+
if (l.look_accent) mLookModel.accent = l.look_accent;
|
|
8159
|
+
if (mLookPicker) mLookPicker.repaint();
|
|
8160
|
+
box.className = 'projMsg ok';
|
|
8161
|
+
box.textContent = (r.adjusted ? 'Your colour was adjusted to ' + r.adjusted.to + ' so text stays readable. ' : '') + (r.message || 'Saved.');
|
|
8162
|
+
}).catch(function (err) {
|
|
8163
|
+
box.className = 'projMsg err';
|
|
8164
|
+
box.textContent = (err && err.message) || 'The look could not be saved — try again in a moment.';
|
|
8165
|
+
}).then(function () { btn.disabled = false; });
|
|
8166
|
+
});
|
|
8167
|
+
|
|
7697
8168
|
var mPick = null; /* the pending choice: { shape, material, style, accent } */
|
|
7698
8169
|
var mPickFor = null; /* which project it belongs to — a different one starts afresh */
|
|
7699
8170
|
var mPickTouched = false; /* the owner changed something since the seed — a late catalog must not undo it */
|