vibespot 1.6.5 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -3
- package/assets/whats-new.json +27 -0
- package/dist/index.js +579 -681
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
- package/ui/chat.js +671 -9
- package/ui/dashboard.js +53 -12
- package/ui/docs/index.html +57 -2
- package/ui/email-preview.js +1 -5
- package/ui/escape-html.js +14 -0
- package/ui/field-editor.js +6 -12
- package/ui/field-save.js +82 -0
- package/ui/index.html +10 -4
- package/ui/inline-edit.js +116 -570
- package/ui/plan.js +22 -13
- package/ui/preview-agent.js +1050 -0
- package/ui/preview.js +248 -265
- package/ui/section-controls.js +16 -622
- package/ui/setup.js +73 -20
- package/ui/styles.css +424 -0
- package/ui/upload-panel.js +7 -8
- package/ui/whats-new.js +249 -0
- package/assets/readme/00-hero-banner.png +0 -0
- package/assets/readme/00-hero-banner.svg +0 -59
- package/assets/readme/01-vibe-coding-hero.png +0 -0
- package/assets/readme/02-plan-mode.png +0 -0
- package/assets/readme/03-figma-import.png +0 -0
- package/assets/readme/04-multi-page-sites.png +0 -0
- package/assets/readme/05-inline-wysiwyg.png +0 -0
- package/assets/readme/06-hubspot-upload.png +0 -0
- package/ui/docs/screenshots/brand-kit-preview.png +0 -0
- package/ui/docs/screenshots/content-type-dropdown.png +0 -0
- package/ui/docs/screenshots/editor-full-layout.png +0 -0
- package/ui/docs/screenshots/inline-wysiwyg-editing.png +0 -0
- package/ui/docs/screenshots/module-overview-slideout.png +0 -0
- package/ui/docs/screenshots/multi-page-tree.png +0 -0
- package/ui/docs/screenshots/onboarding-walkthrough.png +0 -0
- package/ui/docs/screenshots/split-pane-view.png +0 -0
- package/ui/docs/screenshots/visual-controls-toolbar.png +0 -0
- package/ui/docs/screenshots/workspace-tabs.png +0 -0
package/ui/whats-new.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "What's new" release dialog — shown once after a version upgrade.
|
|
3
|
+
*
|
|
4
|
+
* SCAFFOLD (VIB-1886): UX + clickable demo only. Production gating
|
|
5
|
+
* (lastSeenVersion read/write, server route) is a follow-up owned by CTO.
|
|
6
|
+
*
|
|
7
|
+
* Mount points:
|
|
8
|
+
* - Production seam: maybeShowWhatsNew({ currentVersion, lastSeenVersion, onDismiss })
|
|
9
|
+
* - Demo / screenshots: add ?whatsnew=1 to the URL (search OR hash query) and
|
|
10
|
+
* this file self-renders the modal over whatever view is loaded.
|
|
11
|
+
*
|
|
12
|
+
* Reuses the existing overlay pattern (see ui/dialog.js) and the design tokens
|
|
13
|
+
* in ui/styles.css. No new visual language, no one-off values.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// HTML-escape helper — guard for load order (dialog.js usually defines `esc`).
|
|
17
|
+
if (typeof esc === "undefined") {
|
|
18
|
+
// eslint-disable-next-line no-var
|
|
19
|
+
var _wnEscMap = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
20
|
+
// eslint-disable-next-line no-var
|
|
21
|
+
var esc = function (str) {
|
|
22
|
+
return String(str).replace(/[&<>"']/g, function (c) { return _wnEscMap[c]; });
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Release content. In production this is sourced from the current CHANGELOG.md
|
|
28
|
+
* section (CTO follow-up). For the scaffold it's the real, condensed 1.7.x story
|
|
29
|
+
* (the 1.7.1–1.7.7 entries shipped together on 2026-06-27 — checkpoints + softened
|
|
30
|
+
* barge-in). Copy run through the humanify standard: verbs lead, no buzzwords.
|
|
31
|
+
*/
|
|
32
|
+
const WHATS_NEW = {
|
|
33
|
+
version: "1.7.7",
|
|
34
|
+
date: "June 27, 2026",
|
|
35
|
+
changelogUrl: "https://github.com/borismichel/vibespot/blob/main/CHANGELOG.md",
|
|
36
|
+
highlights: [
|
|
37
|
+
{
|
|
38
|
+
title: "Checkpoints survive interruptions",
|
|
39
|
+
body: "Refresh the tab, lock your iPad, or restart the server. An open design, structure, or brand checkpoint comes back exactly where you left it instead of expiring.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
title: "Mid-build messages wait their turn",
|
|
43
|
+
body: "Type while a page is generating and your message queues as “runs next” instead of cancelling the build. Hit Interrupt now when you want to change course.",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
title: "Every decision is on the record",
|
|
47
|
+
body: "Approving a checkpoint leaves a line in the chat: the palette and fonts you chose, the sections you kept. You can see what you signed off on.",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Four-pointed brand star (same mark as the topbar logo), accent-gradient fill.
|
|
53
|
+
const _WN_STAR = `
|
|
54
|
+
<svg viewBox="0 0 512 512" width="22" height="22" aria-hidden="true">
|
|
55
|
+
<defs>
|
|
56
|
+
<linearGradient id="wn-star-grad" x1="0" y1="0" x2="1" y2="1">
|
|
57
|
+
<stop offset="0" stop-color="#e8613a"/>
|
|
58
|
+
<stop offset="1" stop-color="#f2825f"/>
|
|
59
|
+
</linearGradient>
|
|
60
|
+
</defs>
|
|
61
|
+
<path d="M256 76 Q280 220 436 256 Q280 292 256 436 Q232 292 76 256 Q232 220 256 76 Z" fill="url(#wn-star-grad)"/>
|
|
62
|
+
</svg>`;
|
|
63
|
+
|
|
64
|
+
let _wnOpen = false;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Render the "what's new" modal.
|
|
68
|
+
* @param {object} [opts]
|
|
69
|
+
* @param {object} [opts.content] — release content (defaults to WHATS_NEW)
|
|
70
|
+
* @param {Function} [opts.onDismiss] — called when the user dismisses the modal
|
|
71
|
+
* @returns {Promise<void>} resolves on dismiss
|
|
72
|
+
*/
|
|
73
|
+
function showWhatsNew(opts) {
|
|
74
|
+
opts = opts || {};
|
|
75
|
+
const content = opts.content || WHATS_NEW;
|
|
76
|
+
if (_wnOpen) return Promise.resolve();
|
|
77
|
+
_wnOpen = true;
|
|
78
|
+
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
const lastFocused = document.activeElement;
|
|
81
|
+
|
|
82
|
+
const overlay = document.createElement("div");
|
|
83
|
+
overlay.className = "whatsnew-overlay";
|
|
84
|
+
|
|
85
|
+
const bullets = content.highlights.map((h) => `
|
|
86
|
+
<li class="whatsnew__item">
|
|
87
|
+
<span class="whatsnew__marker" aria-hidden="true">${_WN_STAR}</span>
|
|
88
|
+
<div class="whatsnew__item-text">
|
|
89
|
+
<span class="whatsnew__item-title">${esc(h.title)}</span>
|
|
90
|
+
<span class="whatsnew__item-body">${esc(h.body)}</span>
|
|
91
|
+
</div>
|
|
92
|
+
</li>`).join("");
|
|
93
|
+
|
|
94
|
+
overlay.innerHTML = `
|
|
95
|
+
<div class="whatsnew-dialog" role="dialog" aria-modal="true"
|
|
96
|
+
aria-labelledby="whatsnew-title" aria-describedby="whatsnew-desc">
|
|
97
|
+
<button class="whatsnew__close" type="button" data-action="dismiss" aria-label="Close">
|
|
98
|
+
<svg class="icon icon--sm" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
99
|
+
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
100
|
+
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
101
|
+
</svg>
|
|
102
|
+
</button>
|
|
103
|
+
|
|
104
|
+
<div class="whatsnew__brand">
|
|
105
|
+
<span class="whatsnew__logo">${_WN_STAR}</span>
|
|
106
|
+
<span class="whatsnew__wordmark">vibeSpot</span>
|
|
107
|
+
<span class="whatsnew__version">v${esc(content.version)}</span>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<h2 class="whatsnew__title" id="whatsnew-title">What’s new in vibeSpot ${esc(content.version)}</h2>
|
|
111
|
+
<p class="whatsnew__date" id="whatsnew-desc">Released ${esc(content.date)}</p>
|
|
112
|
+
|
|
113
|
+
<ul class="whatsnew__list">${bullets}</ul>
|
|
114
|
+
|
|
115
|
+
<div class="whatsnew__actions">
|
|
116
|
+
<button class="btn btn--secondary" type="button" data-action="dismiss">Got it</button>
|
|
117
|
+
<a class="btn btn--primary" href="${esc(content.changelogUrl)}"
|
|
118
|
+
target="_blank" rel="noopener noreferrer" data-action="changelog">
|
|
119
|
+
Read the full changelog
|
|
120
|
+
</a>
|
|
121
|
+
</div>
|
|
122
|
+
</div>`;
|
|
123
|
+
|
|
124
|
+
document.body.appendChild(overlay);
|
|
125
|
+
|
|
126
|
+
const dialog = overlay.querySelector(".whatsnew-dialog");
|
|
127
|
+
const focusables = dialog.querySelectorAll('button, a[href], [tabindex]:not([tabindex="-1"])');
|
|
128
|
+
|
|
129
|
+
const close = () => {
|
|
130
|
+
if (!_wnOpen) return;
|
|
131
|
+
_wnOpen = false;
|
|
132
|
+
document.removeEventListener("keydown", onKey);
|
|
133
|
+
overlay.remove();
|
|
134
|
+
if (typeof opts.onDismiss === "function") opts.onDismiss();
|
|
135
|
+
if (lastFocused && typeof lastFocused.focus === "function") lastFocused.focus();
|
|
136
|
+
resolve();
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Dismiss affordances: ✕, "Got it", backdrop click. The changelog link
|
|
140
|
+
// opens in a new tab and also counts as "seen" so it never nags again.
|
|
141
|
+
overlay.addEventListener("click", (e) => {
|
|
142
|
+
const action = e.target.closest("[data-action]");
|
|
143
|
+
if (e.target === overlay) return close(); // backdrop
|
|
144
|
+
if (!action) return;
|
|
145
|
+
if (action.dataset.action === "changelog") { close(); return; } // let the link open
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
close();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
function onKey(e) {
|
|
151
|
+
if (e.key === "Escape") { e.preventDefault(); return close(); }
|
|
152
|
+
if (e.key === "Tab" && focusables.length) { // simple focus trap
|
|
153
|
+
const first = focusables[0];
|
|
154
|
+
const last = focusables[focusables.length - 1];
|
|
155
|
+
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
|
|
156
|
+
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
document.addEventListener("keydown", onKey);
|
|
160
|
+
|
|
161
|
+
// Focus the primary action so keyboard users land inside the dialog.
|
|
162
|
+
setTimeout(() => {
|
|
163
|
+
const primary = dialog.querySelector('[data-action="changelog"]');
|
|
164
|
+
if (primary) primary.focus();
|
|
165
|
+
}, 50);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Production seam (CTO follow-up wires this on web-UI load).
|
|
171
|
+
* Shows the modal once per release: when the installed version differs from the
|
|
172
|
+
* version this user last dismissed. On dismiss, persist lastSeenVersion.
|
|
173
|
+
*
|
|
174
|
+
* @param {object} args
|
|
175
|
+
* @param {string} args.currentVersion — package.json version
|
|
176
|
+
* @param {string} [args.lastSeenVersion]— from ~/.vibespot/config.json
|
|
177
|
+
* @param {Function} [args.onDismiss] — persist callback (write lastSeenVersion)
|
|
178
|
+
* @param {object} [args.content] — release content from CHANGELOG.md
|
|
179
|
+
* @returns {boolean} whether the modal was shown
|
|
180
|
+
*/
|
|
181
|
+
function maybeShowWhatsNew(args) {
|
|
182
|
+
args = args || {};
|
|
183
|
+
if (!args.currentVersion) return false;
|
|
184
|
+
if (args.currentVersion === args.lastSeenVersion) return false;
|
|
185
|
+
showWhatsNew({
|
|
186
|
+
content: args.content,
|
|
187
|
+
onDismiss: () => {
|
|
188
|
+
if (typeof args.onDismiss === "function") args.onDismiss(args.currentVersion);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// --- Demo trigger (scaffold only) ------------------------------------------
|
|
195
|
+
// ?whatsnew=1 in the query string OR after the hash (e.g. #/app/foo?whatsnew=1)
|
|
196
|
+
// renders the modal so it can be captured standalone for screenshots.
|
|
197
|
+
function _wnDemoRequested() {
|
|
198
|
+
const inSearch = new URLSearchParams(window.location.search).get("whatsnew") === "1";
|
|
199
|
+
const hash = window.location.hash || "";
|
|
200
|
+
const qIndex = hash.indexOf("?");
|
|
201
|
+
const inHash = qIndex !== -1 &&
|
|
202
|
+
new URLSearchParams(hash.slice(qIndex + 1)).get("whatsnew") === "1";
|
|
203
|
+
return inSearch || inHash;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function _wnInitDemo() {
|
|
207
|
+
if (_wnDemoRequested()) {
|
|
208
|
+
// Small delay so the builder chrome paints first (modal sits over it).
|
|
209
|
+
setTimeout(() => showWhatsNew(), 400);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// --- Production bootstrap (VIB-1885) ----------------------------------------
|
|
214
|
+
// On launch, ask the server whether there's an undismissed release to show.
|
|
215
|
+
// The server reads assets/whats-new.json (generated from CHANGELOG.md) and
|
|
216
|
+
// gates on config.lastSeenVersion, so this surfaces once per upgrade. Dismissing
|
|
217
|
+
// persists lastSeenVersion via POST /api/whats-new/dismiss so it never nags again.
|
|
218
|
+
function _wnInitProd() {
|
|
219
|
+
if (_wnDemoRequested()) return; // demo path owns the modal
|
|
220
|
+
fetch("/api/whats-new")
|
|
221
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
222
|
+
.then((data) => {
|
|
223
|
+
if (!data || !data.show || !data.content) return;
|
|
224
|
+
maybeShowWhatsNew({
|
|
225
|
+
currentVersion: data.content.version,
|
|
226
|
+
lastSeenVersion: null, // server already decided show=true
|
|
227
|
+
content: data.content,
|
|
228
|
+
onDismiss: (version) => {
|
|
229
|
+
fetch("/api/whats-new/dismiss", {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: { "Content-Type": "application/json" },
|
|
232
|
+
body: JSON.stringify({ version }),
|
|
233
|
+
}).catch(() => {});
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
})
|
|
237
|
+
.catch(() => {}); // network/offline: silently skip — never block the app
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function _wnInit() {
|
|
241
|
+
_wnInitDemo();
|
|
242
|
+
_wnInitProd();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (document.readyState === "loading") {
|
|
246
|
+
document.addEventListener("DOMContentLoaded", _wnInit);
|
|
247
|
+
} else {
|
|
248
|
+
_wnInit();
|
|
249
|
+
}
|
|
Binary file
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 400" width="1600" height="400">
|
|
2
|
-
<defs>
|
|
3
|
-
<style>
|
|
4
|
-
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400&family=Space+Grotesk:wght@700&display=swap');
|
|
5
|
-
</style>
|
|
6
|
-
<linearGradient id="star-grad" x1="0" y1="0" x2="1" y2="1">
|
|
7
|
-
<stop offset="0%" stop-color="#e8613a"/>
|
|
8
|
-
<stop offset="100%" stop-color="#f2825f"/>
|
|
9
|
-
</linearGradient>
|
|
10
|
-
<linearGradient id="bar-grad" x1="0" y1="0" x2="1" y2="0">
|
|
11
|
-
<stop offset="0%" stop-color="#e8613a"/>
|
|
12
|
-
<stop offset="100%" stop-color="#f2825f"/>
|
|
13
|
-
</linearGradient>
|
|
14
|
-
<radialGradient id="glow1" cx="75%" cy="10%" r="45%">
|
|
15
|
-
<stop offset="0%" stop-color="#e8613a" stop-opacity="0.08"/>
|
|
16
|
-
<stop offset="40%" stop-color="#e8613a" stop-opacity="0.02"/>
|
|
17
|
-
<stop offset="65%" stop-color="#e8613a" stop-opacity="0"/>
|
|
18
|
-
</radialGradient>
|
|
19
|
-
<radialGradient id="glow2" cx="20%" cy="95%" r="35%">
|
|
20
|
-
<stop offset="0%" stop-color="#e8613a" stop-opacity="0.06"/>
|
|
21
|
-
<stop offset="40%" stop-color="#e8613a" stop-opacity="0.01"/>
|
|
22
|
-
<stop offset="65%" stop-color="#e8613a" stop-opacity="0"/>
|
|
23
|
-
</radialGradient>
|
|
24
|
-
</defs>
|
|
25
|
-
|
|
26
|
-
<!-- Background -->
|
|
27
|
-
<rect width="1600" height="400" fill="#faf9f7"/>
|
|
28
|
-
<rect width="1600" height="400" fill="url(#glow1)"/>
|
|
29
|
-
<rect width="1600" height="400" fill="url(#glow2)"/>
|
|
30
|
-
|
|
31
|
-
<!-- Decorative stars -->
|
|
32
|
-
<g opacity="0.10"><path d="M214 69 C214.6 78.6,221.4 85.4,231 86 C221.4 86.6,214.6 93.4,214 103 C213.4 93.4,206.6 86.6,197 86 C206.6 85.4,213.4 78.6,214 69Z" fill="#e8613a"/></g>
|
|
33
|
-
<g opacity="0.07"><path d="M149 295 C149.4 301.4,153.6 305.6,160 306 C153.6 306.4,149.4 310.6,149 317 C148.6 310.6,144.4 306.4,138 306 C144.4 305.6,148.6 301.4,149 295Z" fill="#e8613a"/></g>
|
|
34
|
-
<g opacity="0.09"><path d="M1372 88 C1372.6 97,1378.6 103,1388 103.6 C1378.6 104.2,1372.6 110.2,1372 119.2 C1371.4 110.2,1365.4 104.2,1356 103.6 C1365.4 103,1371.4 97,1372 88Z" fill="#e8613a"/></g>
|
|
35
|
-
<g opacity="0.06"><path d="M1428 325 C1428.8 337,1437 345.2,1449 346 C1437 346.8,1428.8 355,1428 367 C1427.2 355,1419 346.8,1407 346 C1419 345.2,1427.2 337,1428 325Z" fill="#e8613a"/></g>
|
|
36
|
-
<g opacity="0.05"><path d="M371 175 C371.3 179.8,374.2 182.7,379 183 C374.2 183.3,371.3 186.2,371 191 C370.7 186.2,367.8 183.3,363 183 C367.8 182.7,370.7 179.8,371 175Z" fill="#e8613a"/></g>
|
|
37
|
-
<g opacity="0.07"><path d="M413 303 C413.4 310,418 314.6,425 315 C418 315.4,413.4 320,413 327 C412.6 320,408 315.4,401 315 C408 314.6,412.6 310,413 303Z" fill="#e8613a"/></g>
|
|
38
|
-
<g opacity="0.06"><path d="M1188 57 C1188.3 62.4,1191.6 65.7,1197 66 C1191.6 66.3,1188.3 69.6,1188 75 C1187.7 69.6,1184.4 66.3,1179 66 C1184.4 65.7,1187.7 62.4,1188 57Z" fill="#e8613a"/></g>
|
|
39
|
-
<g opacity="0.05"><path d="M1172 340 C1172.5 348,1177.8 353.3,1186 353.8 C1177.8 354.3,1172.5 359.6,1172 367.6 C1171.5 359.6,1166.2 354.3,1158 353.8 C1166.2 353.3,1171.5 348,1172 340Z" fill="#e8613a"/></g>
|
|
40
|
-
<g opacity="0.04"><path d="M1288 326 C1288.2 330,1290.6 332.4,1295 332.6 C1290.6 332.8,1288.2 335.2,1288 339.2 C1287.8 335.2,1285.4 332.8,1281 332.6 C1285.4 332.4,1287.8 330,1288 326Z" fill="#e8613a"/></g>
|
|
41
|
-
<g opacity="0.04"><path d="M533 138 C533.2 141.6,535.4 143.8,539 144 C535.4 144.2,533.2 146.4,533 150 C532.8 146.4,530.6 144.2,527 144 C530.6 143.8,532.8 141.6,533 138Z" fill="#e8613a"/></g>
|
|
42
|
-
|
|
43
|
-
<!-- Logo mark (dark container + star) -->
|
|
44
|
-
<g transform="translate(680, 128)">
|
|
45
|
-
<rect width="80" height="80" rx="18" fill="#0c0a09"/>
|
|
46
|
-
<g transform="translate(15, 15) scale(0.0977)">
|
|
47
|
-
<path d="M256 28 C260 140, 372 252, 484 256 C372 260, 260 372, 256 484 C252 372, 140 260, 28 256 C140 252, 252 140, 256 28Z" fill="url(#star-grad)"/>
|
|
48
|
-
</g>
|
|
49
|
-
</g>
|
|
50
|
-
|
|
51
|
-
<!-- Wordmark -->
|
|
52
|
-
<text x="784" y="183" font-family="'Space Grotesk', system-ui, -apple-system, sans-serif" font-weight="700" font-size="60" fill="#1a1715" letter-spacing="-1.8">vibeSpot</text>
|
|
53
|
-
|
|
54
|
-
<!-- Tagline -->
|
|
55
|
-
<text x="800" y="234" font-family="'DM Sans', system-ui, -apple-system, sans-serif" font-weight="400" font-size="21" fill="#78716c" text-anchor="middle" letter-spacing="0.2">Build HubSpot landing pages with AI</text>
|
|
56
|
-
|
|
57
|
-
<!-- Accent bar -->
|
|
58
|
-
<rect x="776" y="256" width="48" height="3" rx="1.5" fill="url(#bar-grad)" opacity="0.6"/>
|
|
59
|
-
</svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|