Theia rip-out (parent):
- Remove theia submodule entry (the local fork, Gitea repo, Coolify app,
Cloud Run services, and Artifact Registry image are all gone)
- Drop README.md + INFRASTRUCTURE.md (obsolete "Project OS" snapshots
that also leaked API tokens) and setup.sh (Theia clone bootstrap)
- Delete UI-DESIGN-GUIDE.md, BACKEND_AGENTS_PLAN.md, VIBN_BUILD_PLAN.md,
VISUAL_EDITOR_PLAN.md, core-packages.md, ai-packages.md, tools-list.md
(all 100% Theia-specific or superseded)
- Surgical scrubs of remaining Theia mentions in
AGENT_EXECUTION_ARCHITECTURE.md and TURBOREPO_MIGRATION_PLAN.md
Submodule bumps:
- vibn-agent-runner: Theia rip-out + MCP refactor (api/wrapper/server
pattern across shell/file/git/memory/prd/search/agent/gitea/coolify)
- vibn-frontend: Theia rip-out + P5.1 attach E2E + Justine UI WIP
Retire platform/ scaffold:
- Remove platform/backend/ (control-plane, executors, mcp-adapter),
platform/client-ide/ (gcp-productos extension), platform/contracts/,
platform/infra/terraform/, platform/scripts/templates/turborepo/
(replaced by vibn-agent-runner + vibn-frontend + Coolify direct)
- Drop architecture.md, technical_spec.md, vision-ext.md,
"1.Generate Control Plane API scaffold.md" (same era)
Docs / planning snapshots (new):
- AI_CAPABILITIES.md, AI_CAPABILITIES_ROADMAP.md
- AGENT_TELEMETRY_STREAMING_PROJECT.md
- VIBN_PRD.md, product-idea-a.md
Design assets (new):
- branding/{coolify,gitea,ux-testing}/ static brand collateral
- justine/ HTML mockups for the new onboarding/build flows
- preview-assist-ui/ Vite scratch app
- master-ai.code-workspace
Infra helpers (new):
- setup-coolify-montreal.sh provisioner
- gitea-docker-compose.yml
- vibn-coolify-schema.sql for the Coolify Postgres extensions
- prd-agent-prompt.pdf, prompt, root.txt, remixed-9edec9e9.tsx scratch
- flatten.sh helper
.gitignore: ignore **/node_modules, **/.next, **/.turbo, **/coverage
Made-with: Cursor
1190 lines
64 KiB
HTML
1190 lines
64 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"><link rel="icon" href="favicon_clean.ico">
|
||
<title>vibn — Describe</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||
<style>
|
||
*{box-sizing:border-box;margin:0;padding:0;}
|
||
:root{--ink:#1a1a1a;--ink2:#2c2c2a;--ink3:#444441;--mid:#6b7280;--muted:#9ca3af;--stone:#b4b2a9;--parch:#d3d1c7;--cream:#f1efe8;--paper:#f7f4ee;--white:#ffffff;--border:#e5e7eb;--accent-primary:#6366f1;--accent-secondary:#8b5cf6;--accent-soft:#818cf8;--accent-dark:#2e2a5e;--accent-gradient:#4338ca;}
|
||
body{font-family:'Plus Jakarta Sans',sans-serif;background:linear-gradient(to bottom, #fafafa, #f5f3ff);display:flex;flex-direction:column;height:100vh;height:100dvh;overflow:hidden;}
|
||
.f{font-family:'Plus Jakarta Sans',sans-serif;}
|
||
input::placeholder{color:var(--muted);}
|
||
|
||
/* ── Dark mode ── */
|
||
[data-theme="dark"]{
|
||
--ink:#ECE9F5;--ink2:#C8C4D8;--ink3:#A0A0B8;
|
||
--mid:#9AA3BC;--muted:#6A7490;--border:#3A4260;
|
||
--white:#2A3250;--accent-primary:#A5B4FC;
|
||
}
|
||
[data-theme="dark"] body{background:#1A1F2E;}
|
||
|
||
/* Surface hierarchy: sidebar & chat = mid, prd panel = darkest so cards float */
|
||
[data-theme="dark"] .sidebar-col{background:#2A3250!important;border-right-color:#3A4260!important;}
|
||
[data-theme="dark"] .chat-col{background:#212840!important;border-right-color:#3A4260!important;}
|
||
[data-theme="dark"] .prd-panel{background:#1A1F2E!important;border-left-color:#3A4260!important;}
|
||
|
||
/* PRD cards float above the dark panel */
|
||
[data-theme="dark"] .prd-card{background:#2A3250!important;border-color:#3A4260!important;}
|
||
[data-theme="dark"] .prd-card-active{background:#2E3A5A!important;border-left-color:#A5B4FC!important;}
|
||
[data-theme="dark"] .prd-card-answered:hover{background:#323C5E!important;border-color:#5865A0!important;}
|
||
[data-theme="dark"] .prd-card-pending{opacity:0.5;}
|
||
[data-theme="dark"] .prd-field-label-answered{color:#6A7490!important;}
|
||
[data-theme="dark"] .prd-field-value-answered{color:#C8D0E8!important;}
|
||
|
||
/* PRD header border */
|
||
[data-theme="dark"] [style*="border-bottom:1px solid #c7d2fe"]{border-bottom-color:#3A4260!important;}
|
||
[data-theme="dark"] [style*="color:#4338ca"]{color:#A5B4FC!important;}
|
||
|
||
/* Chat bubbles */
|
||
[data-theme="dark"] .bubble-ai{background:#2A3250!important;border-color:#3A4260!important;}
|
||
[data-theme="dark"] .ai-bubble{background:#2A3250!important;border-color:#3A4260!important;}
|
||
[data-theme="dark"] .typing-bubble{background:#2A3250!important;border-color:#3A4260!important;}
|
||
|
||
/* Chat input area */
|
||
[data-theme="dark"] .chat-input-wrap{background:#2A3250!important;border-color:#3A4260!important;}
|
||
[data-theme="dark"] #chat-input{color:var(--ink)!important;}
|
||
[data-theme="dark"] [style*="background:var(--white)"]{background:#2A3250!important;}
|
||
[data-theme="dark"] [style*="background:#fafafa"]{background:#212840!important;}
|
||
|
||
/* Buttons — match dashboard indigo style */
|
||
[data-theme="dark"] #send-btn{background:linear-gradient(135deg,#4338CA,#6366F1)!important;color:#FFFFFF!important;}
|
||
[data-theme="dark"] #dark-toggle{background:#2A3250!important;border-color:#3A4260!important;color:var(--mid)!important;}
|
||
[data-theme="dark"] button[onclick="saveAndExit()"]{background:#1E2640!important;border-color:#3A4260!important;}
|
||
[data-theme="dark"] button[onclick="saveAndExit()"] span{color:#A5B4FC!important;}
|
||
[data-theme="dark"] .btn-option{background:#2A3250!important;border-color:#3A4260!important;}
|
||
[data-theme="dark"] .btn-option:hover{background:#323C5E!important;border-color:#A5B4FC!important;}
|
||
[data-theme="dark"] .project-type-opt{border-color:#3A4260!important;}
|
||
[data-theme="dark"] .project-type-opt:hover,[data-theme="dark"] .project-type-opt.selected{background:rgba(165,180,252,0.1)!important;border-color:#A5B4FC!important;}
|
||
|
||
/* Sidebar phases & progress */
|
||
[data-theme="dark"] .sidebar-phase.active{background:rgba(165,180,252,0.12)!important;}
|
||
[data-theme="dark"] [style*="background:#fafaff"]{background:#212840!important;}
|
||
[data-theme="dark"] [style*="background:#eef2ff"]{background:rgba(99,102,241,0.18)!important;}
|
||
[data-theme="dark"] [style*="background:#f0f4ff"]{background:#2A3250!important;}
|
||
[data-theme="dark"] [style*="border-color:rgba(99,102,241,0.12)"]{border-color:#3A4260!important;}
|
||
|
||
/* Modals */
|
||
[data-theme="dark"] .edit-modal-box,[data-theme="dark"] #save-exit-box{background:#242B48!important;}
|
||
[data-theme="dark"] #plan-loading-popup{background:#242B48!important;border-color:#3A4260!important;}
|
||
|
||
/* Scrollbars */
|
||
[data-theme="dark"] ::-webkit-scrollbar{width:6px;height:6px;}
|
||
[data-theme="dark"] ::-webkit-scrollbar-track{background:#1A1F2E;}
|
||
[data-theme="dark"] ::-webkit-scrollbar-thumb{background:#3A4260;border-radius:3px;}
|
||
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover{background:#5865A0;}
|
||
[data-theme="dark"] *{scrollbar-color:#3A4260 #1A1F2E;scrollbar-width:thin;}
|
||
[data-theme="dark"] .prd-next-wrap{border-top-color:#3A4260!important;}
|
||
[data-theme="dark"] #next-architect-btn{background:linear-gradient(135deg,#4338CA,#6366F1)!important;color:#FFFFFF!important;box-shadow:0 4px 14px rgba(99,102,241,0.25)!important;}
|
||
|
||
[data-theme="dark"] .vibn-avatar{background:#6366F1!important;}
|
||
|
||
/* Project setup modal — always light mode (shown before dark toggle is accessible) */
|
||
[data-theme="dark"] #project-type-modal{background:rgba(15,14,26,0.6);}
|
||
[data-theme="dark"] #project-type-box{background:#FFFFFF!important;border-color:#E5E7EB!important;}
|
||
[data-theme="dark"] #project-type-box h3{color:#1A1A1A!important;}
|
||
[data-theme="dark"] #project-type-box>p{color:#9CA3AF!important;}
|
||
[data-theme="dark"] #project-type-box label{color:#6B7280!important;}
|
||
[data-theme="dark"] #project-name-input{background:#FAFAFA!important;border-color:#E5E7EB!important;color:#1A1A1A!important;}
|
||
[data-theme="dark"] .project-type-opt{background:transparent!important;border-color:#E5E7EB!important;}
|
||
[data-theme="dark"] .project-type-opt:hover,[data-theme="dark"] .project-type-opt.selected{background:#FAFAFF!important;border-color:#6366F1!important;}
|
||
[data-theme="dark"] .project-type-opt div[style*="color:#1A1A1A"]{color:#1A1A1A!important;}
|
||
[data-theme="dark"] .project-type-opt div[style*="color:#9CA3AF"]{color:#9CA3AF!important;}
|
||
[data-theme="dark"] .project-type-icon{background:#F5F3FF!important;color:#6366F1!important;}
|
||
|
||
/* ── Fade-up animation for new bubbles (improvement 5) ── */
|
||
@keyframes fadeUp{from{opacity:0;transform:translateY(8px);}to{opacity:1;transform:translateY(0);}}
|
||
|
||
/* ── Bubble classes (improvement 1) ── */
|
||
.bubble{max-width:84%;font-size:13px;line-height:1.65;}
|
||
.bubble-ai{background:#f0f4ff;border:1px solid #e0e7ff;border-radius:4px 12px 12px 12px;padding:11px 14px;color:var(--ink);white-space:pre-line;animation:fadeUp 0.3s ease;}
|
||
.bubble-user{background:var(--accent-primary);border-radius:12px 12px 4px 12px;padding:11px 14px;color:var(--white);animation:fadeUp 0.3s ease;}
|
||
|
||
/* Legacy static bubble classes (keep for the pre-seeded chat HTML) */
|
||
.ai-bubble{max-width:84%;background:#f0f4ff;border:1px solid #e0e7ff;border-radius:4px 12px 12px 12px;padding:11px 14px;font-size:13px;color:var(--ink);line-height:1.65;white-space:pre-line;}
|
||
.user-bubble{max-width:84%;background:var(--accent-primary);border-radius:12px 12px 4px 12px;padding:11px 14px;font-size:13px;color:var(--white);line-height:1.65;}
|
||
|
||
@keyframes prdPulse{0%{background-color:#f0f4ff;}50%{background-color:#e0e7ff;}100%{background-color:#f0f4ff;}}
|
||
@keyframes bubbleHighlight{0%{background:#e0e7ff;border-color:#a5b4fc;}70%{background:#e0e7ff;border-color:#a5b4fc;}100%{background:#f0f4ff;border-color:#e0e7ff;}}
|
||
.bubble-highlight{animation:bubbleHighlight 1.4s ease forwards !important;}
|
||
|
||
/* ── PRD card base (improvement 1) ── */
|
||
.prd-card{padding:11px 13px;background:var(--white);border-radius:9px;border:1px solid var(--border);margin-bottom:8px;transition:opacity 0.3s;}
|
||
.prd-card.updating{animation:prdPulse 0.6s ease-out;}
|
||
|
||
/* Answered state — full opacity, pointer cursor for jump-to (improvement 8) */
|
||
.prd-card-answered{opacity:1;cursor:pointer;transition:background 0.15s,border-color 0.15s;}
|
||
.prd-card-answered:hover{background:#f0f4ff;border-color:#c7d2fe;}
|
||
|
||
/* Pending state — muted for cards not yet reached */
|
||
.prd-card-pending{opacity:0.45;border-style:dashed;}
|
||
|
||
/* Active card — currently being filled */
|
||
.prd-card-active{opacity:1 !important;border-style:solid !important;border-left:3px solid var(--accent-primary) !important;background:#fafaff !important;}
|
||
|
||
/* Pending field hint */
|
||
.prd-hint{font-size:11px;color:var(--muted);font-style:italic;margin-top:4px;}
|
||
|
||
/* ── PRD field label (improvement 1 & 3) ── */
|
||
.prd-field-label{font-size:9.5px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;margin-bottom:4px;}
|
||
.prd-field-label-answered{color:var(--ink3);}
|
||
.prd-field-label-pending{color:var(--muted);}
|
||
|
||
/* ── PRD field value (improvement 3) ── */
|
||
.prd-field-value{font-size:12px;line-height:1.55;}
|
||
.prd-field-value-answered{color:#1A1A1A;}
|
||
.prd-field-value-pending{color:#9CA3AF;}
|
||
|
||
.prd-edit-btn{background:none;border:none;cursor:pointer;font-size:14px;padding:4px 8px;color:var(--ink3);opacity:1;transition:color 0.2s;flex-shrink:0;}
|
||
.prd-edit-btn:hover:not(:disabled){color:var(--accent-primary);}
|
||
.prd-edit-btn:disabled{opacity:0.3;cursor:not-allowed;}
|
||
|
||
/* ── PRD panel layout — Next button outside scroll ── */
|
||
.prd-panel{width:384px;background:#f5f3ff;display:flex;flex-direction:column;padding:20px 16px 0 16px;flex-shrink:0;border-left:1px solid var(--border);height:100vh;}
|
||
.prd-scroll{flex:1;overflow-y:auto;padding-bottom:24px;margin-right:-16px;padding-right:16px;}
|
||
.prd-next-wrap{flex-shrink:0;padding:9px 0 13px;border-top:1px solid var(--border);display:flex;flex-direction:column;align-items:center;}
|
||
|
||
/* ── Chat input wrap (improvement 1) ── */
|
||
.chat-input-wrap{display:flex;gap:8px;align-items:center;background:#fafafa;border:1px solid var(--border);border-radius:11px;padding:9px 13px;transition:border-color 0.15s;}
|
||
@keyframes shake{0%,100%{transform:translateX(0);}20%{transform:translateX(-5px);}40%{transform:translateX(5px);}60%{transform:translateX(-4px);}80%{transform:translateX(4px);}}
|
||
.chat-input-wrap.shake{animation:shake 0.35s ease;border-color:#f87171;}
|
||
|
||
/* ── Button classes (improvement 1) ── */
|
||
.btn-primary{background:linear-gradient(135deg,#2e2a5e,#4338ca);color:var(--white);border:none;border-radius:8px;padding:10px 20px;font-size:13px;font-weight:600;cursor:pointer;}
|
||
.btn-secondary{background:transparent;border:1px solid var(--border);color:var(--ink);border-radius:8px;padding:10px 20px;font-size:13px;cursor:pointer;}
|
||
|
||
|
||
.sidebar-phase{display:flex;align-items:center;gap:9px;padding:9px 10px;border-radius:8px;}
|
||
.sidebar-phase.active{background:#fafaff;}
|
||
.phase-dot{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:10px;}
|
||
|
||
/* Edit modal */
|
||
.edit-modal{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.4);z-index:1000;align-items:center;justify-content:center;}
|
||
.edit-modal.visible{display:flex;}
|
||
.edit-modal-box{background:var(--white);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,0.2);padding:28px;width:90%;max-width:400px;}
|
||
.edit-modal-box h3{font-size:16px;font-weight:700;color:var(--ink);margin-bottom:16px;}
|
||
.edit-modal-box p{font-size:13px;color:var(--muted);line-height:1.6;margin-bottom:20px;}
|
||
.edit-options{display:flex;flex-direction:column;gap:10px;margin-bottom:20px;}
|
||
.btn-option{border:1px solid var(--border);background:transparent;border-radius:9px;padding:12px;text-align:left;cursor:pointer;transition:all 0.15s;}
|
||
.btn-option:hover{border-color:var(--accent-primary);background:#fafaff;}
|
||
.btn-option-title{font-size:13px;font-weight:600;color:var(--ink);margin-bottom:4px;}
|
||
.btn-option-desc{font-size:12px;color:var(--muted);}
|
||
.edit-modal-actions{display:flex;gap:10px;justify-content:flex-end;}
|
||
|
||
/* ── Mobile dashboard button in tab bar ── */
|
||
.mobile-dash-btn{background:none;border:none;font-family:'Plus Jakarta Sans',sans-serif;font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;padding:11px 12px;white-space:nowrap;flex-shrink:0;transition:color 0.15s;}
|
||
.mobile-dash-btn:hover{color:var(--ink);}
|
||
|
||
/* ── Mobile journey strip (hidden on desktop) ── */
|
||
.mobile-journey{display:none;align-items:center;gap:0;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;margin-bottom:10px;}
|
||
.mobile-journey::-webkit-scrollbar{display:none;}
|
||
.journey-step{display:flex;align-items:center;gap:0;flex-shrink:0;}
|
||
.journey-step-label{font-size:10px;font-weight:600;color:var(--muted);white-space:nowrap;padding:3px 8px;border-radius:20px;transition:color 0.2s,background 0.2s;}
|
||
.journey-step-label.current{color:var(--accent-primary);background:#ede9fe;}
|
||
.journey-step-arrow{font-size:9px;color:var(--border);margin:0 2px;}
|
||
@media(max-width:900px){.mobile-journey{display:flex;}}
|
||
|
||
/* ── Mobile tab bar (hidden on desktop) ── */
|
||
.mobile-tabs{display:none;flex-shrink:0;background:#EEF0FF;border-bottom:1px solid #D4D8FA;}
|
||
[data-theme="dark"] .mobile-tabs{background:#212840!important;}
|
||
.mobile-tab-btn{flex:1;padding:11px 8px;border:none;background:transparent;font-family:'Plus Jakarta Sans',sans-serif;font-size:13px;font-weight:500;color:var(--muted);cursor:pointer;border-bottom:2px solid transparent;transition:color 0.15s,border-color 0.15s;}
|
||
.mobile-tab-btn.active{color:var(--accent-primary);border-bottom-color:var(--accent-primary);font-weight:600;}
|
||
.tab-badge{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--accent-primary);margin-left:5px;vertical-align:middle;position:relative;top:-1px;}
|
||
.tab-badge.hidden{display:none;}
|
||
.tab-live{font-size:9px;font-weight:600;color:var(--accent-primary);background:#ede9fe;border-radius:4px;padding:1px 5px;margin-left:5px;letter-spacing:0.04em;vertical-align:middle;transition:background 0.2s,color 0.2s;}
|
||
@keyframes livePulse{0%,100%{background:#ede9fe;color:var(--accent-primary);}50%{background:var(--accent-primary);color:#fff;}}
|
||
.tab-live.pulsing{animation:livePulse 0.6s ease;}
|
||
|
||
/* ── Typing indicator ── */
|
||
.typing-bubble{background:#f0f4ff;border:1px solid #e0e7ff;border-radius:4px 12px 12px 12px;padding:11px 14px;display:inline-flex;gap:5px;align-items:center;}
|
||
.typing-bubble span{width:5px;height:5px;border-radius:50%;background:#a5b4fc;animation:loadDot 1.2s infinite ease-in-out;}
|
||
.typing-bubble span:nth-child(2){animation-delay:0.2s;}
|
||
.typing-bubble span:nth-child(3){animation-delay:0.4s;}
|
||
|
||
/* ── Save & exit popup ── */
|
||
#save-exit-popup{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.35);z-index:700;align-items:center;justify-content:center;padding:24px;}
|
||
#save-exit-popup.visible{display:flex;}
|
||
#save-exit-box{background:var(--white);border-radius:16px;box-shadow:0 20px 60px rgba(0,0,0,0.15);padding:32px;width:100%;max-width:380px;text-align:center;}
|
||
#save-exit-box .save-icon{width:48px;height:48px;background:#f0f4ff;border:1px solid #e0e7ff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:20px;margin:0 auto 16px;}
|
||
#save-exit-box h3{font-family:'Plus Jakarta Sans',sans-serif;font-size:18px;font-weight:700;color:var(--ink);margin-bottom:8px;}
|
||
#save-exit-box p{font-size:13px;color:var(--muted);line-height:1.6;margin-bottom:20px;}
|
||
#save-exit-box .save-cancel{font-size:12px;color:var(--muted);cursor:pointer;text-decoration:underline;background:none;border:none;}
|
||
#save-exit-box .save-cancel:hover{color:var(--ink);}
|
||
|
||
/* ── Project type modal ── */
|
||
#project-type-modal{position:fixed;inset:0;background:rgba(15,14,26,0.45);backdrop-filter:blur(2px);z-index:600;display:flex;align-items:center;justify-content:center;padding:24px;}
|
||
#project-type-modal.hidden{display:none;}
|
||
#project-type-box{background:#FFFFFF;border-radius:16px;box-shadow:0 24px 64px rgba(30,27,75,0.18);padding:28px;width:100%;max-width:420px;}
|
||
#project-type-box h3{font-family:'Plus Jakarta Sans',sans-serif;font-size:19px;font-weight:700;color:#1A1A1A;margin-bottom:4px;letter-spacing:-0.02em;}
|
||
#project-type-box>p{font-size:13px;color:#9CA3AF;margin-bottom:20px;line-height:1.5;}
|
||
#project-type-box label{display:block;font-size:11px;font-weight:600;color:#6B7280;text-transform:uppercase;letter-spacing:0.05em;margin-bottom:6px;}
|
||
#project-name-input{width:100%;border:1px solid #E5E7EB;border-radius:8px;padding:10px 13px;font-family:'Plus Jakarta Sans',sans-serif;font-size:14px;color:#1A1A1A;background:#FAFAFA;outline:none;transition:border-color 0.15s,box-shadow 0.15s;}
|
||
#project-name-input:focus{border-color:#6366F1;box-shadow:0 0 0 3px rgba(99,102,241,0.12);}
|
||
#project-setup-btn{width:100%;margin-top:16px;padding:11px;background:linear-gradient(135deg,#2E2A5E,#4338CA);color:#FFFFFF;border:none;border-radius:8px;font-family:'Plus Jakarta Sans',sans-serif;font-size:14px;font-weight:600;cursor:pointer;box-shadow:0 10px 25px rgba(30,27,75,0.15);transition:opacity 0.15s,box-shadow 0.2s;}
|
||
.project-type-opt{border:1px solid var(--border);background:transparent;border-radius:10px;padding:14px 16px;cursor:pointer;margin-bottom:10px;display:flex;align-items:center;gap:12px;transition:all 0.15s;width:100%;text-align:left;}
|
||
.project-type-opt:hover{border-color:var(--accent-primary);background:#fafaff;}
|
||
.project-type-opt.selected{border-color:var(--accent-primary);background:#fafaff;box-shadow:0 0 0 3px rgba(99,102,241,0.1);}
|
||
.project-type-icon{width:36px;height:36px;border-radius:9px;background:var(--soft, #f5f3ff);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;color:var(--accent-primary);}
|
||
|
||
/* ── Plan loading popup ── */
|
||
@keyframes loadDot{0%,80%,100%{opacity:0.25;transform:scale(0.75);}40%{opacity:1;transform:scale(1);}}
|
||
@keyframes popIn{from{opacity:0;transform:translate(-50%,-50%) scale(0.88);}to{opacity:1;transform:translate(-50%,-50%) scale(1);}}
|
||
#plan-loading-popup{display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:500;background:var(--white);border:1px solid var(--border);border-radius:14px;box-shadow:0 8px 32px rgba(0,0,0,0.12);padding:20px 24px;text-align:center;min-width:180px;animation:popIn 0.2s ease;}
|
||
#plan-loading-popup.visible{display:block;}
|
||
#plan-loading-popup .popup-label{font-size:13px;font-weight:600;color:var(--ink);margin-bottom:12px;}
|
||
.plan-loading-dots{display:inline-flex;gap:6px;justify-content:center;}
|
||
.plan-loading-dots span{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--accent-primary);animation:loadDot 1.2s infinite ease-in-out;}
|
||
.plan-loading-dots span:nth-child(2){animation-delay:0.2s;}
|
||
.plan-loading-dots span:nth-child(3){animation-delay:0.4s;}
|
||
|
||
/* ── Tablet: 601–900px — tab switching (replaces stacking) ── */
|
||
@media (max-width:900px){
|
||
body{height:100dvh;overflow:hidden;}
|
||
.main-layout{flex-direction:column !important;height:100dvh !important;overflow:hidden !important;}
|
||
.sidebar-col{display:none !important;}
|
||
.mobile-tabs{display:flex !important;}
|
||
.chat-col{display:none !important;flex:1;min-height:0 !important;border-right:none !important;}
|
||
.chat-col.tab-active{display:flex !important;}
|
||
.prd-panel{display:none !important;flex:1;width:100% !important;height:auto !important;border-left:none !important;}
|
||
.prd-panel.tab-active{display:flex !important;}
|
||
}
|
||
|
||
/* ── Mobile: ≤ 600px — tab switching, tabs at bottom ── */
|
||
@media (max-width:600px){
|
||
body{height:100dvh !important;overflow:hidden !important;}
|
||
.main-layout{flex-direction:column !important;height:100dvh !important;overflow:hidden !important;}
|
||
.mobile-tabs{display:flex !important;order:2;border-bottom:none;border-top:1px solid var(--border);padding-bottom:env(safe-area-inset-bottom);}
|
||
.chat-col{order:1;height:0;min-height:0 !important;flex:1;display:none !important;border-right:none !important;}
|
||
.chat-col.tab-active{display:flex !important;}
|
||
.prd-panel{order:1;height:0;flex:1;display:none !important;width:100% !important;border-left:none !important;border-top:none !important;}
|
||
.prd-panel.tab-active{display:flex !important;}
|
||
#chat{padding:14px 16px !important;}
|
||
#chat-input{font-size:16px !important;}
|
||
#input-area{padding:8px 14px 12px !important;}
|
||
#send-btn{padding:10px 18px !important;font-size:14px !important;}
|
||
.chat-col>div:first-child{padding:12px 14px 10px !important;}
|
||
.prd-scroll{flex:1;max-height:none !important;}
|
||
.prd-next-wrap{padding:9px 0 13px !important;}
|
||
.ai-bubble,.user-bubble{max-width:90% !important;}
|
||
.bubble{max-width:90% !important;}
|
||
}
|
||
|
||
/* ── Laptop narrow (≤ 600px, mouse device) — tabs at top ── */
|
||
/* hover:hover + pointer:fine identifies mouse/trackpad devices (laptop/desktop),
|
||
excluding phones/tablets (hover:none, pointer:coarse) */
|
||
@media (max-width:600px) and (hover:hover) and (pointer:fine){
|
||
.mobile-tabs{order:0 !important;border-top:none !important;border-bottom:1px solid var(--border) !important;padding-bottom:0 !important;}
|
||
.chat-col{order:1 !important;}
|
||
.prd-panel{order:1 !important;}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="main-layout" style="display:flex;height:100%;overflow:hidden;">
|
||
|
||
<!-- Mobile tab bar (only visible on ≤ 900px) -->
|
||
<div class="mobile-tabs" id="mobile-tabs">
|
||
<button onclick="saveAndExit()" class="mobile-dash-btn">Dashboard</button>
|
||
<div style="width:1px;background:var(--border);margin:8px 0;flex-shrink:0;"></div>
|
||
<button class="mobile-tab-btn active" id="tab-chat" onclick="switchTab('chat')">Chat</button>
|
||
<button class="mobile-tab-btn" id="tab-plan" onclick="switchTab('plan')">Your Plan<span class="tab-live">auto-fill</span><span class="tab-badge hidden" id="plan-tab-badge"></span></button>
|
||
</div>
|
||
|
||
<!-- Save & exit popup -->
|
||
<div id="save-exit-popup">
|
||
<div id="save-exit-box">
|
||
<div class="save-icon">✓</div>
|
||
<h3>Your progress is saved.</h3>
|
||
<p>You can come back to this project anytime from your dashboard — everything will be exactly where you left it.</p>
|
||
<button onclick="window.location.href='03_dashboard.html'" style="width:100%;background:linear-gradient(135deg,#2e2a5e,#4338ca);color:#fff;border:none;border-radius:10px;padding:12px;font-family:'Plus Jakarta Sans',sans-serif;font-size:14px;font-weight:600;cursor:pointer;margin-bottom:10px;">Got it, go to dashboard</button>
|
||
<button class="save-cancel" onclick="cancelSaveExit()">Stay on this page</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Project type modal -->
|
||
<div id="project-type-modal" class="hidden">
|
||
<div id="project-type-box">
|
||
<h3>Set up your project</h3>
|
||
<p>Just a few things before we dive in.</p>
|
||
<div style="margin-bottom:18px;">
|
||
<label>Project name</label>
|
||
<input id="project-name-input" type="text" placeholder="e.g. My First Project" maxlength="50">
|
||
</div>
|
||
<label style="margin-bottom:8px;">Who is this for?</label>
|
||
<button class="project-type-opt" onclick="selectProjectType('self', this)">
|
||
<div class="project-type-icon">◎</div>
|
||
<div>
|
||
<div style="font-size:14px;font-weight:600;color:#1A1A1A;margin-bottom:2px;">It's my own idea</div>
|
||
<div style="font-size:12px;color:#9CA3AF;">A personal product or SaaS</div>
|
||
</div>
|
||
</button>
|
||
<button class="project-type-opt" onclick="selectProjectType('client', this)">
|
||
<div class="project-type-icon">◇</div>
|
||
<div>
|
||
<div style="font-size:14px;font-weight:600;color:#1A1A1A;margin-bottom:2px;">It's for a client</div>
|
||
<div style="font-size:12px;color:#9CA3AF;">Building on behalf of someone else</div>
|
||
</div>
|
||
</button>
|
||
<button id="project-setup-btn" onclick="confirmProjectSetup()" disabled style="opacity:0.35;">
|
||
Start building →
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Plan loading popup -->
|
||
<div id="plan-loading-popup">
|
||
<div class="popup-label">Building your plan…</div>
|
||
<div class="plan-loading-dots"><span></span><span></span><span></span></div>
|
||
</div>
|
||
|
||
<!-- SIDEBAR -->
|
||
<div class="sidebar-col" style="width:200px;background:var(--white);border-right:1px solid var(--border);display:flex;flex-direction:column;padding:18px 12px;flex-shrink:0;">
|
||
<div style="padding:0 6px;margin-bottom:26px;">
|
||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
|
||
<div class="vibn-avatar" style="width:26px;height:26px;background:linear-gradient(135deg,#2E2A5E,#4338CA);border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;"><span class="f" style="font-size:13px;font-weight:700;color:#FFFFFF;">V</span></div>
|
||
<span class="f" style="font-size:16px;font-weight:700;color:var(--ink);letter-spacing:-0.02em;">vibn</span>
|
||
</div>
|
||
<div id="sidebar-project-name" style="font-size:11px;font-weight:500;color:var(--muted);padding-left:34px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:none;"></div>
|
||
</div>
|
||
<div style="font-size:9.5px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:var(--muted);padding:0 6px;margin-bottom:8px;">MVP Setup</div>
|
||
<div style="display:flex;flex-direction:column;gap:2px;flex:1;">
|
||
<div class="sidebar-phase active">
|
||
<div class="phase-dot" style="background:var(--accent-primary);color:var(--white);">◇</div>
|
||
<div><div style="font-size:12.5px;font-weight:600;color:var(--ink);">Describe</div><div style="font-size:10px;color:var(--muted);">Your idea</div></div>
|
||
</div>
|
||
<div class="sidebar-phase"><div class="phase-dot" style="background:var(--border);color:var(--muted);">⬡</div><div style="font-size:12.5px;color:var(--muted);">Architect</div></div>
|
||
<div class="sidebar-phase"><div class="phase-dot" style="background:var(--border);color:var(--muted);">◈</div><div style="font-size:12.5px;color:var(--muted);">Design</div></div>
|
||
<div class="sidebar-phase"><div class="phase-dot" style="background:var(--border);color:var(--muted);">✦</div><div style="font-size:12.5px;color:var(--muted);">Market</div></div>
|
||
<div class="sidebar-phase"><div class="phase-dot" style="background:var(--border);color:var(--muted);">▲</div><div style="font-size:12.5px;color:var(--muted);">Build MVP</div></div>
|
||
</div>
|
||
<div style="padding:11px;background:#fafaff;border:1px solid rgba(99,102,241,0.12);border-radius:9px;">
|
||
<div style="font-size:9px;color:var(--muted);font-weight:600;letter-spacing:0.07em;text-transform:uppercase;margin-bottom:7px;">Progress</div>
|
||
<div style="display:flex;flex-direction:column;gap:5px;">
|
||
<div style="display:flex;align-items:center;gap:6px;"><div id="prog-1" style="width:13px;height:13px;border-radius:50%;background:transparent;border:2px solid #6366F1;flex-shrink:0;"></div><span style="font-size:11px;color:#6366F1;font-weight:600;">Product plan</span></div>
|
||
<div style="display:flex;align-items:center;gap:6px;"><div style="width:13px;height:13px;border-radius:50%;background:var(--border);flex-shrink:0;"></div><span style="font-size:11px;color:var(--muted);">Architecture</span></div>
|
||
<div style="display:flex;align-items:center;gap:6px;"><div style="width:13px;height:13px;border-radius:50%;background:var(--border);flex-shrink:0;"></div><span style="font-size:11px;color:var(--muted);">Product design</span></div>
|
||
<div style="display:flex;align-items:center;gap:6px;"><div style="width:13px;height:13px;border-radius:50%;background:var(--border);flex-shrink:0;"></div><span style="font-size:11px;color:var(--muted);">Marketing</span></div>
|
||
</div>
|
||
</div>
|
||
<div style="border-top:1px solid var(--border);margin-top:14px;padding-top:12px;">
|
||
<button onclick="saveAndExit()" style="display:flex;align-items:center;justify-content:center;gap:7px;width:100%;background:#eef2ff;border:1px solid #e0e7ff;border-radius:8px;padding:9px 10px;cursor:pointer;font-family:'Plus Jakarta Sans',sans-serif;transition:background 0.15s;" onmouseover="this.style.background='#e0e7ff';" onmouseout="this.style.background='#eef2ff';">
|
||
<span style="font-size:12px;font-weight:600;color:#6366F1;">Save & go to dashboard</span>
|
||
</button>
|
||
<button id="dark-toggle" onclick="toggleTheme()" style="margin-top:8px;display:flex;align-items:center;justify-content:center;width:100%;background:transparent;border:1px solid var(--border);border-radius:8px;padding:8px 10px;cursor:pointer;font-family:'Plus Jakarta Sans',sans-serif;font-size:12px;font-weight:500;color:var(--mid);transition:background 0.15s,border-color 0.15s;" onmouseover="this.style.borderColor='#6366F1';this.style.color='#6366F1';" onmouseout="this.style.borderColor='var(--border)';this.style.color='var(--mid)';">🌙 Dark mode</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CHAT -->
|
||
<div class="chat-col" style="flex:1;display:flex;flex-direction:column;border-right:1px solid var(--border);min-width:0;">
|
||
<div style="padding:18px 28px 14px;background:var(--white);border-bottom:1px solid var(--border);flex-shrink:0;">
|
||
<div style="margin-bottom:3px;">
|
||
<div class="f" style="font-size:17px;font-weight:700;color:var(--ink);">Describe</div>
|
||
</div>
|
||
<!-- Journey strip — mobile/tablet only -->
|
||
<div class="mobile-journey">
|
||
<div class="journey-step"><span class="journey-step-label current">◇ Describe</span><span class="journey-step-arrow">›</span></div>
|
||
<div class="journey-step"><span class="journey-step-label">⬡ Architect</span><span class="journey-step-arrow">›</span></div>
|
||
<div class="journey-step"><span class="journey-step-label">◈ Design</span><span class="journey-step-arrow">›</span></div>
|
||
<div class="journey-step"><span class="journey-step-label">✦ Market</span><span class="journey-step-arrow">›</span></div>
|
||
<div class="journey-step"><span class="journey-step-label">▲ Build MVP</span></div>
|
||
</div>
|
||
<div id="chat-subtitle" style="font-size:12px;color:var(--muted);margin-bottom:12px;">Tell me about your idea — I'll turn it into a product plan</div>
|
||
<div id="progress-bar" style="display:flex;gap:4px;">
|
||
<div style="flex:1;height:3px;border-radius:3px;background:var(--border);transition:background 0.3s;" id="pb0"></div>
|
||
<div style="flex:1;height:3px;border-radius:3px;background:var(--border);transition:background 0.3s;" id="pb1"></div>
|
||
<div style="flex:1;height:3px;border-radius:3px;background:var(--border);transition:background 0.3s;" id="pb2"></div>
|
||
<div style="flex:1;height:3px;border-radius:3px;background:var(--border);transition:background 0.3s;" id="pb3"></div>
|
||
<div style="flex:1;height:3px;border-radius:3px;background:var(--border);transition:background 0.3s;" id="pb4"></div>
|
||
<div style="flex:1;height:3px;border-radius:3px;background:var(--border);transition:background 0.3s;" id="pb5"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="chat" style="flex:1;overflow-y:auto;padding:20px 28px;display:flex;flex-direction:column;gap:12px;">
|
||
<!-- id="q-idea" marks where the "idea" question bubble lives (improvement 8) -->
|
||
<!-- id="q-idea" marks where the "idea" question bubble lives (improvement 8) -->
|
||
<div id="q-idea" style="display:flex;align-items:flex-start;gap:10px;"><div class="vibn-avatar" style="width:26px;height:26px;background:linear-gradient(135deg,#2E2A5E,#4338CA);border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px;"><span style="font-size:12px;font-weight:700;color:#FFFFFF;">V</span></div><div class="ai-bubble">Let's build your product plan. I'll ask you 6 quick questions — your answers fill your plan in real time.
|
||
|
||
<strong style="font-weight:700;color:#6366f1;">IDEA:</strong> Describe your product idea in one sentence. What does it do and who is it for?</div></div>
|
||
<div id="scroll-anchor"></div>
|
||
</div>
|
||
|
||
<div style="padding:14px 28px 20px;border-top:1px solid var(--border);background:var(--white);flex-shrink:0;" id="input-area">
|
||
<!-- .chat-input-wrap replaces the inline style (improvement 1) -->
|
||
<div class="chat-input-wrap">
|
||
<input id="chat-input" placeholder="Type your answer…" onkeydown="if(event.key==='Enter')sendMsg()" style="flex:1;border:none;background:transparent;font-family:'Plus Jakarta Sans',sans-serif;font-size:13px;color:var(--ink);outline:none;"/>
|
||
<button id="send-btn" onclick="sendMsg()" style="background:linear-gradient(135deg, #2e2a5e 0%, #4338ca 100%);color:var(--white);border:none;border-radius:7px;padding:7px 14px;font-family:'Plus Jakarta Sans',sans-serif;font-size:12px;font-weight:600;cursor:pointer;">→</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PRD PANEL — uses .prd-panel class; Next button is outside the scroll div (improvement 4) -->
|
||
<div class="prd-panel">
|
||
<div style="flex-shrink:0;margin-bottom:16px;padding-bottom:14px;border-bottom:1px solid #c7d2fe;">
|
||
<div style="font-size:15px;font-weight:800;letter-spacing:0.04em;text-transform:uppercase;color:#4338ca;margin-bottom:5px;">Your product plan</div>
|
||
<div id="prd-subtitle" style="font-size:12px;color:var(--ink3);line-height:1.5;">Your plan builds as you answer.</div>
|
||
</div>
|
||
<div id="prd-content" class="prd-scroll">
|
||
|
||
<!-- idea — pending -->
|
||
<div class="prd-card prd-card-pending" id="prd-card-idea">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
|
||
<div style="flex:1;">
|
||
<div class="prd-field-label prd-field-label-pending" id="lbl-idea">Idea</div>
|
||
<div id="prd-idea" class="prd-field-value prd-field-value-pending">Waiting…</div>
|
||
<span class="prd-hint" id="hint-idea">What does your product do?</span>
|
||
</div>
|
||
<button class="prd-edit-btn" id="btn-idea" onclick="event.stopPropagation();editPRDField('idea')" disabled style="font-size:12px;">✎</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- problem — pending -->
|
||
<div class="prd-card prd-card-pending" id="prd-card-problem">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
|
||
<div style="flex:1;">
|
||
<div class="prd-field-label prd-field-label-pending" id="lbl-problem">Problem</div>
|
||
<div id="prd-problem" class="prd-field-value prd-field-value-pending">Waiting…</div>
|
||
<span class="prd-hint" id="hint-problem">What pain does it solve?</span>
|
||
</div>
|
||
<button class="prd-edit-btn" id="btn-problem" onclick="event.stopPropagation();editPRDField('problem')" disabled style="font-size:12px;">✎</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- users — pending -->
|
||
<div class="prd-card prd-card-pending" id="prd-card-users">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
|
||
<div style="flex:1;">
|
||
<div class="prd-field-label prd-field-label-pending" id="lbl-users">Users</div>
|
||
<div id="prd-users" class="prd-field-value prd-field-value-pending">Waiting…</div>
|
||
<span class="prd-hint" id="hint-users">Who is your ideal first customer?</span>
|
||
</div>
|
||
<button class="prd-edit-btn" id="btn-users" onclick="event.stopPropagation();editPRDField('users')" disabled style="font-size:12px;">✎</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- value — pending -->
|
||
<div class="prd-card prd-card-pending" id="prd-card-value">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
|
||
<div style="flex:1;">
|
||
<div class="prd-field-label prd-field-label-pending" id="lbl-value">Value</div>
|
||
<div id="prd-value" class="prd-field-value prd-field-value-pending">Waiting…</div>
|
||
<span class="prd-hint" id="hint-value">What will they love most?</span>
|
||
</div>
|
||
<button class="prd-edit-btn" id="btn-value" onclick="event.stopPropagation();editPRDField('value')" disabled style="font-size:12px;">✎</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- revenue — pending -->
|
||
<div class="prd-card prd-card-pending" id="prd-card-revenue">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
|
||
<div style="flex:1;">
|
||
<div class="prd-field-label prd-field-label-pending" id="lbl-revenue">Revenue</div>
|
||
<div id="prd-revenue" class="prd-field-value prd-field-value-pending">Waiting…</div>
|
||
<span class="prd-hint" id="hint-revenue">How will you charge for it?</span>
|
||
</div>
|
||
<button class="prd-edit-btn" id="btn-revenue" onclick="event.stopPropagation();editPRDField('revenue')" disabled style="font-size:12px;">✎</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- features — pending -->
|
||
<div class="prd-card prd-card-pending" id="prd-card-features">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
|
||
<div style="flex:1;">
|
||
<div class="prd-field-label prd-field-label-pending" id="lbl-features">Features</div>
|
||
<div id="prd-features" class="prd-field-value prd-field-value-pending">Waiting…</div>
|
||
<span class="prd-hint" id="hint-features">3 must-have features for v1</span>
|
||
</div>
|
||
<button class="prd-edit-btn" id="btn-features" onclick="event.stopPropagation();editPRDField('features')" disabled style="font-size:12px;">✎</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Next button lives OUTSIDE the scrollable area (improvement 4) -->
|
||
<!-- Footer — consistent position across all setup pages -->
|
||
<div class="prd-next-wrap">
|
||
<p style="font-size:11.5px;color:var(--muted);text-align:center;margin:0 0 10px;line-height:1.5;">vibn will now pick the best tech stack for your idea.</p>
|
||
<a href="06_architect.html" style="text-decoration:none;display:block;width:80%;">
|
||
<button id="next-architect-btn" disabled style="width:100%;background:linear-gradient(135deg,#2e2a5e,#4338ca);color:var(--white);border:none;border-radius:8px;padding:12px 14px;font-family:'Plus Jakarta Sans',sans-serif;font-size:13px;font-weight:600;cursor:not-allowed;box-shadow:0 10px 25px rgba(30,27,75,0.15);transition:all 0.2s ease;opacity:0.5;" onmouseover="if(!this.disabled){this.style.cursor='pointer';this.style.boxShadow='0 10px 25px rgba(30,27,75,0.15), 0 0 0 6px rgba(99,102,241,0.15)';this.style.transform='translateY(-1px)';}" onmouseout="if(!this.disabled){this.style.boxShadow='0 10px 25px rgba(30,27,75,0.15)';this.style.transform='translateY(0)';this.style.cursor='pointer';}" onclick="return !this.disabled;">
|
||
Next: Architect
|
||
</button>
|
||
</a>
|
||
<div id="next-btn-hint" style="text-align:center;font-size:11px;color:var(--muted);margin-top:7px;">Answer all 6 questions to continue</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
var step = 0;
|
||
var answeredCount = 0;
|
||
var currentEditingField = null;
|
||
var reAnswerMode = false;
|
||
var reAnswerField = null;
|
||
|
||
// Map field keys to their chat question bubble IDs (improvement 8)
|
||
var fieldToQId = {
|
||
idea: 'q-idea',
|
||
problem: 'q-problem',
|
||
users: 'q-users',
|
||
value: 'q-value',
|
||
revenue: 'q-revenue',
|
||
features: 'q-features'
|
||
};
|
||
|
||
// Jump to the corresponding question bubble in the chat panel (improvement 8)
|
||
function jumpToField(fieldKey){
|
||
var id = fieldToQId[fieldKey];
|
||
if(!id) return;
|
||
var el = document.getElementById(id);
|
||
if(!el) return;
|
||
function highlightBubble(){
|
||
var bubble = el.querySelector('.ai-bubble');
|
||
if(!bubble) return;
|
||
bubble.classList.remove('bubble-highlight');
|
||
void bubble.offsetWidth; // force reflow so re-triggering works
|
||
bubble.classList.add('bubble-highlight');
|
||
setTimeout(function(){ bubble.classList.remove('bubble-highlight'); }, 1400);
|
||
}
|
||
if(window.innerWidth < 900){
|
||
switchTab('chat');
|
||
setTimeout(function(){
|
||
el.scrollIntoView({behavior:'smooth'});
|
||
setTimeout(highlightBubble, 300);
|
||
}, 50);
|
||
} else {
|
||
el.scrollIntoView({behavior:'smooth'});
|
||
setTimeout(highlightBubble, 300);
|
||
}
|
||
}
|
||
|
||
// Promote a PRD card from pending to answered state (improvements 3 & 8)
|
||
function markCardAnswered(fieldKey){
|
||
var card = document.getElementById('prd-card-'+fieldKey);
|
||
if(!card) return;
|
||
card.classList.remove('prd-card-pending');
|
||
card.classList.add('prd-card-answered');
|
||
// Label
|
||
var lbl = document.getElementById('lbl-'+fieldKey);
|
||
if(lbl){
|
||
lbl.classList.remove('prd-field-label-pending');
|
||
lbl.classList.add('prd-field-label-answered');
|
||
}
|
||
// Value
|
||
var val = document.getElementById('prd-'+fieldKey);
|
||
if(val){
|
||
val.classList.remove('prd-field-value-pending');
|
||
val.classList.add('prd-field-value-answered');
|
||
}
|
||
// Bind jump-to click once, and count as newly answered (improvement 8)
|
||
if(!card.dataset.jumpBound){
|
||
card.dataset.jumpBound = '1';
|
||
card.onclick = function(){ jumpToField(fieldKey); };
|
||
card.title = 'Click to jump to this question in chat';
|
||
answeredCount++;
|
||
// First answer: update PRD subtitle
|
||
if(answeredCount === 1){
|
||
var sub = document.getElementById('prd-subtitle');
|
||
if(sub) sub.textContent = 'Answered fields can be edited or clicked to jump to the question.';
|
||
} else if(answeredCount === 6){
|
||
var sub = document.getElementById('prd-subtitle');
|
||
if(sub) sub.textContent = 'Your plan is ready — review and continue to the next step.';
|
||
}
|
||
}
|
||
// Hide pending hint
|
||
var hint = document.getElementById('hint-'+fieldKey);
|
||
if(hint) hint.style.display = 'none';
|
||
// Update progress counter
|
||
updateAnsweredCounter();
|
||
// Show badge + pulse live pill on mobile if user is on the chat tab
|
||
if(window.innerWidth < 900){
|
||
var chatColEl = document.querySelector('.chat-col');
|
||
if(chatColEl && chatColEl.classList.contains('tab-active')){
|
||
var badge = document.getElementById('plan-tab-badge');
|
||
if(badge) badge.classList.remove('hidden');
|
||
var livePill = document.querySelector('.tab-live');
|
||
if(livePill){
|
||
livePill.classList.remove('pulsing');
|
||
void livePill.offsetWidth;
|
||
livePill.classList.add('pulsing');
|
||
setTimeout(function(){ livePill.classList.remove('pulsing'); }, 600);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
function setChatEnabled(enabled){
|
||
var inp = document.getElementById('chat-input');
|
||
var sendBtn = document.getElementById('send-btn');
|
||
if(inp){
|
||
inp.disabled = !enabled;
|
||
inp.style.opacity = enabled ? '1' : '0.6';
|
||
inp.style.cursor = enabled ? 'text' : 'not-allowed';
|
||
if(enabled){
|
||
inp.placeholder = 'Type your answer…';
|
||
} else {
|
||
inp.value = '';
|
||
inp.placeholder = 'All done! Edit any field above or tap "Your Plan" to continue.';
|
||
}
|
||
}
|
||
if(sendBtn){
|
||
sendBtn.disabled = !enabled;
|
||
sendBtn.style.opacity = enabled ? '1' : '0.6';
|
||
sendBtn.style.cursor = enabled ? 'pointer' : 'not-allowed';
|
||
}
|
||
}
|
||
|
||
var QS = [
|
||
{q:"Describe your product idea in one sentence. What does it do and who is it for?",label:"IDEA",key:"idea",ack:"Good — I can work with that.",qid:"q-idea"},
|
||
{q:"What's the painful thing your users are doing today instead?",label:"PROBLEM",key:"problem",ack:"That's a real pain worth solving.",qid:"q-problem"},
|
||
{q:"Describe your ideal first customer in one sentence.",label:"USERS",key:"users",ack:"Clear. That focus will shape everything.",qid:"q-users"},
|
||
{q:"What's the one thing they'll love most about your product?",label:"VALUE",key:"value",ack:"Strong differentiator.",qid:"q-value"},
|
||
{q:"How will you charge for it? Subscription, one-time, or free to start?",label:"REVENUE",key:"revenue",ack:"Makes sense for this type of product.",qid:"q-revenue"},
|
||
{q:"Name the 3 things users must be able to do in version one.",label:"FEATURES",key:"features",ack:"That's everything I need.",qid:"q-features"}
|
||
];
|
||
|
||
// Check if all questions have been answered
|
||
function checkAllQuestionsAnswered(){
|
||
if(step <= 5) return false;
|
||
|
||
var allFilled = document.getElementById('prd-idea').textContent !== 'Waiting…' &&
|
||
document.getElementById('prd-problem').textContent !== 'Waiting…' &&
|
||
document.getElementById('prd-users').textContent !== 'Waiting…' &&
|
||
document.getElementById('prd-value').textContent !== 'Waiting…' &&
|
||
document.getElementById('prd-revenue').textContent !== 'Waiting…' &&
|
||
document.getElementById('prd-features').textContent !== 'Waiting…';
|
||
|
||
return allFilled;
|
||
}
|
||
|
||
|
||
// Update the chat subtitle
|
||
function updateAnsweredCounter(){
|
||
var sub = document.getElementById('chat-subtitle');
|
||
if(sub){
|
||
if(answeredCount === 0) sub.textContent = 'Tell me about your idea — I\'ll turn it into a product plan';
|
||
else if(answeredCount < 6) sub.textContent = answeredCount + ' of 6 questions answered — keep going!';
|
||
else sub.textContent = 'All done! Review your plan or move to the next step.';
|
||
}
|
||
}
|
||
|
||
|
||
// Trigger pulse animation on PRD card update
|
||
function triggerPulsAnimation(fieldKey){
|
||
var card = document.getElementById('prd-card-'+fieldKey);
|
||
if(card){
|
||
card.classList.add('updating');
|
||
setTimeout(function(){
|
||
card.classList.remove('updating');
|
||
}, 600);
|
||
}
|
||
}
|
||
|
||
// Highlight the currently active PRD card
|
||
function setActiveCard(fieldKey){
|
||
document.querySelectorAll('.prd-card').forEach(function(c){ c.classList.remove('prd-card-active'); });
|
||
if(fieldKey){
|
||
var card = document.getElementById('prd-card-'+fieldKey);
|
||
if(card && !card.classList.contains('prd-card-answered')) card.classList.add('prd-card-active');
|
||
}
|
||
}
|
||
|
||
// Enable edit button for a field
|
||
function enableEditButton(fieldKey){
|
||
var btn = document.getElementById('btn-'+fieldKey);
|
||
if(btn) btn.disabled = false;
|
||
}
|
||
|
||
// Edit PRD field - shows modal with options
|
||
function editPRDField(fieldKey){
|
||
currentEditingField = fieldKey;
|
||
var modal = document.getElementById('edit-modal');
|
||
if(modal) modal.classList.add('visible');
|
||
}
|
||
|
||
// Close modal
|
||
function closeEditModal(){
|
||
var modal = document.getElementById('edit-modal');
|
||
if(modal) modal.classList.remove('visible');
|
||
currentEditingField = null;
|
||
}
|
||
|
||
// Re-answer mode: inject a new AI bubble and scroll to the original question (improvement 4)
|
||
function editByReAnswer(){
|
||
if(!currentEditingField) return;
|
||
|
||
var questionField = currentEditingField; // SAVE IT FIRST before closeEditModal() clears it!
|
||
reAnswerMode = true;
|
||
reAnswerField = currentEditingField;
|
||
closeEditModal();
|
||
|
||
// On mobile, switch to the chat tab so the user sees the conversation
|
||
if(window.innerWidth < 900) switchTab('chat');
|
||
|
||
var chat = document.getElementById('chat');
|
||
var anchor = document.getElementById('scroll-anchor');
|
||
|
||
if(!chat || !anchor) {
|
||
console.error('Chat or anchor element not found');
|
||
return;
|
||
}
|
||
|
||
setChatEnabled(true);
|
||
|
||
var q = QS.find(function(item){ return item.key === questionField; });
|
||
|
||
var customQuestions = {
|
||
idea: "Tell me about your idea. What does it do and who is it for?",
|
||
problem: "What's the painful thing your users are doing today instead?"
|
||
};
|
||
|
||
var questionText = q ? q.q : customQuestions[questionField];
|
||
if(!questionText) return;
|
||
|
||
var wrapper = document.createElement('div');
|
||
wrapper.style.cssText = 'display:flex;align-items:flex-start;gap:10px;';
|
||
var avatar = document.createElement('div');
|
||
avatar.className = 'vibn-avatar';
|
||
avatar.style.cssText = 'width:26px;height:26px;background:linear-gradient(135deg,#2E2A5E,#4338CA);border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px;';
|
||
avatar.innerHTML = '<span style="font-size:12px;font-weight:700;color:#FFFFFF;">V</span>';
|
||
|
||
var bubble = document.createElement('div');
|
||
// Use animated bubble class (improvement 5)
|
||
bubble.className = 'ai-bubble bubble bubble-ai';
|
||
bubble.textContent = 'Let me ask that again:\n\n' + questionText;
|
||
|
||
wrapper.appendChild(avatar);
|
||
wrapper.appendChild(bubble);
|
||
chat.insertBefore(wrapper, anchor);
|
||
|
||
// Smooth-scroll the chat to the original question bubble (improvement 4)
|
||
setTimeout(function(){
|
||
var origQId = fieldToQId[questionField];
|
||
if(origQId){
|
||
var origEl = document.getElementById(origQId);
|
||
if(origEl){ origEl.scrollIntoView({behavior:'smooth'}); }
|
||
} else {
|
||
wrapper.scrollIntoView({behavior:'smooth'});
|
||
}
|
||
var inp = document.getElementById('chat-input');
|
||
if(inp) inp.focus();
|
||
}, 100);
|
||
}
|
||
|
||
// Inline edit mode: show text input modal
|
||
function editByInline(){
|
||
if(!currentEditingField) return;
|
||
|
||
var currentValue = document.getElementById('prd-'+currentEditingField).textContent;
|
||
var modal = document.getElementById('edit-modal');
|
||
modal.classList.remove('visible');
|
||
|
||
var inlineModal = document.getElementById('inline-edit-modal');
|
||
if(inlineModal){
|
||
document.getElementById('inline-input').value = currentValue;
|
||
inlineModal.classList.add('visible');
|
||
}
|
||
}
|
||
|
||
// Save inline edit — captures previous value for undo (improvement 9)
|
||
function saveInlineEdit(){
|
||
if(!currentEditingField) return;
|
||
|
||
var newValue = document.getElementById('inline-input').value.trim();
|
||
if(!newValue) return;
|
||
|
||
var fieldEl = document.getElementById('prd-'+currentEditingField);
|
||
fieldEl.textContent = newValue;
|
||
fieldEl.classList.remove('prd-field-value-pending');
|
||
fieldEl.classList.add('prd-field-value-answered');
|
||
markCardAnswered(currentEditingField);
|
||
triggerPulsAnimation(currentEditingField);
|
||
enableEditButton(currentEditingField);
|
||
|
||
var inlineModal = document.getElementById('inline-edit-modal');
|
||
if(inlineModal) inlineModal.classList.remove('visible');
|
||
|
||
currentEditingField = null;
|
||
}
|
||
|
||
// Cancel inline edit
|
||
function cancelInlineEdit(){
|
||
var inlineModal = document.getElementById('inline-edit-modal');
|
||
if(inlineModal) inlineModal.classList.remove('visible');
|
||
}
|
||
|
||
// Save & exit
|
||
function saveAndExit(){
|
||
try { localStorage.setItem('vibn_answered_count', answeredCount); } catch(e){}
|
||
document.getElementById('save-exit-popup').classList.add('visible');
|
||
}
|
||
function cancelSaveExit(){
|
||
document.getElementById('save-exit-popup').classList.remove('visible');
|
||
}
|
||
|
||
// Project type modal
|
||
var projectType = null;
|
||
var projectName = 'My First Project';
|
||
|
||
function selectProjectType(type, el){
|
||
projectType = type;
|
||
document.querySelectorAll('.project-type-opt').forEach(function(o){ o.classList.remove('selected'); });
|
||
el.classList.add('selected');
|
||
updateSetupBtn();
|
||
}
|
||
|
||
function updateSetupBtn(){
|
||
var btn = document.getElementById('project-setup-btn');
|
||
var nameVal = document.getElementById('project-name-input').value.trim();
|
||
var ready = projectType !== null && nameVal.length > 0;
|
||
btn.disabled = !ready;
|
||
btn.style.opacity = ready ? '1' : '0.35';
|
||
}
|
||
|
||
function confirmProjectSetup(){
|
||
var nameVal = document.getElementById('project-name-input').value.trim();
|
||
if(!nameVal || !projectType) return;
|
||
projectName = nameVal;
|
||
try { localStorage.setItem('vibn_project_name', projectName); } catch(e){}
|
||
try { localStorage.setItem('vibn_project_type', projectType); } catch(e){}
|
||
document.getElementById('project-type-modal').classList.add('hidden');
|
||
showSidebarProjectName(projectName);
|
||
|
||
// If the user seeded an idea during signup, auto-send it as their first message
|
||
try {
|
||
var seedIdea = sessionStorage.getItem('vibn_seed_idea');
|
||
if(seedIdea){
|
||
sessionStorage.removeItem('vibn_seed_idea');
|
||
setTimeout(function(){
|
||
var inp = document.getElementById('chat-input');
|
||
inp.value = seedIdea;
|
||
sendMsg();
|
||
}, 400);
|
||
}
|
||
} catch(e){}
|
||
}
|
||
|
||
function showSidebarProjectName(name){
|
||
var el = document.getElementById('sidebar-project-name');
|
||
if(!el || !name) return;
|
||
el.textContent = name;
|
||
el.style.display = 'block';
|
||
}
|
||
|
||
// Show modal only when coming from signup; skip it when navigating from the dashboard
|
||
document.addEventListener('DOMContentLoaded', function(){
|
||
document.getElementById('project-name-input').addEventListener('input', updateSetupBtn);
|
||
|
||
var fromSignup = false;
|
||
var dashName = null;
|
||
try {
|
||
fromSignup = sessionStorage.getItem('vibn_new_project') === '1';
|
||
dashName = sessionStorage.getItem('vibn_dashboard_project');
|
||
} catch(e){}
|
||
|
||
if(dashName) {
|
||
// Coming from dashboard — skip modal, use name & type already chosen there
|
||
try {
|
||
projectName = dashName;
|
||
projectType = sessionStorage.getItem('vibn_dashboard_type') || 'self';
|
||
sessionStorage.removeItem('vibn_dashboard_project');
|
||
sessionStorage.removeItem('vibn_dashboard_type');
|
||
localStorage.setItem('vibn_project_name', projectName);
|
||
localStorage.setItem('vibn_project_type', projectType);
|
||
} catch(e){}
|
||
document.getElementById('project-type-modal').classList.add('hidden');
|
||
showSidebarProjectName(projectName);
|
||
} else {
|
||
// Coming from signup or direct navigation — show the setup modal
|
||
if(fromSignup) {
|
||
try { sessionStorage.removeItem('vibn_new_project'); } catch(e){}
|
||
}
|
||
try { localStorage.removeItem('vibn_project_name'); localStorage.removeItem('vibn_project_type'); localStorage.removeItem('vibn_answered_count'); } catch(e){}
|
||
document.getElementById('project-type-modal').classList.remove('hidden');
|
||
}
|
||
});
|
||
|
||
// Typing indicator helpers
|
||
function showTyping(){
|
||
var chat = document.getElementById('chat');
|
||
var anchor = document.getElementById('scroll-anchor');
|
||
var wrapper = document.createElement('div');
|
||
wrapper.id = 'typing-indicator';
|
||
wrapper.style.cssText = 'display:flex;align-items:flex-start;gap:10px;';
|
||
var avatar = document.createElement('div');
|
||
avatar.className = 'vibn-avatar';
|
||
avatar.style.cssText = 'width:26px;height:26px;background:linear-gradient(135deg,#2E2A5E,#4338CA);border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px;';
|
||
avatar.innerHTML = '<span style="font-size:12px;font-weight:700;color:#FFFFFF;">V</span>';
|
||
var bubble = document.createElement('div');
|
||
bubble.className = 'typing-bubble';
|
||
bubble.innerHTML = '<span></span><span></span><span></span>';
|
||
wrapper.appendChild(avatar);
|
||
wrapper.appendChild(bubble);
|
||
chat.insertBefore(wrapper, anchor);
|
||
anchor.scrollIntoView({behavior:'smooth'});
|
||
}
|
||
function hideTyping(){
|
||
var el = document.getElementById('typing-indicator');
|
||
if(el) el.parentNode.removeChild(el);
|
||
}
|
||
|
||
// Format bubble text: escape HTML, preserve newlines via pre-line, style [LABEL] markers
|
||
function formatBubbleText(text){
|
||
var escaped = text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||
return escaped.replace(/\[([A-Z]+)\]/g,'<strong style="font-weight:700;color:#6366f1;">$1:</strong>');
|
||
}
|
||
|
||
// Helper: create animated AI bubble wrapper (improvement 5)
|
||
function createAiBubble(text, optionalId){
|
||
var wrapper = document.createElement('div');
|
||
wrapper.style.cssText = 'display:flex;align-items:flex-start;gap:10px;';
|
||
if(optionalId) wrapper.id = optionalId;
|
||
var avatar = document.createElement('div');
|
||
avatar.className = 'vibn-avatar';
|
||
avatar.style.cssText = 'width:26px;height:26px;background:linear-gradient(135deg,#2E2A5E,#4338CA);border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px;';
|
||
avatar.innerHTML = '<span style="font-size:12px;font-weight:700;color:#FFFFFF;">V</span>';
|
||
var b = document.createElement('div');
|
||
b.className = 'ai-bubble bubble bubble-ai';
|
||
b.innerHTML = formatBubbleText(text);
|
||
wrapper.appendChild(avatar);
|
||
wrapper.appendChild(b);
|
||
return wrapper;
|
||
}
|
||
|
||
// Helper: create animated user bubble wrapper (improvement 5)
|
||
function createUserBubble(text){
|
||
var wrapper = document.createElement('div');
|
||
wrapper.style.cssText = 'display:flex;flex-direction:column;align-items:flex-end;';
|
||
var b = document.createElement('div');
|
||
b.className = 'user-bubble bubble bubble-user';
|
||
b.textContent = text;
|
||
wrapper.appendChild(b);
|
||
return wrapper;
|
||
}
|
||
|
||
function sendMsg(){
|
||
var inp = document.getElementById('chat-input');
|
||
var val = inp.value.trim();
|
||
if(!val){
|
||
var wrap = inp.closest('.chat-input-wrap');
|
||
if(wrap){ wrap.classList.add('shake'); setTimeout(function(){ wrap.classList.remove('shake'); }, 400); }
|
||
return;
|
||
}
|
||
inp.value = '';
|
||
var chat = document.getElementById('chat');
|
||
var anchor = document.getElementById('scroll-anchor');
|
||
|
||
// User bubble with fade-up animation (improvement 5)
|
||
var u = createUserBubble(val);
|
||
chat.insertBefore(u, anchor);
|
||
|
||
// If in re-answer mode, only update that specific field
|
||
if(reAnswerMode && reAnswerField){
|
||
document.getElementById('prd-'+reAnswerField).textContent = val;
|
||
document.getElementById('prd-'+reAnswerField).classList.remove('prd-field-value-pending');
|
||
document.getElementById('prd-'+reAnswerField).classList.add('prd-field-value-answered');
|
||
markCardAnswered(reAnswerField);
|
||
triggerPulsAnimation(reAnswerField);
|
||
enableEditButton(reAnswerField);
|
||
|
||
|
||
var savedRAField = reAnswerField;
|
||
reAnswerMode = false;
|
||
reAnswerField = null;
|
||
|
||
setTimeout(function(){
|
||
var allAnswered = checkAllQuestionsAnswered();
|
||
if(allAnswered){
|
||
var aiFinal = createAiBubble("Updated! Feel free to keep editing, or hit Next when you're ready.");
|
||
chat.insertBefore(aiFinal, anchor);
|
||
setChatEnabled(false);
|
||
if(window.innerWidth < 900){ showPlanLoading(); setTimeout(function(){ switchTab('plan'); }, 3000); }
|
||
anchor.scrollIntoView({behavior:'smooth'});
|
||
return;
|
||
}
|
||
|
||
// Acknowledgment bubble (improvement 5)
|
||
var aiAck = createAiBubble("Got it, I've updated that field.");
|
||
chat.insertBefore(aiAck, anchor);
|
||
|
||
// Next question bubble — assign its id so jump-to works (improvement 8)
|
||
var nextQ = QS[step];
|
||
var nextText = nextQ ? '[' + nextQ.label + '] ' + nextQ.q : 'Your product plan is ready on the right.';
|
||
var aiNext = createAiBubble(nextText, nextQ ? nextQ.qid : null);
|
||
chat.insertBefore(aiNext, anchor);
|
||
anchor.scrollIntoView({behavior:'smooth'});
|
||
}, 100);
|
||
return;
|
||
}
|
||
|
||
// Update PRD
|
||
var q = QS[step];
|
||
if(q && document.getElementById('prd-'+q.key) && !reAnswerMode){
|
||
document.getElementById('prd-'+q.key).textContent = val;
|
||
document.getElementById('prd-'+q.key).classList.remove('prd-field-value-pending');
|
||
document.getElementById('prd-'+q.key).classList.add('prd-field-value-answered');
|
||
markCardAnswered(q.key);
|
||
triggerPulsAnimation(q.key);
|
||
enableEditButton(q.key);
|
||
setActiveCard(null);
|
||
}
|
||
|
||
// Update progress bar + label
|
||
if(step < 6){
|
||
var pb = document.getElementById('pb'+step); if(pb) pb.style.background = 'var(--accent-primary)';
|
||
}
|
||
|
||
showTyping();
|
||
setTimeout(function(){
|
||
var nextQ = QS[step+1];
|
||
|
||
step++;
|
||
|
||
if(checkAllQuestionsAnswered()){
|
||
setTimeout(function(){
|
||
hideTyping();
|
||
var btn = document.getElementById('next-architect-btn');
|
||
if(btn){ btn.disabled = false; btn.style.opacity = '1'; btn.style.cursor = 'pointer'; }
|
||
var hint = document.getElementById('next-btn-hint'); if(hint) hint.style.display = 'none';
|
||
document.getElementById('prog-1').style.background = 'var(--accent-primary)';
|
||
document.getElementById('prog-1').innerHTML = '<span style="color:var(--white);font-size:7px;font-weight:900;">✓</span>';
|
||
var doneBubble = createAiBubble("That's everything I need. You can edit your plan or go to the next step!");
|
||
chat.insertBefore(doneBubble, anchor);
|
||
setChatEnabled(false);
|
||
anchor.scrollIntoView({behavior:'smooth'});
|
||
if(window.innerWidth < 900){ showPlanLoading(); setTimeout(function(){ switchTab('plan'); }, 3000); }
|
||
}, 600);
|
||
} else if(nextQ) {
|
||
// Ack bubble — hide typing first
|
||
setTimeout(function(){
|
||
hideTyping();
|
||
if(q){
|
||
var ackBubble = createAiBubble(q.ack);
|
||
chat.insertBefore(ackBubble, anchor);
|
||
anchor.scrollIntoView({behavior:'smooth'});
|
||
}
|
||
// Show typing again before next question
|
||
showTyping();
|
||
setTimeout(function(){
|
||
hideTyping();
|
||
var questionBubble = createAiBubble('[' + nextQ.label + '] ' + nextQ.q, nextQ.qid);
|
||
chat.insertBefore(questionBubble, anchor);
|
||
setActiveCard(nextQ.key);
|
||
anchor.scrollIntoView({behavior:'smooth'});
|
||
}, 600);
|
||
}, 600);
|
||
}
|
||
|
||
anchor.scrollIntoView({behavior:'smooth'});
|
||
}, 500);
|
||
anchor.scrollIntoView({behavior:'smooth'});
|
||
}
|
||
|
||
// ── Plan loading popup helpers ────────────────────────────────────────────────
|
||
function showPlanLoading(){
|
||
if(window.innerWidth >= 900) return;
|
||
var popup = document.getElementById('plan-loading-popup');
|
||
if(popup) popup.classList.add('visible');
|
||
}
|
||
function hidePlanLoading(){
|
||
var popup = document.getElementById('plan-loading-popup');
|
||
if(popup) popup.classList.remove('visible');
|
||
}
|
||
|
||
// ── Mobile tab switching ──────────────────────────────────────────────────────
|
||
function switchTab(tab){
|
||
hidePlanLoading();
|
||
var chatCol = document.querySelector('.chat-col');
|
||
var prdPanel = document.querySelector('.prd-panel');
|
||
var tabChat = document.getElementById('tab-chat');
|
||
var tabPlan = document.getElementById('tab-plan');
|
||
if(tab === 'chat'){
|
||
chatCol.classList.add('tab-active');
|
||
prdPanel.classList.remove('tab-active');
|
||
tabChat.classList.add('active');
|
||
tabPlan.classList.remove('active');
|
||
} else {
|
||
prdPanel.classList.add('tab-active');
|
||
chatCol.classList.remove('tab-active');
|
||
tabPlan.classList.add('active');
|
||
tabChat.classList.remove('active');
|
||
var badge = document.getElementById('plan-tab-badge');
|
||
if(badge) badge.classList.add('hidden');
|
||
}
|
||
}
|
||
// Initialize chat tab as active on mobile/narrow
|
||
function initTabsIfNeeded(){
|
||
if(window.innerWidth < 900){
|
||
var chatCol = document.querySelector('.chat-col');
|
||
var prdPanel = document.querySelector('.prd-panel');
|
||
if(!chatCol.classList.contains('tab-active') && !prdPanel.classList.contains('tab-active')){
|
||
chatCol.classList.add('tab-active');
|
||
document.getElementById('tab-chat').classList.add('active');
|
||
document.getElementById('tab-plan').classList.remove('active');
|
||
}
|
||
}
|
||
}
|
||
initTabsIfNeeded();
|
||
window.addEventListener('resize', initTabsIfNeeded);
|
||
setActiveCard(QS[0].key);
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
// Initialize chat input enabled by default
|
||
setChatEnabled(true);
|
||
|
||
// ── Dark mode ──
|
||
function toggleTheme() {
|
||
const html = document.documentElement;
|
||
const isDark = html.dataset.theme === 'dark';
|
||
html.dataset.theme = isDark ? '' : 'dark';
|
||
document.getElementById('dark-toggle').textContent = isDark ? '🌙 Dark mode' : '☀️ Light mode';
|
||
localStorage.setItem('vibn-theme', isDark ? '' : 'dark');
|
||
}
|
||
(function(){
|
||
const saved = localStorage.getItem('vibn-theme');
|
||
if (saved === 'dark') {
|
||
document.documentElement.dataset.theme = 'dark';
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const btn = document.getElementById('dark-toggle');
|
||
if (btn) btn.textContent = '☀️ Light mode';
|
||
});
|
||
}
|
||
})();
|
||
</script>
|
||
|
||
<!-- Edit modal - shows two options -->
|
||
<div class="edit-modal" id="edit-modal">
|
||
<div class="edit-modal-box">
|
||
<h3>Edit this field</h3>
|
||
<p>How would you like to update this?</p>
|
||
<div class="edit-options">
|
||
<button class="btn-option" onclick="editByReAnswer()">
|
||
<div class="btn-option-title">Re-answer the question</div>
|
||
<div class="btn-option-desc">I'll ask the question again in chat, and you can refine your answer.</div>
|
||
</button>
|
||
<button class="btn-option" onclick="editByInline()">
|
||
<div class="btn-option-title">Edit directly</div>
|
||
<div class="btn-option-desc">Quickly update the text without re-answering the whole question.</div>
|
||
</button>
|
||
</div>
|
||
<div class="edit-modal-actions">
|
||
<button class="btn-secondary" onclick="closeEditModal()">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Inline edit modal -->
|
||
<div class="edit-modal" id="inline-edit-modal">
|
||
<div class="edit-modal-box">
|
||
<h3>Edit field</h3>
|
||
<p style="margin-bottom:14px;">Update the text below:</p>
|
||
<input id="inline-input" type="text" style="width:100%;border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-family:'Plus Jakarta Sans',sans-serif;font-size:13px;color:var(--ink);margin-bottom:18px;outline:none;" />
|
||
<div class="edit-modal-actions">
|
||
<button class="btn-secondary" onclick="cancelInlineEdit()">Cancel</button>
|
||
<button class="btn-primary" onclick="saveInlineEdit()">Save</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</body></html>
|