vibespot 1.0.1 → 1.0.2
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 +1 -0
- package/dist/index.js +371 -317
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/ui/chat.js +60 -0
- package/ui/dashboard.js +110 -62
- package/ui/index.html +41 -21
- package/ui/styles.css +55 -34
package/package.json
CHANGED
package/ui/chat.js
CHANGED
|
@@ -35,6 +35,7 @@ function connectWebSocket() {
|
|
|
35
35
|
ws.close();
|
|
36
36
|
ws = null;
|
|
37
37
|
}
|
|
38
|
+
brandExtractionPromptShown = false;
|
|
38
39
|
|
|
39
40
|
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
|
|
40
41
|
ws = new WebSocket(`${protocol}//${location.host}`);
|
|
@@ -174,6 +175,18 @@ function handleWsMessage(msg) {
|
|
|
174
175
|
case "agentic_prompt":
|
|
175
176
|
handleAgenticPrompt();
|
|
176
177
|
break;
|
|
178
|
+
case "suggest_brand_extraction":
|
|
179
|
+
handleSuggestBrandExtraction();
|
|
180
|
+
break;
|
|
181
|
+
case "brand_asset_extracted":
|
|
182
|
+
handleBrandAssetExtracted(msg.assetType);
|
|
183
|
+
break;
|
|
184
|
+
case "brand_extraction_complete":
|
|
185
|
+
handleBrandExtractionComplete();
|
|
186
|
+
break;
|
|
187
|
+
case "brand_extraction_error":
|
|
188
|
+
appendSystemMessage("Brand extraction failed: " + (msg.message || "Unknown error"));
|
|
189
|
+
break;
|
|
177
190
|
}
|
|
178
191
|
}
|
|
179
192
|
|
|
@@ -440,6 +453,53 @@ async function handleAgenticPrompt() {
|
|
|
440
453
|
} catch { /* ignore */ }
|
|
441
454
|
}
|
|
442
455
|
|
|
456
|
+
// ---------------------------------------------------------------------------
|
|
457
|
+
// Brand asset extraction prompt
|
|
458
|
+
// ---------------------------------------------------------------------------
|
|
459
|
+
|
|
460
|
+
let brandExtractionPromptShown = false;
|
|
461
|
+
|
|
462
|
+
function handleSuggestBrandExtraction() {
|
|
463
|
+
if (brandExtractionPromptShown) return;
|
|
464
|
+
brandExtractionPromptShown = true;
|
|
465
|
+
|
|
466
|
+
const el = document.createElement("div");
|
|
467
|
+
el.className = "chat-msg chat-msg--system brand-extraction-prompt";
|
|
468
|
+
el.innerHTML = `
|
|
469
|
+
<div class="chat-msg__system" style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
|
470
|
+
<span>Extract product context & styleguide from your page? Helps keep future templates consistent.</span>
|
|
471
|
+
<button class="btn btn--sm btn--primary" id="btn-accept-extraction">Extract</button>
|
|
472
|
+
<button class="btn btn--sm btn--outline" id="btn-dismiss-extraction">Dismiss</button>
|
|
473
|
+
</div>
|
|
474
|
+
`;
|
|
475
|
+
|
|
476
|
+
messagesEl.appendChild(el);
|
|
477
|
+
scrollToBottom();
|
|
478
|
+
|
|
479
|
+
el.querySelector("#btn-accept-extraction").addEventListener("click", () => {
|
|
480
|
+
el.querySelector(".brand-extraction-prompt__actions").innerHTML =
|
|
481
|
+
'<span class="brand-extraction-prompt__status">Extracting...</span>';
|
|
482
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
483
|
+
ws.send(JSON.stringify({ type: "extract_brand_assets" }));
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
el.querySelector("#btn-dismiss-extraction").addEventListener("click", () => {
|
|
488
|
+
el.remove();
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function handleBrandAssetExtracted(assetType) {
|
|
493
|
+
const labelMap = { themeContext: "Product context", styleguide: "Styleguide", brandvoice: "Brand voice" };
|
|
494
|
+
const label = labelMap[assetType] || assetType;
|
|
495
|
+
appendSystemMessage(`${label} extracted and saved.`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function handleBrandExtractionComplete() {
|
|
499
|
+
const prompt = document.querySelector(".brand-extraction-prompt");
|
|
500
|
+
if (prompt) prompt.remove();
|
|
501
|
+
}
|
|
502
|
+
|
|
443
503
|
// ---------------------------------------------------------------------------
|
|
444
504
|
// File attachments
|
|
445
505
|
// ---------------------------------------------------------------------------
|
package/ui/dashboard.js
CHANGED
|
@@ -273,32 +273,32 @@ document.getElementById("dashboard-preview-close").addEventListener("click", clo
|
|
|
273
273
|
// Brand assets
|
|
274
274
|
// ---------------------------------------------------------------------------
|
|
275
275
|
|
|
276
|
+
const ASSET_LABELS = { styleguide: "Styleguide", brandvoice: "Brand Voice", themeContext: "Product Context" };
|
|
277
|
+
const ASSET_FILES = { styleguide: "styleguide.md", brandvoice: "brandvoice.md", themeContext: "theme-context.md" };
|
|
278
|
+
const ASSET_FLAGS = { styleguide: "hasStyleguide", brandvoice: "hasBrandvoice", themeContext: "hasThemeContext" };
|
|
279
|
+
|
|
276
280
|
function renderBrandAssets(assets) {
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
281
|
+
for (const [type, flagKey] of Object.entries(ASSET_FLAGS)) {
|
|
282
|
+
const card = document.querySelector(`.brand-asset-card[data-asset="${type}"]`);
|
|
283
|
+
if (!card) continue;
|
|
284
|
+
const icon = card.querySelector(".brand-asset-card__icon");
|
|
285
|
+
const hasAsset = !!assets[flagKey];
|
|
286
|
+
|
|
287
|
+
if (hasAsset) {
|
|
288
|
+
icon.textContent = "\u2713";
|
|
289
|
+
icon.classList.add("brand-asset-card__icon--done");
|
|
290
|
+
} else {
|
|
291
|
+
icon.textContent = "+";
|
|
292
|
+
icon.classList.remove("brand-asset-card__icon--done");
|
|
293
|
+
}
|
|
287
294
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
bvIcon.classList.remove("brand-asset-upload__icon--done");
|
|
295
|
+
// Toggle which action set is visible on hover
|
|
296
|
+
const actions = card.querySelector(".brand-asset-card__actions");
|
|
297
|
+
const manage = card.querySelector(".brand-asset-card__manage");
|
|
298
|
+
if (actions) actions.classList.toggle("hidden", hasAsset);
|
|
299
|
+
if (manage) manage.classList.toggle("hidden", !hasAsset);
|
|
294
300
|
}
|
|
295
301
|
|
|
296
|
-
// Show/hide action buttons based on asset existence
|
|
297
|
-
const sgActions = document.getElementById("brand-actions-styleguide");
|
|
298
|
-
if (sgActions) sgActions.classList.toggle("hidden", !assets.hasStyleguide);
|
|
299
|
-
const bvActions = document.getElementById("brand-actions-brandvoice");
|
|
300
|
-
if (bvActions) bvActions.classList.toggle("hidden", !assets.hasBrandvoice);
|
|
301
|
-
|
|
302
302
|
// Humanify toggle
|
|
303
303
|
const humanifyCheckbox = document.getElementById("humanify-checkbox");
|
|
304
304
|
if (humanifyCheckbox) {
|
|
@@ -306,37 +306,24 @@ function renderBrandAssets(assets) {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
async function
|
|
309
|
+
async function viewBrandAsset(type) {
|
|
310
310
|
try {
|
|
311
311
|
const res = await fetch("/api/brand-assets");
|
|
312
312
|
const data = await res.json();
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
const content = data[type];
|
|
314
|
+
if (content) {
|
|
315
|
+
await vibeViewContent(content, ASSET_LABELS[type], ASSET_FILES[type]);
|
|
315
316
|
} else {
|
|
316
|
-
await vibeAlert(
|
|
317
|
+
await vibeAlert(`No ${ASSET_LABELS[type].toLowerCase()} found.`, "Info");
|
|
317
318
|
}
|
|
318
319
|
} catch (err) {
|
|
319
|
-
await vibeAlert(
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
async function viewBrandvoice() {
|
|
324
|
-
try {
|
|
325
|
-
const res = await fetch("/api/brand-assets");
|
|
326
|
-
const data = await res.json();
|
|
327
|
-
if (data.brandvoice) {
|
|
328
|
-
await vibeViewContent(data.brandvoice, "Brand Voice", "brandvoice.md");
|
|
329
|
-
} else {
|
|
330
|
-
await vibeAlert("No brand voice found.", "Info");
|
|
331
|
-
}
|
|
332
|
-
} catch (err) {
|
|
333
|
-
await vibeAlert("Failed to load brand voice: " + err.message, "Error");
|
|
320
|
+
await vibeAlert(`Failed to load: ${err.message}`, "Error");
|
|
334
321
|
}
|
|
335
322
|
}
|
|
336
323
|
|
|
337
324
|
async function deleteBrandAsset(type) {
|
|
338
|
-
const label = type
|
|
339
|
-
const ok = await vibeConfirm(`Remove ${label}?`, "This will delete the file from disk.", { confirmLabel: "Remove", confirmClass: "btn--danger" });
|
|
325
|
+
const label = ASSET_LABELS[type] || type;
|
|
326
|
+
const ok = await vibeConfirm(`Remove ${label.toLowerCase()}?`, "This will delete the file from disk.", { confirmLabel: "Remove", confirmClass: "btn--danger" });
|
|
340
327
|
if (!ok) return;
|
|
341
328
|
try {
|
|
342
329
|
const res = await fetch("/api/brand-assets", {
|
|
@@ -352,10 +339,57 @@ async function deleteBrandAsset(type) {
|
|
|
352
339
|
}
|
|
353
340
|
}
|
|
354
341
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
342
|
+
async function extractBrandAsset(type, card) {
|
|
343
|
+
const labelEl = card.querySelector(".brand-asset-card__label");
|
|
344
|
+
const origLabel = labelEl?.textContent;
|
|
345
|
+
if (labelEl) labelEl.textContent = "Extracting...";
|
|
346
|
+
card.classList.add("brand-asset-card--extracting");
|
|
347
|
+
try {
|
|
348
|
+
const res = await fetch("/api/brand-assets/extract", {
|
|
349
|
+
method: "POST",
|
|
350
|
+
headers: { "Content-Type": "application/json" },
|
|
351
|
+
body: JSON.stringify({ type }),
|
|
352
|
+
});
|
|
353
|
+
const data = await res.json();
|
|
354
|
+
if (data.ok && data.content) {
|
|
355
|
+
await refreshDashboard();
|
|
356
|
+
const view = await vibeConfirm(
|
|
357
|
+
`${ASSET_LABELS[type]} extracted.`,
|
|
358
|
+
"Would you like to view it?",
|
|
359
|
+
{ confirmLabel: "View", confirmClass: "btn--primary" },
|
|
360
|
+
);
|
|
361
|
+
if (view) await vibeViewContent(data.content, ASSET_LABELS[type], ASSET_FILES[type]);
|
|
362
|
+
} else {
|
|
363
|
+
await vibeAlert(data.error || "Nothing to extract — generate some modules first.", "Info");
|
|
364
|
+
}
|
|
365
|
+
} catch (err) {
|
|
366
|
+
await vibeAlert("Extraction failed: " + err.message, "Error");
|
|
367
|
+
} finally {
|
|
368
|
+
card.classList.remove("brand-asset-card--extracting");
|
|
369
|
+
if (labelEl) labelEl.textContent = origLabel;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Event delegation for brand asset cards
|
|
374
|
+
document.getElementById("dashboard-brand-assets")?.addEventListener("click", (e) => {
|
|
375
|
+
const card = e.target.closest(".brand-asset-card");
|
|
376
|
+
if (!card) return;
|
|
377
|
+
const type = card.dataset.asset;
|
|
378
|
+
if (!type) return;
|
|
379
|
+
|
|
380
|
+
const action = e.target.closest("[data-action]")?.dataset?.action;
|
|
381
|
+
if (action === "view") { viewBrandAsset(type); return; }
|
|
382
|
+
if (action === "delete") { deleteBrandAsset(type); return; }
|
|
383
|
+
if (action === "extract") { extractBrandAsset(type, card); return; }
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// File upload via label inside cards
|
|
387
|
+
document.getElementById("dashboard-brand-assets")?.addEventListener("change", (e) => {
|
|
388
|
+
if (e.target.type !== "file") return;
|
|
389
|
+
const card = e.target.closest(".brand-asset-card");
|
|
390
|
+
if (!card || !e.target.files[0]) return;
|
|
391
|
+
handleBrandFileSelected(card.dataset.asset, e.target.files[0]);
|
|
392
|
+
});
|
|
359
393
|
|
|
360
394
|
// ---------------------------------------------------------------------------
|
|
361
395
|
// Actions
|
|
@@ -541,33 +575,42 @@ document.getElementById("dashboard-deploy-btn").addEventListener("click", () =>
|
|
|
541
575
|
}
|
|
542
576
|
});
|
|
543
577
|
|
|
544
|
-
//
|
|
545
|
-
document.getElementById("
|
|
546
|
-
|
|
547
|
-
});
|
|
548
|
-
document.getElementById("brand-upload-brandvoice").querySelector("input").addEventListener("change", (e) => {
|
|
549
|
-
if (e.target.files[0]) handleBrandFileSelected("brandvoice", e.target.files[0]);
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
// Extract design from theme
|
|
553
|
-
document.getElementById("btn-extract-design")?.addEventListener("click", async () => {
|
|
554
|
-
const btn = document.getElementById("btn-extract-design");
|
|
578
|
+
// Extract All button
|
|
579
|
+
document.getElementById("btn-extract-all")?.addEventListener("click", async () => {
|
|
580
|
+
const btn = document.getElementById("btn-extract-all");
|
|
555
581
|
const origText = btn.textContent;
|
|
556
582
|
btn.textContent = "Extracting...";
|
|
557
583
|
btn.disabled = true;
|
|
558
584
|
|
|
585
|
+
// Mark all cards as extracting
|
|
586
|
+
const cards = document.querySelectorAll(".brand-asset-card");
|
|
587
|
+
const savedLabels = new Map();
|
|
588
|
+
cards.forEach((card) => {
|
|
589
|
+
const labelEl = card.querySelector(".brand-asset-card__label");
|
|
590
|
+
if (labelEl) {
|
|
591
|
+
savedLabels.set(card, labelEl.textContent);
|
|
592
|
+
labelEl.textContent = "Extracting...";
|
|
593
|
+
}
|
|
594
|
+
card.classList.add("brand-asset-card--extracting");
|
|
595
|
+
});
|
|
596
|
+
|
|
559
597
|
try {
|
|
560
598
|
const res = await fetch("/api/brand-assets/extract", {
|
|
561
599
|
method: "POST",
|
|
562
600
|
headers: { "Content-Type": "application/json" },
|
|
563
|
-
body: JSON.stringify({}),
|
|
601
|
+
body: JSON.stringify({ type: "all" }),
|
|
564
602
|
});
|
|
565
603
|
const data = await res.json();
|
|
566
604
|
if (data.ok) {
|
|
567
605
|
await refreshDashboard();
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
606
|
+
const extracted = data.extracted || {};
|
|
607
|
+
const names = Object.entries(extracted)
|
|
608
|
+
.filter(([, v]) => v)
|
|
609
|
+
.map(([k]) => ASSET_LABELS[k] || k);
|
|
610
|
+
if (names.length > 0) {
|
|
611
|
+
await vibeAlert(`Extracted: ${names.join(", ")}`, "Done");
|
|
612
|
+
} else {
|
|
613
|
+
await vibeAlert("Nothing to extract \u2014 generate some modules first.", "Info");
|
|
571
614
|
}
|
|
572
615
|
} else {
|
|
573
616
|
await vibeAlert(data.error || "Extraction failed", "Error");
|
|
@@ -577,6 +620,11 @@ document.getElementById("btn-extract-design")?.addEventListener("click", async (
|
|
|
577
620
|
} finally {
|
|
578
621
|
btn.textContent = origText;
|
|
579
622
|
btn.disabled = false;
|
|
623
|
+
cards.forEach((card) => {
|
|
624
|
+
card.classList.remove("brand-asset-card--extracting");
|
|
625
|
+
const labelEl = card.querySelector(".brand-asset-card__label");
|
|
626
|
+
if (labelEl && savedLabels.has(card)) labelEl.textContent = savedLabels.get(card);
|
|
627
|
+
});
|
|
580
628
|
}
|
|
581
629
|
});
|
|
582
630
|
|
package/ui/index.html
CHANGED
|
@@ -205,29 +205,49 @@
|
|
|
205
205
|
<span class="brand-asset-toggle__label">Humanify</span>
|
|
206
206
|
<span class="brand-asset-toggle__tooltip" data-tooltip="Strips AI-sounding copy: removes em dashes, banned words like 'delve' and 'leverage', cliché openers, and forced enthusiasm. Makes your landing page read like a human wrote it.">?</span>
|
|
207
207
|
</div>
|
|
208
|
-
<div class="brand-asset-
|
|
209
|
-
<
|
|
210
|
-
<
|
|
211
|
-
<span class="brand-asset-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<button class="brand-asset-
|
|
216
|
-
|
|
217
|
-
|
|
208
|
+
<div class="brand-asset-card" data-asset="styleguide">
|
|
209
|
+
<div class="brand-asset-card__base">
|
|
210
|
+
<span class="brand-asset-card__icon" id="brand-icon-styleguide">+</span>
|
|
211
|
+
<span class="brand-asset-card__label">styleguide</span>
|
|
212
|
+
</div>
|
|
213
|
+
<div class="brand-asset-card__actions">
|
|
214
|
+
<label class="brand-asset-card__btn" title="Upload .md/.txt file"><input type="file" accept=".md,.txt" hidden>Upload</label>
|
|
215
|
+
<button class="brand-asset-card__btn" data-action="extract" title="AI-extract from theme">Extract</button>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="brand-asset-card__manage hidden">
|
|
218
|
+
<button class="brand-asset-card__btn" data-action="view" title="View">View</button>
|
|
219
|
+
<button class="brand-asset-card__btn brand-asset-card__btn--danger" data-action="delete" title="Remove">Delete</button>
|
|
220
|
+
</div>
|
|
218
221
|
</div>
|
|
219
|
-
<div class="brand-asset-
|
|
220
|
-
<
|
|
221
|
-
<
|
|
222
|
-
<span class="brand-asset-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
<button class="brand-asset-
|
|
227
|
-
|
|
228
|
-
|
|
222
|
+
<div class="brand-asset-card" data-asset="brandvoice">
|
|
223
|
+
<div class="brand-asset-card__base">
|
|
224
|
+
<span class="brand-asset-card__icon" id="brand-icon-brandvoice">+</span>
|
|
225
|
+
<span class="brand-asset-card__label">brand voice</span>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="brand-asset-card__actions">
|
|
228
|
+
<label class="brand-asset-card__btn" title="Upload .md/.txt file"><input type="file" accept=".md,.txt" hidden>Upload</label>
|
|
229
|
+
<button class="brand-asset-card__btn" data-action="extract" title="AI-extract from page copy">Extract</button>
|
|
230
|
+
</div>
|
|
231
|
+
<div class="brand-asset-card__manage hidden">
|
|
232
|
+
<button class="brand-asset-card__btn" data-action="view" title="View">View</button>
|
|
233
|
+
<button class="brand-asset-card__btn brand-asset-card__btn--danger" data-action="delete" title="Remove">Delete</button>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="brand-asset-card" data-asset="themeContext">
|
|
237
|
+
<div class="brand-asset-card__base">
|
|
238
|
+
<span class="brand-asset-card__icon" id="brand-icon-themeContext">+</span>
|
|
239
|
+
<span class="brand-asset-card__label">product context</span>
|
|
240
|
+
</div>
|
|
241
|
+
<div class="brand-asset-card__actions">
|
|
242
|
+
<label class="brand-asset-card__btn" title="Upload .md/.txt file"><input type="file" accept=".md,.txt" hidden>Upload</label>
|
|
243
|
+
<button class="brand-asset-card__btn" data-action="extract" title="AI-extract from page content">Extract</button>
|
|
244
|
+
</div>
|
|
245
|
+
<div class="brand-asset-card__manage hidden">
|
|
246
|
+
<button class="brand-asset-card__btn" data-action="view" title="View">View</button>
|
|
247
|
+
<button class="brand-asset-card__btn brand-asset-card__btn--danger" data-action="delete" title="Remove">Delete</button>
|
|
248
|
+
</div>
|
|
229
249
|
</div>
|
|
230
|
-
<button class="btn btn--sm btn--outline" id="btn-extract-
|
|
250
|
+
<button class="btn btn--sm btn--outline" id="btn-extract-all" title="AI-extract all brand assets from this theme">Extract All</button>
|
|
231
251
|
<button class="btn btn--sm btn--outline" id="btn-import-reference" title="Import design from another HubSpot theme or local folder">Import Reference</button>
|
|
232
252
|
</div>
|
|
233
253
|
</section>
|
package/ui/styles.css
CHANGED
|
@@ -807,69 +807,90 @@ body { display: flex; }
|
|
|
807
807
|
gap: 12px;
|
|
808
808
|
}
|
|
809
809
|
|
|
810
|
-
|
|
810
|
+
/* Brand asset cards — ghost buttons with hover-expand actions */
|
|
811
|
+
.brand-asset-card {
|
|
811
812
|
display: flex;
|
|
812
|
-
align-items:
|
|
813
|
-
gap: 8px;
|
|
814
|
-
padding: 10px 16px;
|
|
813
|
+
align-items: stretch;
|
|
815
814
|
border: 1px dashed var(--border);
|
|
816
815
|
border-radius: var(--radius);
|
|
817
|
-
|
|
818
|
-
transition: border-color 0.15s
|
|
816
|
+
overflow: hidden;
|
|
817
|
+
transition: border-color 0.15s;
|
|
819
818
|
}
|
|
820
|
-
|
|
821
|
-
.brand-asset-upload:hover {
|
|
819
|
+
.brand-asset-card:hover {
|
|
822
820
|
border-color: var(--accent);
|
|
823
821
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
822
|
+
.brand-asset-card__base {
|
|
823
|
+
display: flex;
|
|
824
|
+
align-items: center;
|
|
825
|
+
gap: 8px;
|
|
826
|
+
padding: 8px 14px;
|
|
827
|
+
}
|
|
828
|
+
.brand-asset-card__icon {
|
|
829
|
+
width: 22px;
|
|
830
|
+
height: 22px;
|
|
828
831
|
display: flex;
|
|
829
832
|
align-items: center;
|
|
830
833
|
justify-content: center;
|
|
831
834
|
border-radius: 50%;
|
|
832
835
|
background: var(--bg-hover);
|
|
833
836
|
color: var(--text-dim);
|
|
834
|
-
font-size:
|
|
837
|
+
font-size: 13px;
|
|
835
838
|
font-weight: 500;
|
|
839
|
+
flex-shrink: 0;
|
|
836
840
|
}
|
|
837
|
-
|
|
838
|
-
.brand-asset-upload__icon--done {
|
|
841
|
+
.brand-asset-card__icon--done {
|
|
839
842
|
background: rgba(74,222,128,0.15);
|
|
840
843
|
color: var(--success);
|
|
841
844
|
}
|
|
845
|
+
.brand-asset-card__label {
|
|
846
|
+
font-size: 13px;
|
|
847
|
+
color: var(--text);
|
|
848
|
+
white-space: nowrap;
|
|
849
|
+
}
|
|
842
850
|
|
|
843
|
-
|
|
851
|
+
/* Slide-in actions on hover */
|
|
852
|
+
.brand-asset-card__actions,
|
|
853
|
+
.brand-asset-card__manage {
|
|
844
854
|
display: flex;
|
|
845
|
-
|
|
846
|
-
|
|
855
|
+
max-width: 0;
|
|
856
|
+
overflow: hidden;
|
|
857
|
+
opacity: 0;
|
|
858
|
+
transition: max-width 0.2s ease, opacity 0.15s ease;
|
|
847
859
|
}
|
|
848
|
-
.brand-asset-
|
|
849
|
-
|
|
850
|
-
|
|
860
|
+
.brand-asset-card:hover > .brand-asset-card__actions:not(.hidden),
|
|
861
|
+
.brand-asset-card:hover > .brand-asset-card__manage:not(.hidden) {
|
|
862
|
+
max-width: 180px;
|
|
863
|
+
opacity: 1;
|
|
851
864
|
}
|
|
852
|
-
|
|
853
|
-
|
|
865
|
+
|
|
866
|
+
.brand-asset-card__btn {
|
|
867
|
+
display: flex;
|
|
868
|
+
align-items: center;
|
|
869
|
+
padding: 6px 10px;
|
|
870
|
+
font-size: 12px;
|
|
871
|
+
font-weight: 500;
|
|
854
872
|
border: none;
|
|
855
|
-
|
|
873
|
+
border-left: 1px solid var(--border);
|
|
874
|
+
background: var(--bg-hover);
|
|
875
|
+
color: var(--text-dim);
|
|
856
876
|
cursor: pointer;
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
border-radius: var(--radius-sm);
|
|
860
|
-
transition: color 0.15s, background 0.15s;
|
|
877
|
+
white-space: nowrap;
|
|
878
|
+
transition: background 0.12s, color 0.12s;
|
|
861
879
|
}
|
|
862
|
-
.brand-asset-
|
|
880
|
+
.brand-asset-card__btn:hover {
|
|
881
|
+
background: var(--bg-active);
|
|
863
882
|
color: var(--accent);
|
|
864
|
-
background: var(--bg-hover);
|
|
865
883
|
}
|
|
866
|
-
.brand-asset-
|
|
884
|
+
.brand-asset-card__btn--danger:hover {
|
|
867
885
|
color: var(--error);
|
|
868
886
|
}
|
|
887
|
+
/* Hide file input inside label */
|
|
888
|
+
.brand-asset-card__btn input[type="file"] { display: none; }
|
|
869
889
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
890
|
+
/* Extracting state */
|
|
891
|
+
.brand-asset-card--extracting {
|
|
892
|
+
opacity: 0.6;
|
|
893
|
+
pointer-events: none;
|
|
873
894
|
}
|
|
874
895
|
|
|
875
896
|
/* Back button in dashboard topbar */
|