Design a fully working, production-grade inline Context Selection feature inside the existing Superfuel Workflow page (dark theme). The page must match the screenshot exactly: left sidebar, a header with the workflow title “Add Competitor Keywords to the Bullet Points,” and a numbered vertical list of workflow steps. Add a fully functional Select Context button on Step 1 (“Get ASIN from Context”) that expands an inline Context Selection Panel beneath Step 1 (not a separate page or full-screen drawer). The panel must support three tabs: Manual Input, Search & Filters, Saved Contexts. Use the Superfuel x shadcn style: Inter font, 8px grid, dark palette (background #0B0C0F, panel #131417, borders #2B2C2F, text #E6E6E6, secondary text #A0A3A8, accent #2563EB), 8px radii, subtle shadows, and 150ms ease motion. All copy must match the tone: concise, factual, and technical.
Browse filesBehavioral requirements (must be implemented in the prototype):
• Clicking Select Context expands the inline panel with a slide-down animation and traps focus inside. • The panel contains three tabs: Manual Input, Search & Filters, Saved Contexts. State must persist between tab switches (local temp state). • Manual Input: multiline textarea accepts comma/space/newline separated ASINs or CSV upload. Clicking Parse & Preview validates ASINs with regex [A-Z0-9]{10}, deduplicates, and calls a mocked endpoint POST /api/products/validate-asins which returns valid/invalid lists and sample product details. The preview shows first 50 products in a scrollable grid of cards (image 64x64, title 2 lines, ASIN monospace, metric chips Sales(30d)/CVR/AdClicks). Each card has checkbox for include/exclude. Provide Select All / Deselect All. If valid count > 50 show “Preview shows first 50 of N valid products” and a “Select all matching (N)” toggle. • Search & Filters: a command-style search input (debounced 300ms) calls GET /api/products/search?q= and renders dropdown suggestions; allow picking multiple results. Provide Quick Filter chips: Top 10% by Sales (30d), Bottom 5% CVR (30d), Sales = 0 (30d); clicking a chip calls POST /api/products/preview-by-filter and returns count + sample. Provide a compact Custom Filter Builder (Metric, Condition, Value, Time range) allowing multiple rows with AND/OR; “Preview Matching Products” button calls preview endpoint. • Saved Contexts: GET /api/contexts returns saved contexts. Each card shows name, count, last used, thumbnail cluster and actions Apply/Edit/Delete. Applying closes panel and updates Step 1 subtitle. • Footer inside panel: sticky summary left (“Selected: X products” or “All matching — N products”) and right: Save as Context checkbox + name input (when checked), Cancel, and Apply Selection CTA. • Safety: if selected count > 500 show inline soft warning. If > 5,000 require modal that asks user to type CONFIRM before Apply continues. • On Apply Selection: if preview-only, POST /api/workflows/:id/apply-context with explicit ASIN list; if all-matching, send filterDef and estimatedCount. Panel collapses and Step 1 subtitle updates to: e.g., “Context: 142 products selected (Saved as ‘Top Sellers 30D’)”. Show toast success. • Accessibility: keyboard navigation, ESC to close, ARIA live region announces parse/preview counts, focus management, and visible focus ring (#2563EB). • All endpoints are mocked in the prototype with example JSON provided below. Provide skeleton loading states, invalid ASIN UI, CSV upload errors, and “no matches” states.
Screens to generate and wire:
1. Workflow page default (Select Context button visible)
2. Panel expanded (Search & Filters default or Saved Contexts if user has saved)
3. Manual Input pre-parse and post-parse preview state (valid/invalid details)
4. Search & Filters: quick chip preview and custom builder preview
5. Saved Contexts list and empty states
6. Product preview grid with selection interactions
7. Sticky footer normal / soft warning / hard confirmation modal
8. After apply: Step 1 collapsed showing updated subtitle + edit icon
9. Error states: CSV missing ASIN, network timeout, zero matches
Prototype must be interactive: expand/collapse, tab switching, textarea parsing, CSV upload simulation, search suggestions, preview card selection, select-all toggle, save context flow, safety modal, apply selection (mocked API call), and success toast. Add keyboard flows and ARIA attributes. Use realistic sample data for ~50 products. Export generated screens and provide a shareable prototype link.
⸻
2) Mock API responses & sample data (copy into your prototype mocking layer)
Use this JSON for endpoints. Keep it locally as /mock-data/products.json and wire the endpoints to return appropriate parts.
sample products.json (50 items, truncated example for brevity; include more in implementation): [
{"asin":"B08PP5MSVB","title":"Runner Pro Shoe - Blue","image":"https://via.placeholder.com/64","metrics":{"sales_30":1423,"cvr_30":0.18,"clicks_30":921}},
{"asin":"B09XYZ1234","title":"Trail Grip Shoe - Black","image":"https://via.placeholder.com/64","metrics":{"sales_30":543,"cvr_30":0.12,"clicks_30":320}},
{"asin":"B07ABC9876","title":"Daily Comfort Sneaker","image":"https://via.placeholder.com/64","metrics":{"sales_30":12,"cvr_30":0.02,"clicks_30":4}},
...
]POST /api/products/validate-asins request: { asins: ["B08PP5MSVB","B09XYZ1234",...] }
response:{ "valid": [{"asin":"B08PP5MSVB","title":"Runner Pro Shoe - Blue","image":"...","metrics":{...}}, ...],
"invalid": [{"asin":"B000XXX","reason":"Malformed ASIN"}, {"asin":"B09NOTINCAT","reason":"Not in your catalog"}],
"total_valid_count": 1320
}GET /api/products/search?q=shoe&page=1
response:{ "total": 234, "sample": [ {asin, title, image, metrics}, ... ] }POST /api/products/preview-by-filter request: { filterDef: { rules:[{metric:"sales", op:"top_percent", value:10, time:30}], logic:"AND" } }
response:GET /api/contexts[{ "id":"CTX_1","name":"Top Sellers 30D","count":423,"updated":"2025-10-12","thumbs":["...","...","..."], "type":"filter","payload":{...} }]POST /api/workflows/WF_1/apply-context request either:
• preview-only: { type:"manual", asins:[...], count: N }
• filter: { type:"filter", filterDef:{...}, estimatedCount: N }
response: { status:"ok", appliedCount: N }
⸻
3) Next.js + shadcn prototype scaffold (quick dev plan)
Files & components to implement (summary):
• /pages/workflow/[id].tsx — renders existing workflow page, steps, and Select Context button.
• /components/ContextPanel/ContextPanel.tsx — the inline panel and tab manager.
• /components/ContextPanel/ManualTab.tsx — textarea, CSV upload, parse logic (client chunking), preview grid.
• /components/ContextPanel/FilterTab.tsx — search, quick chips, custom builder, preview call.
• /components/ContextPanel/SavedTab.tsx — list and apply behavior.
• /components/ProductCard.tsx — preview card states (selected, hover, disabled).
• /lib/mockApi.ts — simple mocked endpoints using local JSON and timeouts to simulate network.
• /styles/theme.ts — tokens for the dark palette and shadcn integration.
Important implementation notes:
• Use React Query to drive mock API calls and handle loading/error states.
• For CSV parsing, use PapaParse and chunk into 100 ASIN groups for validation.
• For “Select all matching,” do not fetch all ASINs — send only filterDef to server on apply.
• Use local state to store temporary selection; on Apply, clear temp state and update workflow step metadata.
• Implement the safety modal logic on the client before calling apply API.
Mock server behavior:
• For preview endpoints, return sample of up to 50 products and total estimating the real count.
• For validate-asins, return valid and invalid lists, with total_valid_count representing full size (simulate uploaded large lists).
Deployment:
• Run locally: npm run dev. Provide README with commands.
• Include a static JSON file with 50–200 products for demo.
⸻
4) Figma frames + wiring map (for DeepSite → Figma handoff)
Frame names:
• WF_SelectContext_Default
• WF_SelectContext_Panel_Open
• WF_Manual_PreParse
• WF_Manual_PostParse
• WF_Filter_Chips
• WF_Filter_CustomBuilder
• WF_SavedContexts_Empty
• WF_SavedContexts_List
• WF_Preview_Grid
• WF_Footer_Normal
• WF_Footer_Warning
• WF_Modal_SoftConfirm
• WF_Modal_HardConfirm
• WF_Applied_Summary
Component naming:
• SC.Step.Row / SC.Button.SelectContext / SC.Panel.Header / SC.Tabs / SC.Tab.Manual / SC.Card.ProductPreview / SC.Footer.SelectionBar / SC.Modal.Confirm
Prototype wiring:
• SelectContext button → animate open WF_SelectContext_Panel_Open
• Tab switches → reveal corresponding frames
• Parse → transition to WF_Manual_PostParse state (mocked)
• Quick chip click → WF_Filter_Chips → show WF_Preview_Grid
• Click Apply → run modal if thresholds, then collapse to WF_Applied_Summary
Include micro-interactions: hover lifts, focus ring outline, toast messages.
⸻
5) Acceptance Criteria & QA checklist
Critical behaviors to verify in prototype:
• Clicking Select Context expands inline panel (animation, focus trap).
• Tab switching preserves input state.
• Manual parse validates ASINs, dedupes, lists invalid with reasons.
• Preview grid shows up to 50 items, checkboxes toggle individually and Select All works.
• Search dropdown returns suggestions and selecting them marks preview selection.
• Quick filter chips call preview endpoint and display sample + total.
• Select All Matching toggle sets selection scope and shows warning for >500.
• Save as Context works (mock POST), saved contexts appear in Saved tab.
• Apply Selection either sends asins or filterDef correctly and collapses panel.
• Safety modals show soft/hard confirmations at correct thresholds and block apply until confirmed.
• Keyboard navigation and ARIA live region announce counts.
• Edge cases: CSV missing ASIN, network timeout, no matches, >10k ASIN upload handled.
Testing flows:
• Manual small list (5 ASINs) → parse → preview → apply.
• Manu
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
title: Context Selector Pro Flow
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: Context Selector Pro Flow
|
| 3 |
+
colorFrom: blue
|
| 4 |
+
colorTo: red
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
|
@@ -1,19 +1,291 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="dark">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Superfuel | Add Competitor Keywords Workflow</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 10 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 11 |
+
</head>
|
| 12 |
+
<body class="bg-[#0B0C0F] text-[#E6E6E6] font-inter">
|
| 13 |
+
<div class="flex h-screen">
|
| 14 |
+
<!-- Left Sidebar -->
|
| 15 |
+
<div class="w-64 bg-[#131417] border-r border-[#2B2C2F] p-4">
|
| 16 |
+
<div class="flex items-center mb-8">
|
| 17 |
+
<i data-feather="zap" class="text-[#2563EB] mr-2"></i>
|
| 18 |
+
<h2 class="font-semibold">Superfuel</h2>
|
| 19 |
+
</div>
|
| 20 |
+
<nav>
|
| 21 |
+
<ul class="space-y-2">
|
| 22 |
+
<li class="flex items-center p-2 rounded hover:bg-[#1E1F23]">
|
| 23 |
+
<i data-feather="home" class="w-4 h-4 mr-2 text-[#A0A3A8]"></i>
|
| 24 |
+
<span>Dashboard</span>
|
| 25 |
+
</li>
|
| 26 |
+
<li class="flex items-center p-2 rounded bg-[#1E1F23]">
|
| 27 |
+
<i data-feather="layers" class="w-4 h-4 mr-2 text-[#2563EB]"></i>
|
| 28 |
+
<span class="font-medium">Workflows</span>
|
| 29 |
+
</li>
|
| 30 |
+
<li class="flex items-center p-2 rounded hover:bg-[#1E1F23]">
|
| 31 |
+
<i data-feather="database" class="w-4 h-4 mr-2 text-[#A0A3A8]"></i>
|
| 32 |
+
<span>Data Sources</span>
|
| 33 |
+
</li>
|
| 34 |
+
</ul>
|
| 35 |
+
</nav>
|
| 36 |
+
</div>
|
| 37 |
+
|
| 38 |
+
<!-- Main Content -->
|
| 39 |
+
<div class="flex-1 overflow-auto">
|
| 40 |
+
<header class="border-b border-[#2B2C2F] p-4">
|
| 41 |
+
<h1 class="text-xl font-semibold">Add Competitor Keywords to the Bullet Points</h1>
|
| 42 |
+
</header>
|
| 43 |
+
|
| 44 |
+
<main class="p-6 max-w-4xl mx-auto">
|
| 45 |
+
<div class="space-y-6">
|
| 46 |
+
<!-- Step 1 -->
|
| 47 |
+
<div class="bg-[#131417] rounded-lg border border-[#2B2C2F] overflow-hidden">
|
| 48 |
+
<div class="p-4 flex items-start">
|
| 49 |
+
<div class="bg-[#2563EB] text-white rounded-full w-6 h-6 flex items-center justify-center mr-3 mt-1 flex-shrink-0">1</div>
|
| 50 |
+
<div class="flex-1">
|
| 51 |
+
<div class="flex justify-between items-start">
|
| 52 |
+
<div>
|
| 53 |
+
<h3 class="font-medium">Get ASIN from Context</h3>
|
| 54 |
+
<p class="text-sm text-[#A0A3A8] mt-1">Context: Not selected</p>
|
| 55 |
+
</div>
|
| 56 |
+
<button id="selectContextBtn" class="bg-[#2563EB] hover:bg-[#1E55D5] text-white px-3 py-1.5 rounded-md text-sm font-medium transition-colors duration-150 ease-in-out">
|
| 57 |
+
Select Context
|
| 58 |
+
</button>
|
| 59 |
+
</div>
|
| 60 |
+
|
| 61 |
+
<!-- Context Selection Panel (Initially hidden) -->
|
| 62 |
+
<div id="contextPanel" class="mt-4 hidden">
|
| 63 |
+
<div class="bg-[#1A1C21] rounded-lg border border-[#2B2C2F] overflow-hidden">
|
| 64 |
+
<!-- Tabs -->
|
| 65 |
+
<div class="flex border-b border-[#2B2C2F]">
|
| 66 |
+
<button data-tab="manual" class="tab-btn px-4 py-3 font-medium text-sm border-b-2 border-transparent hover:bg-[#22242A] active-tab">
|
| 67 |
+
Manual Input
|
| 68 |
+
</button>
|
| 69 |
+
<button data-tab="search" class="tab-btn px-4 py-3 font-medium text-sm border-b-2 border-transparent hover:bg-[#22242A]">
|
| 70 |
+
Search & Filters
|
| 71 |
+
</button>
|
| 72 |
+
<button data-tab="saved" class="tab-btn px-4 py-3 font-medium text-sm border-b-2 border-transparent hover:bg-[#22242A]">
|
| 73 |
+
Saved Contexts
|
| 74 |
+
</button>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
<!-- Tab Content -->
|
| 78 |
+
<div class="p-4">
|
| 79 |
+
<!-- Manual Input Tab -->
|
| 80 |
+
<div id="manualTab" class="tab-content">
|
| 81 |
+
<div class="space-y-4">
|
| 82 |
+
<div>
|
| 83 |
+
<label class="block text-sm font-medium mb-1">Enter ASINs (comma, space, or newline separated)</label>
|
| 84 |
+
<textarea id="asinInput" class="w-full bg-[#1E1F23] border border-[#2B2C2F] rounded-md p-2 text-sm h-32" placeholder="B08PP5MSVB, B09XYZ1234..."></textarea>
|
| 85 |
+
</div>
|
| 86 |
+
<div class="flex items-center">
|
| 87 |
+
<span class="text-sm text-[#A0A3A8]">or</span>
|
| 88 |
+
<button class="ml-2 text-sm text-[#2563EB] hover:underline">Upload CSV file</button>
|
| 89 |
+
</div>
|
| 90 |
+
<button id="parseBtn" class="bg-[#2563EB] hover:bg-[#1E55D5] text-white px-3 py-1.5 rounded-md text-sm font-medium">
|
| 91 |
+
Parse & Preview
|
| 92 |
+
</button>
|
| 93 |
+
</div>
|
| 94 |
+
|
| 95 |
+
<!-- Preview Section (Initially hidden) -->
|
| 96 |
+
<div id="previewSection" class="mt-4 hidden">
|
| 97 |
+
<div class="flex justify-between items-center mb-3">
|
| 98 |
+
<h4 class="font-medium">Preview (50 of <span id="totalValidCount">0</span> valid products)</h4>
|
| 99 |
+
<div class="flex items-center space-x-2">
|
| 100 |
+
<button id="selectAllBtn" class="text-sm text-[#2563EB] hover:underline">Select All</button>
|
| 101 |
+
<button id="deselectAllBtn" class="text-sm text-[#2563EB] hover:underline">Deselect All</button>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
|
| 105 |
+
<div id="previewGrid" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 max-h-96 overflow-y-auto">
|
| 106 |
+
<!-- Product cards will be inserted here -->
|
| 107 |
+
</div>
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
+
<!-- Search & Filters Tab (Initially hidden) -->
|
| 112 |
+
<div id="searchTab" class="tab-content hidden">
|
| 113 |
+
<div class="space-y-4">
|
| 114 |
+
<div>
|
| 115 |
+
<label class="block text-sm font-medium mb-1">Search products</label>
|
| 116 |
+
<div class="relative">
|
| 117 |
+
<input type="text" class="w-full bg-[#1E1F23] border border-[#2B2C2F] rounded-md p-2 text-sm" placeholder="Search by title, brand, or ASIN...">
|
| 118 |
+
<i data-feather="search" class="absolute right-2 top-2.5 text-[#A0A3A8] w-4 h-4"></i>
|
| 119 |
+
</div>
|
| 120 |
+
</div>
|
| 121 |
+
|
| 122 |
+
<div>
|
| 123 |
+
<h4 class="text-sm font-medium mb-2">Quick Filters</h4>
|
| 124 |
+
<div class="flex flex-wrap gap-2">
|
| 125 |
+
<button class="chip-btn bg-[#1E1F23] hover:bg-[#22242A] px-3 py-1 rounded-full text-sm border border-[#2B2C2F]">
|
| 126 |
+
Top 10% by Sales (30d)
|
| 127 |
+
</button>
|
| 128 |
+
<button class="chip-btn bg-[#1E1F23] hover:bg-[#22242A] px-3 py-1 rounded-full text-sm border border-[#2B2C2F]">
|
| 129 |
+
Bottom 5% CVR (30d)
|
| 130 |
+
</button>
|
| 131 |
+
<button class="chip-btn bg-[#1E1F23] hover:bg-[#22242A] px-3 py-1 rounded-full text-sm border border-[#2B2C2F]">
|
| 132 |
+
Sales = 0 (30d)
|
| 133 |
+
</button>
|
| 134 |
+
</div>
|
| 135 |
+
</div>
|
| 136 |
+
|
| 137 |
+
<div>
|
| 138 |
+
<h4 class="text-sm font-medium mb-2">Custom Filters</h4>
|
| 139 |
+
<div class="bg-[#1E1F23] border border-[#2B2C2F] rounded-md p-3 space-y-3">
|
| 140 |
+
<div class="flex items-center space-x-2">
|
| 141 |
+
<select class="bg-[#131417] border border-[#2B2C2F] rounded-md p-1.5 text-sm flex-1">
|
| 142 |
+
<option>Sales (30d)</option>
|
| 143 |
+
<option>CVR (30d)</option>
|
| 144 |
+
<option>Ad Clicks (30d)</option>
|
| 145 |
+
</select>
|
| 146 |
+
<select class="bg-[#131417] border border-[#2B2C2F] rounded-md p-1.5 text-sm w-24">
|
| 147 |
+
<option>=</option>
|
| 148 |
+
<option>></option>
|
| 149 |
+
<option><</option>
|
| 150 |
+
<option>Top %</option>
|
| 151 |
+
<option>Bottom %</option>
|
| 152 |
+
</select>
|
| 153 |
+
<input type="text" class="bg-[#131417] border border-[#2B2C2F] rounded-md p-1.5 text-sm w-16">
|
| 154 |
+
<button class="text-[#A0A3A8] hover:text-[#E6E6E6]">
|
| 155 |
+
<i data-feather="trash-2" class="w-4 h-4"></i>
|
| 156 |
+
</button>
|
| 157 |
+
</div>
|
| 158 |
+
<button class="text-sm text-[#2563EB] hover:underline flex items-center">
|
| 159 |
+
<i data-feather="plus" class="w-4 h-4 mr-1"></i> Add Condition
|
| 160 |
+
</button>
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
<button class="bg-[#2563EB] hover:bg-[#1E55D5] text-white px-3 py-1.5 rounded-md text-sm font-medium mt-2">
|
| 165 |
+
Preview Matching Products
|
| 166 |
+
</button>
|
| 167 |
+
</div>
|
| 168 |
+
</div>
|
| 169 |
+
|
| 170 |
+
<!-- Saved Contexts Tab (Initially hidden) -->
|
| 171 |
+
<div id="savedTab" class="tab-content hidden">
|
| 172 |
+
<div class="space-y-3">
|
| 173 |
+
<p class="text-sm text-[#A0A3A8]">No saved contexts yet. Create one by saving a selection.</p>
|
| 174 |
+
|
| 175 |
+
<!-- Example saved context card -->
|
| 176 |
+
<div class="bg-[#1E1F23] border border-[#2B2C2F] rounded-md p-3 hidden">
|
| 177 |
+
<div class="flex justify-between">
|
| 178 |
+
<div>
|
| 179 |
+
<h4 class="font-medium">Top Sellers 30D</h4>
|
| 180 |
+
<p class="text-sm text-[#A0A3A8]">423 products • Last used Oct 12, 2025</p>
|
| 181 |
+
</div>
|
| 182 |
+
<div class="flex space-x-2">
|
| 183 |
+
<button class="text-[#A0A3A8] hover:text-[#E6E6E6]">
|
| 184 |
+
<i data-feather="edit-2" class="w-4 h-4"></i>
|
| 185 |
+
</button>
|
| 186 |
+
<button class="text-[#A0A3A8] hover:text-[#E6E6E6]">
|
| 187 |
+
<i data-feather="trash-2" class="w-4 h-4"></i>
|
| 188 |
+
</button>
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
<div class="flex mt-2 space-x-1">
|
| 192 |
+
<div class="w-8 h-8 bg-[#2B2C2F] rounded"></div>
|
| 193 |
+
<div class="w-8 h-8 bg-[#2B2C2F] rounded"></div>
|
| 194 |
+
<div class="w-8 h-8 bg-[#2B2C2F] rounded"></div>
|
| 195 |
+
</div>
|
| 196 |
+
<button class="mt-3 w-full bg-[#2563EB] hover:bg-[#1E55D5] text-white px-3 py-1.5 rounded-md text-sm font-medium">
|
| 197 |
+
Apply
|
| 198 |
+
</button>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
|
| 204 |
+
<!-- Footer -->
|
| 205 |
+
<div class="bg-[#1A1C21] border-t border-[#2B2C2F] p-3 sticky bottom-0">
|
| 206 |
+
<div class="flex justify-between items-center">
|
| 207 |
+
<div class="text-sm">
|
| 208 |
+
<span id="selectedCountText">Selected: 0 products</span>
|
| 209 |
+
</div>
|
| 210 |
+
<div class="flex items-center space-x-3">
|
| 211 |
+
<div class="flex items-center">
|
| 212 |
+
<input type="checkbox" id="saveContextCheckbox" class="mr-2">
|
| 213 |
+
<label for="saveContextCheckbox" class="text-sm">Save as</label>
|
| 214 |
+
<input type="text" id="contextNameInput" class="ml-2 bg-[#1E1F23] border border-[#2B2C2F] rounded-md p-1 text-sm w-40 hidden">
|
| 215 |
+
</div>
|
| 216 |
+
<button class="text-sm px-3 py-1.5 rounded-md border border-[#2B2C2F] hover:bg-[#22242A]">
|
| 217 |
+
Cancel
|
| 218 |
+
</button>
|
| 219 |
+
<button id="applySelectionBtn" class="bg-[#2563EB] hover:bg-[#1E55D5] text-white px-3 py-1.5 rounded-md text-sm font-medium">
|
| 220 |
+
Apply Selection
|
| 221 |
+
</button>
|
| 222 |
+
</div>
|
| 223 |
+
</div>
|
| 224 |
+
</div>
|
| 225 |
+
</div>
|
| 226 |
+
</div>
|
| 227 |
+
</div>
|
| 228 |
+
</div>
|
| 229 |
+
</div>
|
| 230 |
+
|
| 231 |
+
<!-- Step 2 -->
|
| 232 |
+
<div class="bg-[#131417] rounded-lg border border-[#2B2C2F]">
|
| 233 |
+
<div class="p-4 flex items-start">
|
| 234 |
+
<div class="bg-[#2B2C2F] text-[#A0A3A8] rounded-full w-6 h-6 flex items-center justify-center mr-3 mt-1 flex-shrink-0">2</div>
|
| 235 |
+
<div>
|
| 236 |
+
<h3 class="font-medium">Extract competitor bullet points</h3>
|
| 237 |
+
<p class="text-sm text-[#A0A3A8] mt-1">Configure extraction settings</p>
|
| 238 |
+
</div>
|
| 239 |
+
</div>
|
| 240 |
+
</div>
|
| 241 |
+
|
| 242 |
+
<!-- Step 3 -->
|
| 243 |
+
<div class="bg-[#131417] rounded-lg border border-[#2B2C2F]">
|
| 244 |
+
<div class="p-4 flex items-start">
|
| 245 |
+
<div class="bg-[#2B2C2F] text-[#A0A3A8] rounded-full w-6 h-6 flex items-center justify-center mr-3 mt-1 flex-shrink-0">3</div>
|
| 246 |
+
<div>
|
| 247 |
+
<h3 class="font-medium">Add keywords to your bullet points</h3>
|
| 248 |
+
<p class="text-sm text-[#A0A3A8] mt-1">Select target ASINs and configure insertion</p>
|
| 249 |
+
</div>
|
| 250 |
+
</div>
|
| 251 |
+
</div>
|
| 252 |
+
</div>
|
| 253 |
+
</main>
|
| 254 |
+
</div>
|
| 255 |
+
</div>
|
| 256 |
+
|
| 257 |
+
<!-- Toast Notification (Hidden by default) -->
|
| 258 |
+
<div id="toast" class="fixed bottom-4 right-4 bg-[#131417] border border-[#2B2C2F] rounded-md p-3 shadow-lg hidden">
|
| 259 |
+
<div class="flex items-center">
|
| 260 |
+
<i data-feather="check-circle" class="text-green-500 mr-2"></i>
|
| 261 |
+
<span id="toastMessage">Context applied successfully</span>
|
| 262 |
+
</div>
|
| 263 |
+
</div>
|
| 264 |
+
|
| 265 |
+
<!-- Confirmation Modal (Hidden by default) -->
|
| 266 |
+
<div id="confirmModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
|
| 267 |
+
<div class="bg-[#131417] rounded-lg border border-[#2B2C2F] p-6 max-w-md w-full">
|
| 268 |
+
<h3 class="font-medium text-lg mb-3">Confirm Large Selection</h3>
|
| 269 |
+
<p class="text-sm text-[#A0A3A8] mb-4">You're about to apply a selection of <span id="modalCount">5,000</span> products. This may take a while to process.</p>
|
| 270 |
+
<div class="mb-4">
|
| 271 |
+
<label class="block text-sm font-medium mb-1">Type "CONFIRM" to continue</label>
|
| 272 |
+
<input type="text" id="confirmInput" class="w-full bg-[#1E1F23] border border-[#2B2C2F] rounded-md p-2 text-sm">
|
| 273 |
+
</div>
|
| 274 |
+
<div class="flex justify-end space-x-3">
|
| 275 |
+
<button id="cancelConfirmBtn" class="text-sm px-3 py-1.5 rounded-md border border-[#2B2C2F] hover:bg-[#22242A]">
|
| 276 |
+
Cancel
|
| 277 |
+
</button>
|
| 278 |
+
<button id="proceedConfirmBtn" class="bg-[#2563EB] hover:bg-[#1E55D5] text-white px-3 py-1.5 rounded-md text-sm font-medium disabled:opacity-50" disabled>
|
| 279 |
+
Proceed
|
| 280 |
+
</button>
|
| 281 |
+
</div>
|
| 282 |
+
</div>
|
| 283 |
+
</div>
|
| 284 |
+
|
| 285 |
+
<script src="script.js"></script>
|
| 286 |
+
<script>
|
| 287 |
+
feather.replace();
|
| 288 |
+
</script>
|
| 289 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 290 |
+
</body>
|
| 291 |
+
</html>
|
|
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 2 |
+
// Mock product data
|
| 3 |
+
const mockProducts = [
|
| 4 |
+
{"asin":"B08PP5MSVB","title":"Runner Pro Shoe - Blue","image":"https://via.placeholder.com/64","metrics":{"sales_30":1423,"cvr_30":0.18,"clicks_30":921}},
|
| 5 |
+
{"asin":"B09XYZ1234","title":"Trail Grip Shoe - Black","image":"https://via.placeholder.com/64","metrics":{"sales_30":543,"cvr_30":0.12,"clicks_30":320}},
|
| 6 |
+
{"asin":"B07ABC9876","title":"Daily Comfort Sneaker","image":"https://via.placeholder.com/64","metrics":{"sales_30":12,"cvr_30":0.02,"clicks_30":4}},
|
| 7 |
+
{"asin":"B0A1B2C3D4","title":"Performance Running Shorts","image":"https://via.placeholder.com/64","metrics":{"sales_30":876,"cvr_30":0.15,"clicks_30":421}},
|
| 8 |
+
{"asin":"B0D5E6F7G8","title":"Athletic Compression Socks","image":"https://via.placeholder.com/64","metrics":{"sales_30":321,"cvr_30":0.08,"clicks_30":154}},
|
| 9 |
+
{"asin":"B0H9I8J7K6","title":"Yoga Mat - Extra Thick","image":"https://via.placeholder.com/64","metrics":{"sales_30":654,"cvr_30":0.22,"clicks_30":298}},
|
| 10 |
+
{"asin":"B0L1M2N3O4","title":"Adjustable Dumbbell Set","image":"https://via.placeholder.com/64","metrics":{"sales_30":432,"cvr_30":0.14,"clicks_30":187}},
|
| 11 |
+
{"asin":"B0P5Q6R7S8","title":"Foam Roller - High Density","image":"https://via.placeholder.com/64","metrics":{"sales_30":189,"cvr_30":0.07,"clicks_30":92}},
|
| 12 |
+
{"asin":"B0T9U8V7W6","title":"Resistance Bands Set","image":"https://via.placeholder.com/64","metrics":{"sales_30":567,"cvr_30":0.19,"clicks_30":301}},
|
| 13 |
+
{"asin":"B0X1Y2Z3A4","title":"Jump Rope - Weighted","image":"https://via.placeholder.com/64","metrics":{"sales_30":234,"cvr_30":0.11,"clicks_30":123}}
|
| 14 |
+
];
|
| 15 |
+
|
| 16 |
+
// DOM Elements
|
| 17 |
+
const selectContextBtn = document.getElementById('selectContextBtn');
|
| 18 |
+
const contextPanel = document.getElementById('contextPanel');
|
| 19 |
+
const parseBtn = document.getElementById('parseBtn');
|
| 20 |
+
const asinInput = document.getElementById('asinInput');
|
| 21 |
+
const previewSection = document.getElementById('previewSection');
|
| 22 |
+
const previewGrid = document.getElementById('previewGrid');
|
| 23 |
+
const totalValidCount = document.getElementById('totalValidCount');
|
| 24 |
+
const selectAllBtn = document.getElementById('selectAllBtn');
|
| 25 |
+
const deselectAllBtn = document.getElementById('deselectAllBtn');
|
| 26 |
+
const selectedCountText = document.getElementById('selectedCountText');
|
| 27 |
+
const applySelectionBtn = document.getElementById('applySelectionBtn');
|
| 28 |
+
const saveContextCheckbox = document.getElementById('saveContextCheckbox');
|
| 29 |
+
const contextNameInput = document.getElementById('contextNameInput');
|
| 30 |
+
const tabButtons = document.querySelectorAll('.tab-btn');
|
| 31 |
+
const tabContents = document.querySelectorAll('.tab-content');
|
| 32 |
+
const toast = document.getElementById('toast');
|
| 33 |
+
const confirmModal = document.getElementById('confirmModal');
|
| 34 |
+
const modalCount = document.getElementById('modalCount');
|
| 35 |
+
const confirmInput = document.getElementById('confirmInput');
|
| 36 |
+
const cancelConfirmBtn = document.getElementById('cancelConfirmBtn');
|
| 37 |
+
const proceedConfirmBtn = document.getElementById('proceedConfirmBtn');
|
| 38 |
+
|
| 39 |
+
// State
|
| 40 |
+
let selectedProducts = [];
|
| 41 |
+
let currentTab = 'manual';
|
| 42 |
+
|
| 43 |
+
// Event Listeners
|
| 44 |
+
selectContextBtn.addEventListener('click', toggleContextPanel);
|
| 45 |
+
parseBtn.addEventListener('click', parseAsins);
|
| 46 |
+
selectAllBtn.addEventListener('click', selectAllProducts);
|
| 47 |
+
deselectAllBtn.addEventListener('click', deselectAllProducts);
|
| 48 |
+
applySelectionBtn.addEventListener('click', applySelection);
|
| 49 |
+
saveContextCheckbox.addEventListener('change', toggleSaveContextInput);
|
| 50 |
+
|
| 51 |
+
tabButtons.forEach(button => {
|
| 52 |
+
button.addEventListener('click', () => switchTab(button.dataset.tab));
|
| 53 |
+
});
|
| 54 |
+
|
| 55 |
+
confirmInput.addEventListener('input', checkConfirmation);
|
| 56 |
+
cancelConfirmBtn.addEventListener('click', closeConfirmModal);
|
| 57 |
+
proceedConfirmBtn.addEventListener('click', confirmAndApply);
|
| 58 |
+
|
| 59 |
+
// Functions
|
| 60 |
+
function toggleContextPanel() {
|
| 61 |
+
contextPanel.classList.toggle('hidden');
|
| 62 |
+
if (!contextPanel.classList.contains('hidden')) {
|
| 63 |
+
// Focus first input when panel opens
|
| 64 |
+
if (currentTab === 'manual') {
|
| 65 |
+
asinInput.focus();
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
function switchTab(tabName) {
|
| 71 |
+
currentTab = tabName;
|
| 72 |
+
|
| 73 |
+
// Update tab button states
|
| 74 |
+
tabButtons.forEach(button => {
|
| 75 |
+
if (button.dataset.tab === tabName) {
|
| 76 |
+
button.classList.add('active-tab');
|
| 77 |
+
} else {
|
| 78 |
+
button.classList.remove('active-tab');
|
| 79 |
+
}
|
| 80 |
+
});
|
| 81 |
+
|
| 82 |
+
// Show/hide tab contents
|
| 83 |
+
tabContents.forEach(content => {
|
| 84 |
+
if (content.id === `${tabName}Tab`) {
|
| 85 |
+
content.classList.remove('hidden');
|
| 86 |
+
} else {
|
| 87 |
+
content.classList.add('hidden');
|
| 88 |
+
}
|
| 89 |
+
});
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
function parseAsins() {
|
| 93 |
+
const asinsText = asinInput.value.trim();
|
| 94 |
+
|
| 95 |
+
if (!asinsText) {
|
| 96 |
+
return;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
// Simulate API call
|
| 100 |
+
setTimeout(() => {
|
| 101 |
+
// Validate ASINs (mock)
|
| 102 |
+
const validProducts = mockProducts.slice(0, 10); // Just show first 10 for demo
|
| 103 |
+
const totalCount = 142; // Mock total count
|
| 104 |
+
|
| 105 |
+
// Update UI
|
| 106 |
+
renderProductPreview(validProducts);
|
| 107 |
+
totalValidCount.textContent = totalCount;
|
| 108 |
+
previewSection.classList.remove('hidden');
|
| 109 |
+
|
| 110 |
+
// Update selected count
|
| 111 |
+
updateSelectedCount();
|
| 112 |
+
}, 300);
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
function renderProductPreview(products) {
|
| 116 |
+
previewGrid.innerHTML = '';
|
| 117 |
+
|
| 118 |
+
products.forEach(product => {
|
| 119 |
+
const productCard = document.createElement('div');
|
| 120 |
+
productCard.className = 'product-card bg-[#1E1F23] rounded-md p-3 cursor-pointer';
|
| 121 |
+
productCard.dataset.asin = product.asin;
|
| 122 |
+
|
| 123 |
+
productCard.innerHTML = `
|
| 124 |
+
<div class="flex">
|
| 125 |
+
<div class="flex-shrink-0 mr-3">
|
| 126 |
+
<img src="${product.image}" alt="${product.title}" class="w-16 h-16 object-cover rounded">
|
| 127 |
+
</div>
|
| 128 |
+
<div class="flex-1 min-w-0">
|
| 129 |
+
<h4 class="text-sm font-medium truncate-2-lines" title="${product.title}">${product.title}</h4>
|
| 130 |
+
<div class="flex items-center mt-1">
|
| 131 |
+
<span class="bg-[#131417] text-xs px-1.5 py-0.5 rounded font-mono">${product.asin}</span>
|
| 132 |
+
</div>
|
| 133 |
+
<div class="flex flex-wrap gap-1.5 mt-2">
|
| 134 |
+
<span class="bg-[#1A1C21] text-xs px-1.5 py-0.5 rounded">Sales: ${product.metrics.sales_30}</span>
|
| 135 |
+
<span class="bg-[#1A1C21] text-xs px-1.5 py-0.5 rounded">CVR: ${(product.metrics.cvr_30 * 100).toFixed(1)}%</span>
|
| 136 |
+
<span class="bg-[#1A1C21] text-xs px-1.5 py-0.5 rounded">Clicks: ${product.metrics.clicks_30}</span>
|
| 137 |
+
</div>
|
| 138 |
+
</div>
|
| 139 |
+
<div class="ml-2 flex-shrink-0">
|
| 140 |
+
<input type="checkbox" class="product-checkbox" data-asin="${product.asin}">
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
`;
|
| 144 |
+
|
| 145 |
+
// Add event listeners
|
| 146 |
+
const checkbox = productCard.querySelector('.product-checkbox');
|
| 147 |
+
checkbox.addEventListener('change', () => toggleProductSelection(product.asin, checkbox.checked));
|
| 148 |
+
|
| 149 |
+
productCard.addEventListener('click', (e) => {
|
| 150 |
+
if (e.target.tagName !== 'INPUT') {
|
| 151 |
+
checkbox.checked = !checkbox.checked;
|
| 152 |
+
toggleProductSelection(product.asin, checkbox.checked);
|
| 153 |
+
}
|
| 154 |
+
});
|
| 155 |
+
|
| 156 |
+
previewGrid.appendChild(productCard);
|
| 157 |
+
});
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
function toggleProductSelection(asin, isSelected) {
|
| 161 |
+
if (isSelected && !selectedProducts.includes(asin)) {
|
| 162 |
+
selectedProducts.push(asin);
|
| 163 |
+
} else if (!isSelected) {
|
| 164 |
+
selectedProducts = selectedProducts.filter(a => a !== asin);
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
// Update UI
|
| 168 |
+
updateSelectedCount();
|
| 169 |
+
highlightSelectedCards();
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
function selectAllProducts() {
|
| 173 |
+
const checkboxes = document.querySelectorAll('.product-checkbox');
|
| 174 |
+
checkboxes.forEach(checkbox => {
|
| 175 |
+
checkbox.checked = true;
|
| 176 |
+
if (!selectedProducts.includes(checkbox.dataset.asin)) {
|
| 177 |
+
selectedProducts.push(checkbox.dataset.asin);
|
| 178 |
+
}
|
| 179 |
+
});
|
| 180 |
+
|
| 181 |
+
updateSelectedCount();
|
| 182 |
+
highlightSelectedCards();
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
function deselectAllProducts() {
|
| 186 |
+
const checkboxes = document.querySelectorAll('.product-checkbox');
|
| 187 |
+
checkboxes.forEach(checkbox => {
|
| 188 |
+
checkbox.checked = false;
|
| 189 |
+
});
|
| 190 |
+
|
| 191 |
+
selectedProducts = [];
|
| 192 |
+
updateSelectedCount();
|
| 193 |
+
highlightSelectedCards();
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
function highlightSelectedCards() {
|
| 197 |
+
const productCards = document.querySelectorAll('.product-card');
|
| 198 |
+
productCards.forEach(card => {
|
| 199 |
+
if (selectedProducts.includes(card.dataset.asin)) {
|
| 200 |
+
card.classList.add('selected');
|
| 201 |
+
} else {
|
| 202 |
+
card.classList.remove('selected');
|
| 203 |
+
}
|
| 204 |
+
});
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
function updateSelectedCount() {
|
| 208 |
+
const count = selectedProducts.length;
|
| 209 |
+
selectedCountText.textContent = `Selected: ${count} product${count !== 1 ? 's' : ''}`;
|
| 210 |
+
|
| 211 |
+
// Show warning if count is large
|
| 212 |
+
if (count > 500) {
|
| 213 |
+
selectedCountText.innerHTML += ` <span class="text-yellow-400">(Large selection)</span>`;
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
function toggleSaveContextInput() {
|
| 218 |
+
if (saveContextCheckbox.checked) {
|
| 219 |
+
contextNameInput.classList.remove('hidden');
|
| 220 |
+
contextNameInput.focus();
|
| 221 |
+
} else {
|
| 222 |
+
contextNameInput.classList.add('hidden');
|
| 223 |
+
}
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
function applySelection() {
|
| 227 |
+
const count = selectedProducts.length;
|
| 228 |
+
|
| 229 |
+
if (count === 0) {
|
| 230 |
+
showToast('Please select at least one product');
|
| 231 |
+
return;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
if (count > 5000) {
|
| 235 |
+
showConfirmModal(count);
|
| 236 |
+
return;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
if (count > 500) {
|
| 240 |
+
// Soft warning - just proceed
|
| 241 |
+
proceedWithApply();
|
| 242 |
+
} else {
|
| 243 |
+
proceedWithApply();
|
| 244 |
+
}
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
function showConfirmModal(count) {
|
| 248 |
+
modalCount.textContent = count.toLocaleString();
|
| 249 |
+
confirmModal.classList.remove('hidden');
|
| 250 |
+
confirmInput.value = '';
|
| 251 |
+
proceedConfirmBtn.disabled = true;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
function closeConfirmModal() {
|
| 255 |
+
confirmModal.classList.add('hidden');
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
function checkConfirmation() {
|
| 259 |
+
proceedConfirmBtn.disabled = confirmInput.value.trim().toUpperCase() !== 'CONFIRM';
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
function confirmAndApply() {
|
| 263 |
+
closeConfirmModal();
|
| 264 |
+
proceedWithApply();
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
function proceedWithApply() {
|
| 268 |
+
const contextName = saveContextCheckbox.checked ? contextNameInput.value.trim() : null;
|
| 269 |
+
|
| 270 |
+
// Simulate API call
|
| 271 |
+
setTimeout(() => {
|
| 272 |
+
// Update step 1 subtitle
|
| 273 |
+
const subtitle = document.querySelector('#contextPanel + div p');
|
| 274 |
+
subtitle.textContent = `Context: ${selectedProducts.length} product${selectedProducts.length !== 1 ? 's' : ''} selected`;
|
| 275 |
+
if (contextName) {
|
| 276 |
+
subtitle.textContent += ` (Saved as "${contextName}")`;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
// Close panel
|
| 280 |
+
contextPanel.classList.add('hidden');
|
| 281 |
+
|
| 282 |
+
// Show success toast
|
| 283 |
+
showToast('Context applied successfully');
|
| 284 |
+
|
| 285 |
+
// Reset selection for next time
|
| 286 |
+
selectedProducts = [];
|
| 287 |
+
asinInput.value = '';
|
| 288 |
+
previewSection.classList.add('hidden');
|
| 289 |
+
saveContextCheckbox.checked = false;
|
| 290 |
+
contextNameInput.classList.add('hidden');
|
| 291 |
+
contextNameInput.value = '';
|
| 292 |
+
}, 500);
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
function showToast(message) {
|
| 296 |
+
const toastMessage = document.getElementById('toastMessage');
|
| 297 |
+
toastMessage.textContent = message;
|
| 298 |
+
toast.classList.remove('hidden');
|
| 299 |
+
|
| 300 |
+
setTimeout(() => {
|
| 301 |
+
toast.classList.add('hidden');
|
| 302 |
+
}, 3000);
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
// Initialize
|
| 306 |
+
switchTab('manual');
|
| 307 |
+
});
|
|
@@ -1,28 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
body {
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
}
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
}
|
| 17 |
|
| 18 |
-
.card {
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
}
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
|
|
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
|
| 2 |
+
|
| 3 |
+
* {
|
| 4 |
+
box-sizing: border-box;
|
| 5 |
+
margin: 0;
|
| 6 |
+
padding: 0;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
body {
|
| 10 |
+
font-family: 'Inter', sans-serif;
|
| 11 |
+
-webkit-font-smoothing: antialiased;
|
| 12 |
+
-moz-osx-font-smoothing: grayscale;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
/* Animation for panel */
|
| 16 |
+
@keyframes slideDown {
|
| 17 |
+
from {
|
| 18 |
+
opacity: 0;
|
| 19 |
+
transform: translateY(-10px);
|
| 20 |
+
}
|
| 21 |
+
to {
|
| 22 |
+
opacity: 1;
|
| 23 |
+
transform: translateY(0);
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
#contextPanel {
|
| 28 |
+
animation: slideDown 150ms ease-out forwards;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
/* Tab styling */
|
| 32 |
+
.tab-btn {
|
| 33 |
+
transition: all 150ms ease;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.tab-btn.active-tab {
|
| 37 |
+
border-bottom-color: #2563EB;
|
| 38 |
+
color: #E6E6E6;
|
| 39 |
}
|
| 40 |
|
| 41 |
+
/* Product card styling */
|
| 42 |
+
.product-card {
|
| 43 |
+
transition: all 150ms ease;
|
| 44 |
+
border: 1px solid #2B2C2F;
|
| 45 |
}
|
| 46 |
|
| 47 |
+
.product-card:hover {
|
| 48 |
+
border-color: #2563EB;
|
| 49 |
+
transform: translateY(-2px);
|
|
|
|
|
|
|
| 50 |
}
|
| 51 |
|
| 52 |
+
.product-card.selected {
|
| 53 |
+
border-color: #2563EB;
|
| 54 |
+
background-color: rgba(37, 99, 235, 0.08);
|
|
|
|
|
|
|
|
|
|
| 55 |
}
|
| 56 |
|
| 57 |
+
/* Chip buttons */
|
| 58 |
+
.chip-btn {
|
| 59 |
+
transition: all 150ms ease;
|
| 60 |
}
|
| 61 |
+
|
| 62 |
+
.chip-btn:hover {
|
| 63 |
+
border-color: #2563EB;
|
| 64 |
+
color: #E6E6E6;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
/* Scrollbar styling */
|
| 68 |
+
::-webkit-scrollbar {
|
| 69 |
+
width: 6px;
|
| 70 |
+
height: 6px;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
::-webkit-scrollbar-track {
|
| 74 |
+
background: #1E1F23;
|
| 75 |
+
border-radius: 3px;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
::-webkit-scrollbar-thumb {
|
| 79 |
+
background: #2B2C2F;
|
| 80 |
+
border-radius: 3px;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
::-webkit-scrollbar-thumb:hover {
|
| 84 |
+
background: #3B3C3F;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
/* Focus styles */
|
| 88 |
+
button:focus, input:focus, textarea:focus, select:focus {
|
| 89 |
+
outline: 2px solid #2563EB;
|
| 90 |
+
outline-offset: 2px;
|
| 91 |
+
}
|