[fluentform id=”3″]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Junis Pro - Interactive Project Estimator</title>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Google Fonts: Inter -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- FontAwesome Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: #09090b;
color: #f4f4f5;
-webkit-tap-highlight-color: transparent;
}
/* Smooth Scrollbar */
#chat-scroll::-webkit-scrollbar {
width: 4px;
}
#chat-scroll::-webkit-scrollbar-track {
background: transparent;
}
#chat-scroll::-webkit-scrollbar-thumb {
background: #27272a;
border-radius: 4px;
}
/* Keyframe Animations */
@keyframes slideUp {
from { opacity: 0; transform: translateY(12px) scale(0.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes pulseGlow {
0%, 100% { box-shadow: 0 0 15px rgba(59, 130, 246, 0.2); }
50% { box-shadow: 0 0 25px rgba(59, 130, 246, 0.4); }
}
.animate-slide-up {
animation: slideUp 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.glow-effect {
animation: pulseGlow 3s infinite ease-in-out;
}
.typing-dot {
animation: typingBounce 1.4s infinite ease-in-out both;
}
.typing-dot:nth-child(1) { animation-delay: 0s; }
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes typingBounce {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
40% { transform: scale(1); opacity: 1; }
}
</style>
</head>
<body class="h-screen w-full flex justify-center items-center bg-zinc-950 p-0 sm:p-4 overflow-hidden">
<div class="w-full max-w-md h-full sm:h-[92vh] sm:rounded-3xl border border-zinc-800/80 bg-zinc-900/90 shadow-2xl flex flex-col relative overflow-hidden backdrop-blur-xl">
<!-- Header -->
<header class="px-5 py-4 bg-zinc-900/80 border-b border-zinc-800/80 flex items-center justify-between shrink-0 z-20">
<div class="flex items-center gap-3">
<div class="relative">
<div class="w-10 h-10 rounded-2xl bg-gradient-to-tr from-blue-600 via-indigo-600 to-violet-500 flex items-center justify-center font-extrabold text-white shadow-md shadow-blue-500/20 text-sm">
JP
</div>
<span class="absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 bg-emerald-500 border-2 border-zinc-900 rounded-full"></span>
</div>
<div>
<div class="flex items-center gap-1.5">
<h1 class="font-bold text-sm text-zinc-100">Junis Pro Assistant</h1>
<span class="text-[10px] bg-blue-500/10 text-blue-400 font-medium px-2 py-0.5 rounded-full border border-blue-500/20">AI Lead</span>
</div>
<p class="text-xs text-zinc-400 flex items-center gap-1">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-ping"></span> Online • Instant Estimator
</p>
</div>
</div>
<!-- Live Estimate Badge -->
<div class="text-right">
<span class="text-[10px] uppercase tracking-wider text-zinc-500 font-semibold block">Est. Range</span>
<span id="header-price-display" class="text-sm font-extrabold text-blue-400 transition-all">$0</span>
</div>
</header>
<!-- Chat Container -->
<main id="chat-scroll" class="flex-1 overflow-y-auto p-4 space-y-4 relative">
<!-- Conversation history dynamically populates here -->
</main>
<!-- Typing Indicator Overlay -->
<div id="typing-indicator" class="hidden px-5 py-2 flex items-center gap-2 text-zinc-400 text-xs shrink-0">
<div class="w-6 h-6 rounded-full bg-blue-600/20 border border-blue-500/30 flex items-center justify-center text-[10px] text-blue-400 font-bold">JP</div>
<div class="bg-zinc-800/80 px-3 py-2 rounded-2xl rounded-tl-sm flex items-center gap-1.5">
<span class="w-1.5 h-1.5 bg-zinc-400 rounded-full typing-dot"></span>
<span class="w-1.5 h-1.5 bg-zinc-400 rounded-full typing-dot"></span>
<span class="w-1.5 h-1.5 bg-zinc-400 rounded-full typing-dot"></span>
</div>
</div>
<!-- Action/Controls Footer -->
<footer id="controls-panel" class="p-4 bg-zinc-900/90 border-t border-zinc-800/80 shrink-0 z-20">
<!-- Options, buttons or form inputs render dynamically here -->
</footer>
<!-- Toast Notification Overlay -->
<div id="toast" class="absolute top-16 left-1/2 -translate-x-1/2 bg-zinc-800 border border-zinc-700 text-zinc-100 px-4 py-2 rounded-xl text-xs font-medium shadow-xl opacity-0 transition-all pointer-events-none z-50 flex items-center gap-2">
<i id="toast-icon" class="fa-solid fa-circle-check text-emerald-400"></i>
<span id="toast-text">Notification</span>
</div>
</div>
<script>
const quizState = {
step: 0,
selections: {
service: null,
serviceCost: 0,
scope: null,
scopeCost: 0,
design: null,
designCost: 0,
speed: null,
speedMultiplier: 1.0,
retainer: null,
retainerCost: 0
},
leadInfo: {
name: '',
email: '',
company: ''
}
};
const conversationFlow = [
{
id: 'service',
question: "Hey! 👋 Welcome to Junis Pro. What type of digital solution are you planning to build?",
options: [
{ label: "Custom Website", detail: "From $1,500", icon: "fa-laptop-code", cost: 1500, key: "Custom Website" },
{ label: "Web Application / SaaS", detail: "From $3,500", icon: "fa-layer-group", cost: 3500, key: "Web Application" },
{ label: "E-Commerce Store", detail: "From $2,500", icon: "fa-cart-shopping", cost: 2500, key: "E-Commerce Store" },
{ label: "Brand Identity", detail: "From $1,000", icon: "fa-palette", cost: 1000, key: "Brand Identity" }
]
},
{
id: 'scope',
question: "Awesome! What's the size and page structure of this project?",
options: [
{ label: "1–4 Landing Pages", detail: "+$400", icon: "fa-file", cost: 400, key: "1-4 Landing Pages" },
{ label: "5–10 Core Pages", detail: "+$800", icon: "fa-copy", cost: 800, key: "5-10 Core Pages" },
{ label: "11–20+ Pages / Portal", detail: "+$1,800", icon: "fa-sitemap", cost: 1800, key: "11-20+ Pages" }
]
},
{
id: 'design',
question: "Got it! What level of visual fidelity and GSAP animation depth are you aiming for?",
options: [
{ label: "Clean & Minimal", detail: "Standard UI (+$500)", icon: "fa-wand-magic-sparkles", cost: 500, key: "Clean & Minimal" },
{ label: "Custom UI + GSAP", detail: "Rich Animations (+$1,200)", icon: "fa-bolt", cost: 1200, key: "Custom UI + GSAP" },
{ label: "High-End 3D & Interactive", detail: "Immersive WebGL (+$2,500)", icon: "fa-cube", cost: 2500, key: "High-End 3D" }
]
},
{
id: 'speed',
question: "What is your target launch timeline?",
options: [
{ label: "Standard Delivery", detail: "4–6 Wks (1.0x)", icon: "fa-calendar-days", multiplier: 1.0, key: "Standard (4-6 Wks)" },
{ label: "Express Rush", detail: "< 2 Wks (+35% Fee)", icon: "fa-rocket", multiplier: 1.35, key: "Express Rush (<2 Wks)" }
]
},
{
id: 'retainer',
question: "Do you need ongoing post-launch maintenance, hosting, or SEO growth?",
options: [
{ label: "No Retainer", detail: "Internal Handoff ($0)", icon: "fa-circle-xmark", cost: 0, key: "No Retainer" },
{ label: "Essential Care & Hosting", detail: "+$200/mo", icon: "fa-shield-halved", cost: 200, key: "Essential Care ($200/mo)" },
{ label: "Full Growth Retainer", detail: "+$800/mo", icon: "fa-chart-line", cost: 800, key: "Growth Retainer ($800/mo)" }
]
}
];
const chatScroll = document.getElementById('chat-scroll');
const controlsPanel = document.getElementById('controls-panel');
const typingIndicator = document.getElementById('typing-indicator');
const headerPriceDisplay = document.getElementById('header-price-display');
function calculateEstimate() {
const s = quizState.selections;
const baseTotal = (s.serviceCost + s.scopeCost + s.designCost) * s.speedMultiplier + s.retainerCost;
if (baseTotal === 0) return { low: 0, high: 0, text: "$0" };
const low = Math.round((baseTotal * 0.95) / 50) * 50;
const high = Math.round((baseTotal * 1.15) / 50) * 50;
return {
low,
high,
text: `$${low.toLocaleString()} – $${high.toLocaleString()}`
};
}
function updateHeaderPrice() {
const est = calculateEstimate();
headerPriceDisplay.textContent = est.text;
}
function showToast(text, iconClass = "fa-circle-check") {
const toast = document.getElementById('toast');
document.getElementById('toast-text').textContent = text;
document.getElementById('toast-icon').className = `fa-solid ${iconClass} text-emerald-400`;
toast.classList.remove('opacity-0', 'pointer-events-none');
setTimeout(() => {
toast.classList.add('opacity-0', 'pointer-events-none');
}, 3000);
}
function scrollToBottom() {
chatScroll.scrollTo({
top: chatScroll.scrollHeight,
behavior: 'smooth'
});
}
function showTyping(callback, delay = 600) {
typingIndicator.classList.remove('hidden');
scrollToBottom();
setTimeout(() => {
typingIndicator.classList.add('hidden');
if (callback) callback();
}, delay);
}
function appendBotMessage(text, callback) {
showTyping(() => {
const msgDiv = document.createElement('div');
msgDiv.className = "flex gap-3 items-start animate-slide-up";
msgDiv.innerHTML = `
<div class="w-8 h-8 rounded-full bg-gradient-to-tr from-blue-600 to-indigo-600 flex items-center justify-center shrink-0 text-white font-bold text-xs shadow-md shadow-blue-500/20">
JP
</div>
<div class="bg-zinc-900 border border-zinc-800/80 text-zinc-100 p-3.5 rounded-2xl rounded-tl-sm text-sm leading-relaxed max-w-[85%] shadow-sm">
${text}
</div>
`;
chatScroll.appendChild(msgDiv);
scrollToBottom();
if (callback) callback();
});
}
function appendUserMessage(text) {
const msgDiv = document.createElement('div');
msgDiv.className = "flex justify-end animate-slide-up";
msgDiv.innerHTML = `
<div class="bg-blue-600 text-white p-3.5 rounded-2xl rounded-tr-sm text-sm font-medium max-w-[85%] shadow-md shadow-blue-600/20">
${text}
</div>
`;
chatScroll.appendChild(msgDiv);
scrollToBottom();
}
function renderStepOptions(stepObj) {
controlsPanel.innerHTML = '';
const grid = document.createElement('div');
grid.className = "grid grid-cols-1 gap-2 animate-slide-up";
stepObj.options.forEach(opt => {
const btn = document.createElement('button');
btn.className = "group w-full flex items-center justify-between bg-zinc-800/60 hover:bg-zinc-800 active:bg-blue-600/20 active:border-blue-500 border border-zinc-700/60 p-3 rounded-2xl text-left transition-all duration-200";
btn.innerHTML = `
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-xl bg-zinc-700/50 group-hover:bg-blue-500/20 group-hover:text-blue-400 flex items-center justify-center text-zinc-400 text-xs transition-colors">
<i class="fa-solid ${opt.icon}"></i>
</div>
<span class="text-sm font-medium text-zinc-200 group-hover:text-white">${opt.label}</span>
</div>
<span class="text-xs font-semibold text-zinc-400 font-mono bg-zinc-900/60 px-2.5 py-1 rounded-lg border border-zinc-800">${opt.detail}</span>
`;
btn.onclick = () => handleOptionSelection(stepObj.id, opt);
grid.appendChild(btn);
});
controlsPanel.appendChild(grid);
}
function handleOptionSelection(stepId, option) {
// Store value in state
if (stepId === 'service') { quizState.selections.service = option.key; quizState.selections.serviceCost = option.cost; }
if (stepId === 'scope') { quizState.selections.scope = option.key; quizState.selections.scopeCost = option.cost; }
if (stepId === 'design') { quizState.selections.design = option.key; quizState.selections.designCost = option.cost; }
if (stepId === 'speed') { quizState.selections.speed = option.key; quizState.selections.speedMultiplier = option.multiplier; }
if (stepId === 'retainer') { quizState.selections.retainer = option.key; quizState.selections.retainerCost = option.cost; }
updateHeaderPrice();
appendUserMessage(`${option.label} (${option.detail})`);
// Clear current controls
controlsPanel.innerHTML = '';
quizState.step++;
if (quizState.step < conversationFlow.length) {
const nextStepObj = conversationFlow[quizState.step];
appendBotMessage(nextStepObj.question, () => {
renderStepOptions(nextStepObj);
});
} else {
renderSummaryAndForm();
}
}
function renderSummaryAndForm() {
const est = calculateEstimate();
const summaryHTML = `
<div class="space-y-3">
<p>Fantastic! We have calculated your estimate based on your custom requirements:</p>
<div class="bg-zinc-950/80 border border-zinc-800 p-4 rounded-2xl space-y-2 text-xs">
<div class="flex justify-between text-zinc-400"><span>Build Type:</span> <strong class="text-zinc-200">${quizState.selections.service}</strong></div>
<div class="flex justify-between text-zinc-400"><span>Scope:</span> <strong class="text-zinc-200">${quizState.selections.scope}</strong></div>
<div class="flex justify-between text-zinc-400"><span>Design / Motion:</span> <strong class="text-zinc-200">${quizState.selections.design}</strong></div>
<div class="flex justify-between text-zinc-400"><span>Speed:</span> <strong class="text-zinc-200">${quizState.selections.speed}</strong></div>
<div class="flex justify-between text-zinc-400"><span>Retainer:</span> <strong class="text-zinc-200">${quizState.selections.retainer}</strong></div>
<div class="border-t border-zinc-800 pt-2 flex justify-between items-center text-sm font-bold text-zinc-100">
<span>Estimated Total:</span>
<span class="text-blue-400 text-base font-extrabold">${est.text}</span>
</div>
</div>
</div>
`;
appendBotMessage(summaryHTML, () => {
appendBotMessage("Where should we send your official formal proposal?", () => {
renderLeadCaptureForm();
});
});
}
function renderLeadCaptureForm() {
controlsPanel.innerHTML = `
<form id="lead-form" onsubmit="handleFormSubmit(event)" class="space-y-2.5 animate-slide-up">
<div>
<input type="text" id="user-name" required placeholder="Full Name"
class="w-full bg-zinc-800/80 border border-zinc-700/80 rounded-xl px-3.5 py-2.5 text-xs text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-blue-500 transition-colors" />
</div>
<div>
<input type="email" id="user-email" required placeholder="Work Email Address"
class="w-full bg-zinc-800/80 border border-zinc-700/80 rounded-xl px-3.5 py-2.5 text-xs text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-blue-500 transition-colors" />
</div>
<div>
<input type="text" id="user-company" placeholder="Company Name (Optional)"
class="w-full bg-zinc-800/80 border border-zinc-700/80 rounded-xl px-3.5 py-2.5 text-xs text-zinc-100 placeholder-zinc-500 focus:outline-none focus:border-blue-500 transition-colors" />
</div>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-500 active:scale-[0.98] text-white font-semibold py-3 rounded-xl text-xs shadow-lg shadow-blue-600/20 transition-all flex items-center justify-center gap-2">
<span>Send Formal Proposal Request</span>
<i class="fa-solid fa-paper-plane"></i>
</button>
</form>
`;
}
function handleFormSubmit(e) {
e.preventDefault();
const name = document.getElementById('user-name').value;
const email = document.getElementById('user-email').value;
const company = document.getElementById('user-company').value || 'N/A';
quizState.leadInfo = { name, email, company };
controlsPanel.innerHTML = '';
appendUserMessage(`Proposal request for ${name} (${email})`);
appendBotMessage(`Thank you, ${name}! 🎉 We've logged your preferences. A senior technical strategist from Junis Pro will review your specs and email your tailored proposal within 24 hours.`, () => {
showToast("Proposal Request Sent!");
// Final Action Buttons
controlsPanel.innerHTML = `
<div class="flex gap-2">
<button onclick="resetQuiz()" class="flex-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 font-medium py-2.5 rounded-xl text-xs transition-colors flex items-center justify-center gap-2">
<i class="fa-solid fa-rotate-right"></i> Start New Estimate
</button>
</div>
`;
});
}
function resetQuiz() {
chatScroll.innerHTML = '';
controlsPanel.innerHTML = '';
quizState.step = 0;
quizState.selections = {
service: null, serviceCost: 0,
scope: null, scopeCost: 0,
design: null, designCost: 0,
speed: null, speedMultiplier: 1.0,
retainer: null, retainerCost: 0
};
updateHeaderPrice();
startConversation();
}
function startConversation() {
const firstStep = conversationFlow[0];
appendBotMessage(firstStep.question, () => {
renderStepOptions(firstStep);
});
}
// Initialize application
window.onload = startConversation;
</script>
</body>
</html>