<!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" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<title>BrutallyHonest.ai - Transform Your Bullsh*t Into Breakthroughs</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
:root {
/* Light Theme (Default) */
--black: #FFFFFF; --black-soft: #F8F9FA; --black-card: #F1F3F4; --border: #E8EAED; --border-light: #DADCE0; --white: #000000; --gray: #5F6368; --gray-light: #3C4043; --gray-dark: #80868B;
--brand: #5D5CDE; --brand-light: #7C7CE8; --brand-dark: #4B4BC7; --accent: #EF4444; --success: #22C55E; --warning: #F59E0B; --gold: #FCD34D;
--gradient-brand: linear-gradient(135deg, #5D5CDE 0%, #7C7CE8 100%); --gradient-fire: linear-gradient(135deg, #EF4444 0%, #F59E0B 100%); --gradient-success: linear-gradient(135deg, #22C55E 0%, #4ADE80 100%);
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.08); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.12), 0 1px 2px -1px rgb(0 0 0 / 0.12); --shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06); --shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05); --shadow-glow: 0 0 0 1px rgba(93, 92, 222, 0.3), 0 0 20px rgba(93, 92, 222, 0.2);
}
[data-theme="dark"] {
--black: #0F0F0F; --black-soft: #1A1A1A; --black-card: #262626; --border: #404040; --border-light: #525252; --white: #FFFFFF; --gray: #A3A3A3; --gray-light: #D4D4D4; --gray-dark: #737373;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html { position: static; height: auto; overflow: auto; }
body { background: var(--black); color: var(--white); line-height: 1.6; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transition: background-color 0.5s ease, color 0.5s ease; min-height: 100vh; overflow-x: hidden; position: relative; }
.app { max-width: 1200px; margin: 0 auto; padding: 16px; min-height: 100vh; position: relative; width: 100%; transition: all 0.3s ease; }
/* Toast Container */
.toast-container { position: fixed; top: 20px; right: 20px; z-index: 10000; pointer-events: none; }
.toast { background: var(--black-soft); border: 1px solid var(--border); border-radius: 8px; padding: 12px 16px; margin-bottom: 8px; box-shadow: var(--shadow-lg); backdrop-filter: blur(8px); animation: toastSlideIn 0.3s ease-out; pointer-events: all; font-size: 14px; max-width: 300px; }
.toast.success { border-color: var(--success); background: rgba(34, 197, 94, 0.1); }
.toast.error { border-color: var(--accent); background: rgba(239, 68, 68, 0.1); }
@keyframes toastSlideIn { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } }
/* FULLSCREEN MODE */
.app.fullscreen { position: fixed; top: 0; left: 0; right: 0; bottom: 0; max-width: none; padding: 0; z-index: 1000; background: var(--black); }
.app.fullscreen .header,
.app.fullscreen .explainer,
.app.fullscreen .model-selection-container,
.app.fullscreen .persona-selection-container { display: none; }
.app.fullscreen .chat-container { height: 100vh; border-radius: 0; border: none; }
.floating-panel { position: fixed; top: 60px; right: 16px; width: 250px; background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 16px; z-index: 10002; display: none; box-shadow: var(--shadow-xl); }
.floating-panel.active { display: block; }
.floating-panel h3 { font-size: 14px; font-weight: 600; margin-bottom: 12px; }
.floating-panel select { width: 100%; background: var(--black-card); border: 1px solid var(--border); border-radius: 8px; padding: 8px; color: var(--white); font-size: 12px; margin-bottom: 8px; }
/* KEYBOARD SHORTCUTS PANEL */
.shortcuts-panel { position: fixed; bottom: 20px; left: 20px; background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 16px; z-index: 10000; opacity: 0; visibility: hidden; transition: all 0.3s ease; font-size: 12px; max-width: 300px; }
.shortcuts-panel.show { opacity: 1; visibility: visible; }
.shortcuts-panel h3 { font-size: 14px; font-weight: 600; margin-bottom: 8px; color: var(--brand); }
.shortcut-item { display: flex; justify-content: space-between; margin-bottom: 4px; color: var(--gray-light); }
.shortcut-key { background: var(--black-card); border: 1px solid var(--border); border-radius: 4px; padding: 2px 6px; font-family: monospace; font-size: 11px; }
/* Feedback & Payment Modals */
.feedback-modal, .payment-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 1001;
}
.feedback-content, .payment-content {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
width: 90%;
max-width: 400px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: var(--white);
}
.feedback-content h3, .payment-content h3 {
margin-bottom: 16px;
color: var(--brand);
}
.feedback-content textarea {
width: 100%;
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px;
color: var(--white);
resize: vertical;
margin-bottom: 16px;
}
.feedback-content textarea::placeholder {
color: var(--gray);
}
.modal-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.modal-btn {
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
.modal-btn.secondary {
background: transparent;
border: 1px solid var(--border);
color: var(--white);
}
.modal-btn.primary {
background: var(--brand);
border: none;
color: var(--white);
}
.modal-btn:hover {
transform: translateY(-1px);
}
/* Onboarding Overlay */
.onboarding-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.onboarding-content {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 16px;
padding: 32px;
max-width: 500px;
text-align: center;
animation: slideIn 0.3s ease-out;
color: var(--white);
}
@keyframes slideIn {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.onboarding-step {
display: none;
}
.onboarding-step.active {
display: block;
}
.progress-dots {
display: flex;
justify-content: center;
gap: 8px;
margin: 20px 0;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--border);
}
.dot.active {
background: var(--brand);
}
.onboarding-btn {
background: var(--brand);
color: var(--white);
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
transition: all 0.2s ease;
}
.onboarding-btn:hover {
transform: translateY(-1px);
}
/* Credit Display */
.credit-display {
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px;
text-align: center;
margin: 16px 0;
font-weight: 600;
}
.credit-warning {
background: rgba(245, 158, 11, 0.1);
border: 1px solid var(--warning);
color: var(--warning);
}
/* --- UI COMPONENTS --- */
.copy-button { position: absolute; top: 8px; right: 8px; background: var(--black-card); border: 1px solid var(--border); border-radius: 4px; padding: 4px 6px; font-size: 10px; cursor: pointer; opacity: 0; transition: all 0.2s ease; color: var(--gray); font-weight: 500; }
.message.bot:hover .copy-button { opacity: 1; }
.copy-button:hover { background: var(--brand); color: var(--white); border-color: var(--brand); }
.copy-button.copied { background: var(--success); color: var(--white); border-color: var(--success); }
.credits-container { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; display: flex; align-items: center; gap: 12px; cursor: pointer; transition: all 0.2s ease; }
.credits-container:hover { border-color: var(--brand); transform: translateY(-1px); }
.credits-progress { flex: 1; min-width: 80px; }
.credits-bar { width: 100%; height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; margin-top: 4px; }
.credits-fill { height: 100%; background: var(--gradient-brand); border-radius: 2px; transition: width 0.5s ease; }
.credits-text { font-size: 12px; color: var(--gray); }
.credits-number { font-weight: 700; color: var(--brand); }
.loading-placeholder { background: var(--black-card); border-radius: 8px; padding: 16px; text-align: center; color: var(--gray); font-size: 14px; }
.loading-animation { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--border); border-radius: 50%; border-top-color: var(--brand); animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.premium-badge { position: absolute; top: -4px; right: -4px; background: var(--gradient-brand); color: var(--white); border-radius: 50%; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; }
.model-section { margin-bottom: 16px; }
.model-section-title { font-size: 10px; font-weight: 600; color: var(--gray); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; padding-left: 8px; }
.model-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
.model-option { background: var(--black-card); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 12px; text-align: center; position: relative; }
.model-option:hover { border-color: var(--brand); transform: translateY(-1px); }
.model-option.active { background: rgba(93, 92, 222, 0.1); border-color: var(--brand); box-shadow: var(--shadow-glow); }
.model-name { font-size: 16px; font-weight: 600; margin-bottom: 2px; }
.model-cost { font-size: 14px; text-transform: uppercase; opacity: 0.8; }
.model-cost.free { color: var(--success); } .model-cost.paid { color: var(--warning); }
.persona-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
.persona-card { background: var(--black-card); border: 2px solid var(--border); border-radius: 12px; padding: 16px 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; position: relative; }
.persona-card:hover { border-color: var(--brand); transform: translateY(-2px); }
.persona-card.active { border-color: var(--brand); background: rgba(93, 92, 222, 0.05); box-shadow: var(--shadow-glow); }
.persona-card.premium { border-color: var(--gold); }
.persona-avatar { width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; color: var(--white); position: relative; }
.persona-name { font-weight: 600; font-size: 18px; margin-bottom: 4px; }
.persona-desc { font-size: 13px; color: var(--gray); line-height: 1.3; }
/* --- CHAT INTERFACE --- */
.chat-container { background: var(--black-soft); border: 2px solid var(--border); border-radius: 20px; height: 500px; display: flex; flex-direction: column; overflow: hidden; position: relative; transition: all 0.3s ease; }
.chat-container.flexbox { height: 70vh; max-height: 800px; min-height: 400px; }
.chat-header { padding: 12px 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; background: var(--black-card); gap: 16px; }
.chat-title { font-weight: 600; display: flex; align-items: center; gap: 8px; font-size: 14px; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-controls { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.control-group { display: flex; gap: 0; align-items: center; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.control-group .chat-control-btn { border: none; border-left: 1px solid var(--border); border-radius: 0; }
.control-group .chat-control-btn:first-child { border-left: none; }
.control-group .chat-control-btn:hover { background: var(--black); }
.menu-dropdown { position: relative; }
.menu-dropdown-content { position: absolute; top: calc(100% + 8px); right: 0; background: var(--black-soft); border: 1px solid var(--border); border-radius: 8px; min-width: 150px; box-shadow: var(--shadow-lg); z-index: 1010; opacity: 0; visibility: hidden; transform: translateY(-10px); transition: all 0.2s ease; }
.menu-dropdown.active .menu-dropdown-content { opacity: 1; visibility: visible; transform: translateY(0); }
.menu-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 12px; background: none; border: none; color: var(--white); font-size: 13px; text-align: left; cursor: pointer; transition: background-color 0.2s ease; }
.menu-item:hover { background: var(--black-card); }
.chat-control-btn { background: transparent; border: 1px solid var(--border); padding: 6px 10px; border-radius: 8px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 13px; white-space: nowrap; display: flex; align-items: center; gap: 6px; }
.chat-control-btn:hover:not(:disabled) { border-color: var(--brand); color: var(--brand); }
.chat-control-btn.new-chat { background: var(--gradient-brand); color: var(--white); border: none; font-weight: 600; padding: 6px 12px; }
[data-theme="light"] .chat-control-btn.new-chat { color: var(--black); }
.chat-control-btn.new-chat:hover:not(:disabled) { transform: translateY(-1px); box-shadow: var(--shadow-glow); }
.chat-control-btn.menu-toggle { padding: 6px; font-size: 18px; line-height: 1; }
.chat-messages { flex: 1; overflow-y: auto; padding: 16px; scroll-behavior: smooth; }
.message { margin-bottom: 16px; animation: slideIn 0.3s ease-out; }
@keyframes slideIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.message-header { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; font-size: 12px; color: var(--gray); }
.message-avatar { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 600; }
.message.user .message-avatar { background: var(--gradient-brand); color: var(--white); }
[data-theme="light"] .message.user .message-avatar { color: var(--black); }
.message.bot .message-avatar { background: var(--gradient-success); color: var(--white); }
[data-theme="light"] .message.bot .message-avatar { color: var(--black); }
.message-bubble { padding: 12px 16px; border-radius: 18px; font-size: 14px; line-height: 1.5; max-width: 85%; word-wrap: break-word; }
.message.user .message-bubble { margin-left: auto; background: var(--gradient-brand); color: var(--white); border-radius: 18px 18px 4px 18px; }
[data-theme="light"] .message.user .message-bubble { color: var(--black); }
.message.bot .message-bubble { background: var(--black-card); color: var(--gray-light); border: 1px solid var(--border-light); padding-right: 40px; border-radius: 18px 18px 18px 4px; position: relative; }
.message.system .message-bubble { background: rgba(93, 92, 222, 0.1); border: 1px solid var(--brand); color: var(--brand); font-size: 12px; padding: 8px 12px; font-style: italic; text-align: center; max-width: 100%; border-radius: 8px; }
.message.system .message-avatar { background: var(--gradient-brand); font-size: 12px; }
.chat-input-area { padding: 16px; border-top: 1px solid var(--border); background: var(--black-card); flex-shrink: 0; }
.chat-input-container { display: flex; gap: 8px; align-items: flex-end; }
.chat-input { flex: 1; background: var(--black); border: 1px solid var(--border); border-radius: 12px; padding: 12px; font-size: 16px; color: var(--white); resize: none; min-height: 48px; max-height: 120px; transition: all 0.2s ease; font-family: inherit; }
.chat-input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); }
.chat-input::placeholder { color: var(--gray-dark); }
.send-button { background: var(--gradient-brand); border: none; padding: 0 16px; border-radius: 12px; color: var(--white); font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; height: 48px; width: 48px;}
[data-theme="light"] .send-button { color: var(--black); }
.send-button:hover:not(:disabled) { transform: translateY(-1px); }
.send-button:disabled { opacity: 0.5; cursor: not-allowed; }
.send-button .send-icon { font-size: 20px; }
.error-message { background: rgba(239, 68, 68, 0.1); border: 1px solid var(--accent); border-radius: 8px; padding: 12px; margin: 8px 0; color: var(--accent); font-size: 14px; display: none; }
.error-message.show { display: block; animation: errorSlide 0.3s ease-out; }
@keyframes errorSlide { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
/* Typing indicator */
.typing-indicator { display: inline-flex; align-items: center; gap: 2px; }
.typing-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--brand); animation: typing 1.4s infinite; }
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing { 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; } 30% { transform: translateY(-10px); opacity: 1; } }
/* --- GLOBAL & RESPONSIVE --- */
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 16px; }
.logo { font-size: 20px; font-weight: 700; background: var(--gradient-brand); -webkit-background-clip: text; -webkit-text-fill-color: transparent; flex-shrink: 0; }
.header-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.theme-toggle { background: var(--black-card); border: 1px solid var(--border); border-radius: 50px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 14px; display: flex; align-items: center; gap: 8px; white-space: nowrap; flex-shrink: 0; }
.theme-toggle:hover { border-color: var(--brand); }
.explainer { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 24px; text-align: center; }
.explainer h3 { font-size: 20px; font-weight: 600; color: var(--brand); margin-bottom: 8px; }
.explainer p { font-size: 14px; color: var(--gray-light); line-height: 1.5; }
.modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 9000; opacity: 0; visibility: hidden; transition: all 0.3s ease; padding: 16px; }
.modal.active { opacity: 1; visibility: visible; }
.modal-content { background: var(--black-soft); border: 1px solid var(--border); border-radius: 20px; padding: 24px; max-width: 500px; width: 100%; max-height: 80vh; overflow-y: auto; }
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.modal-title { font-size: 20px; font-weight: 700; margin: 0; }
.modal-close { background: none; border: none; color: var(--gray); font-size: 20px; cursor: pointer; padding: 4px; }
.modal-close:hover { color: var(--white); }
.pricing-option { background: var(--black-card); border: 2px solid var(--border); border-radius: 16px; padding: 20px; margin-bottom: 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; }
.pricing-option:hover { border-color: var(--brand); transform: translateY(-2px); }
.pricing-option.recommended { border-color: var(--success); position: relative; }
.pricing-option.recommended::before { content: "RECOMMENDED"; position: absolute; top: -8px; left: 50%; transform: translateX(-50%); background: var(--success); color: var(--white); padding: 4px 8px; border-radius: 12px; font-size: 9px; font-weight: 700; }
.pricing-title { font-size: 16px; font-weight: 600; margin-bottom: 6px; }
.pricing-price { font-size: 24px; font-weight: 700; color: var(--success); margin-bottom: 8px; }
.pricing-features { list-style: none; text-align: left; color: var(--gray-light); margin-bottom: 12px; }
.pricing-features li { padding: 3px 0; display: flex; align-items: center; gap: 6px; font-size: 12px; }
.pricing-features li::before { content: "✓"; color: var(--success); font-weight: bold; }
.action-button { background: var(--gradient-brand); border: none; padding: 12px 12px; border-radius: 16px; color: var(--white); font-weight: 600; font-size: 16px; cursor: pointer; transition: all 0.2s ease; width: 70%; }
[data-theme="light"] .action-button { color: var(--black); }
.action-button:hover { transform: translateY(-2px); box-shadow: var(--shadow-glow); }
@media (max-width: 768px) {
.app { padding: 12px; }
.header { flex-direction: column; align-items: stretch; text-align: center; margin-bottom: 20px; }
.header-actions { justify-content: center; }
.chat-container { height: auto; flex-grow: 1; border-radius: 0; margin: 0 -12px; border-left: none; border-right: none; }
body, .app { display: flex; flex-direction: column; }
.chat-header { padding: 8px 12px; }
.chat-controls { gap: 6px; }
.chat-control-btn, .control-group .chat-control-btn { font-size: 11px; padding: 6px 8px; }
.chat-control-btn.new-chat .btn-text { display: none; } /* Hide text on mobile */
.floating-panel { width: calc(100vw - 32px); }
}
</style>
</head>
<body data-theme="light">
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<div class="app" id="app">
<!-- Quick Switch Panel -->
<div id="quickSwitchPanel" class="floating-panel">
<h3>Quick Switch</h3>
<select id="quickModelSelect"></select>
<select id="quickPersonaSelect"></select>
<button class="chat-control-btn new-chat" style="width: 100%; margin-top: 8px;" onclick="applyQuickSwitch()">Apply</button>
</div>
<!-- Keyboard Shortcuts Panel -->
<div id="shortcutsPanel" class="shortcuts-panel">
<h3>Keyboard Shortcuts</h3>
<div class="shortcut-item"><span>New Chat</span><span class="shortcut-key">Ctrl+N</span></div>
<div class="shortcut-item"><span>Fullscreen</span><span class="shortcut-key">F11</span></div>
<div class="shortcut-item"><span>Theme Toggle</span><span class="shortcut-key">Ctrl+/</span></div>
<div class="shortcut-item"><span>Send Message</span><span class="shortcut-key">Enter</span></div>
<div class="shortcut-item"><span>New Line</span><span class="shortcut-key">Shift+Enter</span></div>
<div class="shortcut-item"><span>Quick Switch</span><span class="shortcut-key">Ctrl+K</span></div>
</div>
<!-- Header -->
<header class="header">
<div class="logo">BrutallyHonest.ai</div>
<div class="header-actions">
<div class="credits-container" id="creditsDisplay">
<div class="credits-progress">
<div class="credits-text"><span class="credits-number" id="creditsAmount">10</span> / <span id="creditsMax">1000</span></div>
<div class="credits-bar"><div class="credits-fill" id="creditsFill" style="width: 1%"></div></div>
</div>
<div style="font-size: 12px; color: var(--brand); font-weight: 600;">Upgrade</div>
</div>
<div class="theme-toggle" id="themeToggle"><span id="themeIcon">☀️</span> <span id="themeText">Light</span></div>
</div>
</header>
<!-- Explainer -->
<section class="explainer">
<h3>🔥 The AI That Has Transformed Thousands</h3>
<p>Choose your AI personality, pick a model, and start chatting. <strong>Free models are unlimited.</strong> Premium costs credits.</p>
</section>
<!-- Error Display -->
<div class="error-message" id="errorMessage"></div>
<!-- Main Chat Interface -->
<div class="chat-container">
<div class="chat-header">
<div class="chat-title">
<span id="currentPersonaEmoji">🔥</span>
<span id="currentPersonaName">Loading...</span>
<span id="currentModelInfo" style="font-size: 12px; color: var(--gray);"></span>
</div>
<div class="chat-controls">
<button class="chat-control-btn new-chat" id="newChatBtn">✨ <span class="btn-text">New Chat</span></button>
<div class="control-group">
<button class="chat-control-btn" id="layoutToggle" title="Toggle Layout">📐</button>
<button class="chat-control-btn" id="chatFullscreenBtn" title="Toggle Fullscreen">⛶</button>
</div>
<div class="menu-dropdown" id="menuDropdown">
<button class="chat-control-btn menu-toggle" id="menuToggle" title="More Options">⋯</button>
<div class="menu-dropdown-content">
<button class="menu-item" id="quickSwitchBtn">⚡ Quick Switch</button>
<button class="menu-item" id="showShortcutsBtn">⌨️ Shortcuts</button>
<hr style="border: none; border-top: 1px solid var(--border); margin: 4px 0;">
<button class="menu-item" id="saveBtn">💾 Save Chat</button>
<button class="menu-item" id="clearBtn">🗑️ Clear Chat</button>
<button class="menu-item" id="exportBtn">📤 Export Chat</button>
</div>
</div>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<div class="message bot">
<div class="message-header">
<div class="message-avatar">🔥</div>
<span>Loading...</span>
<span>•</span>
<span>Just now</span>
</div>
<div class="message-bubble">
Welcome! Configuring your experience...
<button class="copy-button" onclick="copyMessage(this)" data-text="Welcome! Configuring your experience...">Copy</button>
</div>
</div>
</div>
<div class="chat-input-area">
<div class="chat-input-container">
<textarea class="chat-input" id="chatInput" placeholder="Type your message here..." rows="1"></textarea>
<button class="send-button" id="sendButton" title="Send (Enter)"><span class="send-icon">→</span></button>
</div>
</div>
</div>
<!-- Model & Persona Selection -->
<div class="model-selection-container" id="modelSelectionContainer">
<h4 style="font-weight: 600; margin: 24px 0 16px; color: var(--brand);">Select AI Model</h4>
<div class="loading-placeholder" id="modelsLoadingPlaceholder"><div class="loading-animation"></div> Loading models...</div>
<div id="modelsContainer"></div>
</div>
<div class="persona-selection-container" id="personaSelectionContainer">
<h4 style="font-weight: 600; margin: 24px 0 16px; color: var(--brand);">Choose Your AI Personality</h4>
<div class="loading-placeholder" id="personasLoadingPlaceholder"><div class="loading-animation"></div> Loading personalities...</div>
<div class="persona-grid" id="personaGrid"></div>
</div>
</div>
<!-- Credits Modal -->
<div class="modal" id="creditsModal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">💎 Get More Credits</h2>
<button class="modal-close" id="closeCreditsModal">×</button>
</div>
<div class="pricing-option recommended">
<div class="pricing-title">Monthly Subscription</div>
<div class="pricing-price">$9<span style="font-size: 12px; color: var(--gray);">/month</span></div>
<ul class="pricing-features">
<li>500 credits per month</li>
<li>Unlimited free models</li>
<li>Access to all premium models</li>
<li>Credits roll over (up to 1000)</li>
<li>Priority support</li>
</ul>
<button class="action-button" onclick="purchaseMonthly()">Subscribe Now ✨</button>
</div>
<div class="pricing-option">
<div class="pricing-title">Pay As You Go</div>
<div class="pricing-price">From $2.99</div>
<ul class="pricing-features">
<li>100 credits for $2.99</li>
<li>250 credits for $5.99</li>
<li>500 credits for $9.99</li>
<li>Credits never expire</li>
</ul>
<button class="action-button" onclick="purchaseCredits()">Buy Credits 💰</button>
</div>
</div>
</div>
<!-- Feedback Modal -->
<div class="feedback-modal" id="feedbackModal">
<div class="feedback-content">
<h3>💬 Quick Feedback</h3>
<textarea id="feedbackText" placeholder="What's working? What isn't? Any suggestions?" rows="4"></textarea>
<div class="modal-buttons">
<button onclick="closeFeedback()" class="modal-btn secondary">Cancel</button>
<button onclick="submitFeedback()" class="modal-btn primary">Send Feedback</button>
</div>
</div>
</div>
<!-- Payment Modal -->
<div class="payment-modal" id="paymentModal">
<div class="payment-content">
<h3>🚀 Upgrade to Pro</h3>
<div class="credit-display" id="creditDisplay">
Credits remaining: <span id="creditCount">10</span>
</div>
<div style="margin: 20px 0;">
<h4>Pro Plan - $9/month</h4>
<ul style="text-align: left; margin: 12px 0;">
<li>Unlimited messages</li>
<li>All AI personas</li>
<li>Priority support</li>
</ul>
</div>
<div class="modal-buttons">
<button onclick="closePayment()" class="modal-btn secondary">Maybe Later</button>
<button onclick="startPayment()" class="modal-btn primary">Upgrade Now</button>
</div>
</div>
</div>
<!-- Onboarding Overlay Modal -->
<div class="onboarding-overlay" id="onboarding">
<div class="onboarding-content">
<div class="onboarding-step active">
<h2>🚀 Welcome to AI Personas!</h2>
<p>Choose from specialized AI assistants, each with unique expertise and personality.</p>
<div class="progress-dots">
<div class="dot active"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
<button onclick="nextOnboardingStep()" class="onboarding-btn">Get Started</button>
</div>
<div class="onboarding-step">
<h2>💬 Pick Your AI</h2>
<p>Each persona has specialized knowledge. Try the Code Mentor for programming or Creative Writer for content.</p>
<div class="progress-dots">
<div class="dot active"></div>
<div class="dot active"></div>
<div class="dot"></div>
</div>
<button onclick="nextOnboardingStep()" class="onboarding-btn">Cool!</button>
</div>
<div class="onboarding-step">
<h2>⚡ Free to Start</h2>
<p>You get 10 free messages to try any persona. Unlock unlimited access for just $9/month.</p>
<div class="progress-dots">
<div class="dot active"></div>
<div class="dot active"></div>
<div class="dot active"></div>
</div>
<button onclick="completeOnboarding()" class="onboarding-btn">Let's Chat!</button>
</div>
</div>
</div>
<!-- Feedback Widget -->
<div class="feedback-widget" style="position: fixed; bottom: 20px; right: 20px; z-index: 1000;">
<button onclick="openFeedback()" style="background: #10b981; color: white; border: none; border-radius: 50%; width: 56px; height: 56px; font-size: 24px; cursor: pointer; box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3); transition: all 0.2s;">💬</button>
</div>
<script>
// No Stripe.js needed - N8N handles everything! 🎉
// ===================================================================================
// CONFIG & STATE MANAGEMENT
// ===================================================================================
const CONFIG = {
mainWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/bha-mvp',
configWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/config',
feedbackWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/feedback',
errorWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/errors',
stripeCheckoutWebhookUrl: 'https://photobar.app.n8n.cloud/webhook-test/stripe-webhook'
};
class BHAStateManager {
constructor() {
this.state = {
user: { credits: 10, maxCredits: 1000, userId: this.generateUserId() },
chat: { currentModel: null, currentPersona: null, currentModelCost: 0, currentModelName: '', conversationId: null, messages: [], isTyping: false },
ui: { currentTheme: 'light', isFullscreen: false, isFlexLayout: false },
config: { personas: [], models: [], modelsByTier: { free: [], standard: [], premium: [] }, isLoaded: false }
};
this.listeners = new Map();
this.loadPersistedState();
}
generateUserId() {
let stored = localStorage.getItem('bha_user_id');
if (stored) return stored;
let id = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('bha_user_id', id);
return id;
}
loadPersistedState() {
this.state.ui.currentTheme = localStorage.getItem('bha_selectedTheme') || 'light';
this.state.chat.currentPersona = localStorage.getItem('bha_selectedPersona');
this.state.chat.currentModel = localStorage.getItem('bha_selectedModel');
this.state.chat.conversationId = localStorage.getItem('bha_conversation_id') || this.generateConversationId();
this.state.ui.isFlexLayout = localStorage.getItem('bha_flexboxMode') === 'true';
// Load credits from localStorage or default to 10 for new users
const savedCredits = localStorage.getItem('bha_user_credits');
this.state.user.credits = savedCredits ? parseInt(savedCredits) : 10;
}
generateConversationId() { return 'conv_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5); }
subscribe(key, callback) {
if (!this.listeners.has(key)) { this.listeners.set(key, []); }
this.listeners.get(key).push(callback);
}
setState(path, value) {
const keys = path.split('.');
let current = this.state;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
}
const finalKey = keys[keys.length - 1];
if (current[finalKey] === value) return;
console.log(`🔄 State Change: ${path} to "${value}"`);
current[finalKey] = value;
if (path.startsWith('chat.')) localStorage.setItem(`bha_${finalKey}`, value);
if (path === 'user.credits') localStorage.setItem('bha_user_credits', value);
if (this.listeners.has(path)) this.listeners.get(path).forEach(cb => cb(value));
}
getState(path) { return path.split('.').reduce((acc, key) => acc?.[key], this.state); }
resetConversationContext(reason = 'manual') {
const newId = this.generateConversationId();
this.setState('chat.conversationId', newId);
this.setState('chat.messages', []);
console.log(`🔄 Context Reset (${reason}): New conversation ${newId}`);
return newId;
}
}
const StateManager = new BHAStateManager();
// ===================================================================================
// UTILITY FUNCTIONS
// ===================================================================================
function showToast(message, type = 'success', duration = 3000) {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(100%)';
setTimeout(() => toast.remove(), 300);
}, duration);
}
function showError(message) {
const errorEl = document.getElementById('errorMessage');
errorEl.textContent = message;
errorEl.classList.add('show');
setTimeout(() => errorEl.classList.remove('show'), 5000);
}
function escapeHtml(unsafe) {
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
}
// ===================================================================================
// ONBOARDING FUNCTIONS
// ===================================================================================
let onboardingStep = 0;
const totalSteps = 3;
function nextOnboardingStep() {
onboardingStep++;
updateOnboardingStep();
}
function updateOnboardingStep() {
document.querySelectorAll('.onboarding-step').forEach((step, index) => {
step.classList.toggle('active', index === onboardingStep);
});
}
function completeOnboarding() {
document.getElementById('onboarding').style.display = 'none';
localStorage.setItem('onboardingComplete', 'true');
}
function checkOnboarding() {
if (!localStorage.getItem('onboardingComplete')) {
document.getElementById('onboarding').style.display = 'flex';
}
}
// ===================================================================================
// CREDIT MANAGEMENT
// ===================================================================================
function initCredits() {
updateCreditDisplay();
}
function updateCreditDisplay() {
const credits = StateManager.getState('user.credits');
const maxCredits = StateManager.getState('user.maxCredits');
document.getElementById('creditsAmount').textContent = credits;
document.getElementById('creditsMax').textContent = maxCredits;
// Update credit count in payment modal if it exists
const creditCountEl = document.getElementById('creditCount');
if (creditCountEl) {
creditCountEl.textContent = credits;
}
const percentage = (credits / maxCredits) * 100;
document.getElementById('creditsFill').style.width = percentage + '%';
// Show warning styling if credits are low
const creditDisplay = document.getElementById('creditDisplay');
if (creditDisplay) {
creditDisplay.classList.toggle('credit-warning', credits <= 2);
}
}
function deductCredit() {
let credits = StateManager.getState('user.credits');
credits = Math.max(0, credits - 1);
StateManager.setState('user.credits', credits);
updateCreditDisplay();
if (credits <= 2) {
showPaymentModal();
}
return credits > 0;
}
function showPaymentModal() {
document.getElementById('paymentModal').style.display = 'block';
updateCreditDisplay();
}
function closePayment() {
document.getElementById('paymentModal').style.display = 'none';
}
async function startPayment() {
try {
showToast('Redirecting to secure checkout...', 'success', 2000);
const userId = StateManager.getState('user.userId');
const monthlyUrl = `https://buy.stripe.com/28E4gz9RM3vyd6r3gGcV20a?client_reference_id=${userId}`;
// Redirect directly to Stripe payment link
window.location.href = monthlyUrl;
} catch (error) {
console.error('Payment error:', error);
showToast('Payment setup failed. Please try again.', 'error');
logError('Payment Error', error.message, '', '');
}
}
// ===================================================================================
// FEEDBACK SYSTEM
// ===================================================================================
function openFeedback() {
document.getElementById('feedbackModal').style.display = 'block';
}
function closeFeedback() {
document.getElementById('feedbackModal').style.display = 'none';
document.getElementById('feedbackText').value = '';
}
async function submitFeedback() {
const feedback = document.getElementById('feedbackText').value;
if (feedback.trim()) {
try {
await fetch(CONFIG.feedbackWebhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
feedback: feedback,
userId: StateManager.getState('user.userId'),
timestamp: Date.now(),
url: window.location.href
})
});
closeFeedback();
showToast('✅ Feedback sent! Thanks for helping us improve.', 'success');
} catch (error) {
console.error('Feedback error:', error);
showToast('Failed to send feedback. Please try again.', 'error');
}
}
}
// ===================================================================================
// ERROR LOGGING
// ===================================================================================
function logError(type, message, file, line) {
try {
fetch(CONFIG.errorWebhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: type,
message: message,
file: file,
line: line,
userId: StateManager.getState('user.userId'),
timestamp: Date.now(),
userAgent: navigator.userAgent
})
});
} catch (err) {
console.error('Error logging failed:', err);
}
}
// Enhanced error handling
window.addEventListener('error', (e) => {
logError('JavaScript Error', e.error ? e.error.message : e.message, e.filename, e.lineno);
});
// ===================================================================================
// CONFIGURATION MANAGER
// ===================================================================================
class BHAConfigManager {
constructor() {
this.config = {
personas: [],
models: [],
modelsByTier: { free: [], standard: [], premium: [] },
features: {}
};
this.isLoaded = false;
}
async loadConfiguration() {
try {
console.log('🔄 Loading configuration from:', CONFIG.configWebhookUrl);
const response = await fetch(CONFIG.configWebhookUrl);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.config = await response.json();
this.isLoaded = true;
StateManager.setState('config', this.config);
StateManager.setState('config.isLoaded', true);
console.log('✅ Configuration loaded:', this.config);
this.renderDynamicContent();
this.initializeDefaults();
} catch (error) {
console.error('❌ Config load failed:', error);
this.loadFallbackConfig();
}
}
loadFallbackConfig() {
console.log('🔄 Loading fallback configuration...');
this.config = {
personas: [
{
id: 'QuickResonanceSpark_Persona_Haiku',
name: 'Brutally Honest',
emoji: '🔥',
description: 'No BS, just raw truth',
isPremium: false,
gradientColor: 'linear-gradient(135deg, #EF4444, #F59E0B)'
},
{
id: 'ResonanceSession_Orchestrator_Opus',
name: 'Deep Guide',
emoji: '🧠',
description: 'Profound insights & breakthrough clarity',
isPremium: false,
gradientColor: 'linear-gradient(135deg, #10B981, #06D6A0)'
}
],
models: [
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' },
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' }
],
modelsByTier: {
free: [
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' },
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' }
],
standard: [],
premium: []
}
};
this.isLoaded = true;
StateManager.setState('config', this.config);
StateManager.setState('config.isLoaded', true);
this.renderDynamicContent();
this.initializeDefaults();
}
renderDynamicContent() {
this.renderPersonas();
this.renderModels();
this.populateQuickSwitcher();
}
renderPersonas() {
const container = document.getElementById('personaGrid');
const loadingPlaceholder = document.getElementById('personasLoadingPlaceholder');
if (loadingPlaceholder) loadingPlaceholder.style.display = 'none';
container.innerHTML = this.config.personas.map(persona => `
<div class="persona-card ${persona.isPremium ? 'premium' : ''}"
data-persona-id="${persona.id}"
data-persona-name="${persona.name}"
data-persona-emoji="${persona.emoji}">
<div class="persona-avatar" style="background: ${persona.gradientColor};">
${persona.emoji}
${persona.isPremium ? '<div class="premium-badge">💎</div>' : ''}
</div>
<div class="persona-name">${persona.name}</div>
<div class="persona-desc">${persona.description}</div>
</div>
`).join('');
document.querySelectorAll('.persona-card').forEach(card => {
card.addEventListener('click', () => this.selectPersona(card));
});
}
renderModels() {
const container = document.getElementById('modelsContainer');
const loadingPlaceholder = document.getElementById('modelsLoadingPlaceholder');
if (loadingPlaceholder) loadingPlaceholder.style.display = 'none';
const tiers = ['free', 'standard', 'premium'];
const tierLabels = {
'free': 'Free Models',
'standard': 'Standard Models (5-15 credits)',
'premium': 'Premium Models (15+ credits)'
};
container.innerHTML = tiers.map(tier => {
const models = this.config.modelsByTier[tier] || [];
if (models.length === 0) return '';
return `
<div class="model-section" data-tier="${tier}">
<div class="model-section-title">${tierLabels[tier]}</div>
<div class="model-grid">
${models.map(model => `
<div class="model-option"
data-model-id="${model.id}"
data-model-name="${model.name}"
data-model-cost="${model.cost}">
<div class="model-name">${model.name}</div>
<div class="model-cost ${model.cost === 0 ? 'free' : 'paid'}">
${model.cost === 0 ? 'FREE' : `${model.cost} credits`}
</div>
</div>
`).join('')}
</div>
</div>
`;
}).join('');
document.querySelectorAll('.model-option').forEach(option => {
option.addEventListener('click', () => this.selectModel(option));
});
}
populateQuickSwitcher() {
if (!this.isLoaded) return;
const modelSelect = document.getElementById('quickModelSelect');
const personaSelect = document.getElementById('quickPersonaSelect');
modelSelect.innerHTML = this.config.models.map(m =>
`<option value="${m.id}">${m.name}</option>`
).join('');
personaSelect.innerHTML = this.config.personas.map(p =>
`<option value="${p.id}">${p.emoji} ${p.name}</option>`
).join('');
modelSelect.value = StateManager.getState('chat.currentModel') || '';
personaSelect.value = StateManager.getState('chat.currentPersona') || '';
}
selectPersona(personaElement) {
document.querySelectorAll('.persona-card').forEach(el => el.classList.remove('active'));
personaElement.classList.add('active');
const personaId = String(personaElement.dataset.personaId || '');
const personaName = String(personaElement.dataset.personaName || '');
const personaEmoji = String(personaElement.dataset.personaEmoji || '');
StateManager.setState('chat.currentPersona', personaId);
document.getElementById('currentPersonaEmoji').textContent = personaEmoji;
document.getElementById('currentPersonaName').textContent = personaName;
this.addSystemMessage(`🎭 Now chatting with ${personaName}`);
this.populateQuickSwitcher();
console.log('✅ Persona Selected:', personaId);
}
selectModel(modelElement) {
document.querySelectorAll('.model-option').forEach(el => el.classList.remove('active'));
modelElement.classList.add('active');
const modelId = String(modelElement.dataset.modelId || '');
const modelName = String(modelElement.dataset.modelName || '');
const modelCost = parseInt(modelElement.dataset.modelCost || '0');
StateManager.setState('chat.currentModel', modelId);
StateManager.setState('chat.currentModelCost', modelCost);
StateManager.setState('chat.currentModelName', modelName);
const modelInfo = document.getElementById('currentModelInfo');
if (modelInfo) {
modelInfo.textContent = `(${modelName})`;
}
this.addSystemMessage(`🤖 Now using ${modelName}`);
this.populateQuickSwitcher();
console.log('✅ Model Selected:', modelId);
}
addSystemMessage(content) {
const messagesContainer = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = 'message system';
messageDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">⚙️</div>
<span>System</span>
<span>•</span>
<span>${new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
</div>
<div class="message-bubble">
${content}
</div>
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
initializeDefaults() {
const savedPersonaId = localStorage.getItem('bha_selectedPersona');
const savedModelId = localStorage.getItem('bha_selectedModel');
if (this.config.personas.length > 0) {
let targetPersona = savedPersonaId ?
document.querySelector(`[data-persona-id="${savedPersonaId}"]`) :
document.querySelector('.persona-card');
if (targetPersona) {
this.selectPersona(targetPersona);
}
}
if (this.config.models.length > 0) {
let targetModel = savedModelId ?
document.querySelector(`[data-model-id="${savedModelId}"]`) :
document.querySelector('.model-option');
if (targetModel) {
this.selectModel(targetModel);
}
}
setTimeout(() => {
this.updateWelcomeMessage();
}, 100);
}
updateWelcomeMessage() {
const messagesContainer = document.getElementById('chatMessages');
const currentPersonaId = StateManager.getState('chat.currentPersona');
const currentPersona = this.config.personas.find(p => p.id === currentPersonaId);
if (currentPersona) {
messagesContainer.innerHTML = `
<div class="message bot">
<div class="message-header">
<div class="message-avatar">${currentPersona.emoji}</div>
<span>${currentPersona.name}</span>
<span>•</span>
<span>Just now</span>
</div>
<div class="message-bubble">
I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today?
<button class="copy-button" onclick="copyMessage(this)" data-text="I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today?">Copy</button>
</div>
</div>
`;
}
}
}
// ===================================================================================
// CHAT FUNCTIONS
// ===================================================================================
function addMessage(content, isUser = false, persona = null) {
const messagesContainer = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user' : 'bot'}`;
const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
const currentPersonaId = StateManager.getState('chat.currentPersona');
const personas = StateManager.getState('config.personas') || [];
const currentPersona = personas.find(p => p.id === currentPersonaId);
const sender = isUser ? 'You' : (persona || currentPersona?.name || 'AI');
const avatar = isUser ? '👤' : (currentPersona?.emoji || '🤖');
const processedContent = isUser ?
escapeHtml(content) :
(typeof marked !== 'undefined' ? marked.parse(content) : content);
const copyButton = isUser ? '' :
`<button class="copy-button" onclick="copyMessage(this)" data-text="${escapeHtml(content)}">Copy</button>`;
messageDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">${avatar}</div>
<span>${sender}</span>
<span>•</span>
<span>${timestamp}</span>
</div>
<div class="message-bubble">
${processedContent}
${copyButton}
</div>
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
const messages = StateManager.getState('chat.messages') || [];
messages.push({
content: content,
isUser: isUser,
timestamp: Date.now(),
persona: persona
});
StateManager.setState('chat.messages', messages);
}
function addTypingIndicator() {
const messagesContainer = document.getElementById('chatMessages');
const typingDiv = document.createElement('div');
typingDiv.id = 'typingIndicator';
typingDiv.className = 'message bot';
const currentPersonaId = StateManager.getState('chat.currentPersona');
const personas = StateManager.getState('config.personas') || [];
const currentPersona = personas.find(p => p.id === currentPersonaId);
typingDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">${currentPersona?.emoji || '🤖'}</div>
<span>${currentPersona?.name || 'AI'}</span>
<span>•</span>
<span>typing...</span>
</div>
<div class="message-bubble">
<div class="typing-indicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
`;
messagesContainer.appendChild(typingDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function removeTypingIndicator() {
const indicator = document.getElementById('typingIndicator');
if (indicator) {
indicator.remove();
}
}
async function sendMessage() {
const input = document.getElementById('chatInput');
const message = input.value.trim();
if (!message || StateManager.getState('chat.isTyping')) return;
const currentPersona = StateManager.getState('chat.currentPersona');
const currentModel = StateManager.getState('chat.currentModel');
let conversationId = StateManager.getState('chat.conversationId');
if (!conversationId) {
conversationId = StateManager.generateConversationId();
StateManager.setState('chat.conversationId', conversationId);
}
if (!currentPersona || !currentModel) {
showError('Please select a persona and model before sending a message.');
return;
}
const modelCost = StateManager.getState('chat.currentModelCost') || 0;
const userCredits = StateManager.getState('user.credits');
if (modelCost > 0 && userCredits < modelCost) {
showError(`Insufficient credits! You need ${modelCost} credits.`);
showPaymentModal();
return;
}
addMessage(message, true);
input.value = '';
input.style.height = 'auto';
addTypingIndicator();
StateManager.setState('chat.isTyping', true);
const sendBtn = document.getElementById('sendButton');
sendBtn.disabled = true;
try {
const payload = {
user_id: String(StateManager.getState('user.userId')),
bot_to_call: String(currentPersona),
full_prompt_for_llm: String(message),
raw_user_situation: String(message),
selected_model_for_openrouter: String(currentModel),
conversation_id: String(conversationId),
timestamp: new Date().toISOString(),
frontend_state_snapshot: JSON.stringify({
persona: currentPersona,
model: currentModel,
conversation_id: conversationId,
message_count: StateManager.getState('chat.messages').length || 0
})
};
console.log('🚀 Sending Payload:', payload);
const response = await fetch(CONFIG.mainWebhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log('✅ Response received:', data);
let aiResponseText = "I'm having trouble formulating a response right now.";
if (data && typeof data.response === 'string') {
aiResponseText = data.response;
} else if (data.bot_response && data.bot_response.analysis) {
aiResponseText = data.bot_response.analysis;
} else if (data.final_response_payload && data.final_response_payload.bot_response) {
aiResponseText = data.final_response_payload.bot_response.analysis;
}
removeTypingIndicator();
addMessage(aiResponseText, false);
if (data.credits_remaining !== undefined) {
StateManager.setState('user.credits', data.credits_remaining);
} else if (modelCost > 0) {
StateManager.setState('user.credits', Math.max(0, userCredits - modelCost));
}
updateCreditDisplay();
if (data.tool_used) {
showToast('Enhanced response generated', 'success', 2000);
}
} catch (error) {
console.error('❌ Error sending message:', error);
removeTypingIndicator();
showError('Failed to send message. Please try again.');
logError('Send Message Error', error.message, '', '');
} finally {
StateManager.setState('chat.isTyping', false);
sendBtn.disabled = false;
}
}
// ===================================================================================
// UI CONTROL FUNCTIONS
// ===================================================================================
function applyQuickSwitch() {
const modelId = document.getElementById('quickModelSelect').value;
const personaId = document.getElementById('quickPersonaSelect').value;
const modelElement = document.querySelector(`[data-model-id="${modelId}"]`);
const personaElement = document.querySelector(`[data-persona-id="${personaId}"]`);
if (modelElement) configManager.selectModel(modelElement);
if (personaElement) configManager.selectPersona(personaElement);
document.getElementById('quickSwitchPanel').classList.remove('active');
showToast('Settings applied!', 'success');
}
function toggleFullscreen(forceState) {
const isFullscreen = forceState ?? !StateManager.getState('ui.isFullscreen');
StateManager.setState('ui.isFullscreen', isFullscreen);
document.getElementById('app').classList.toggle('fullscreen', isFullscreen);
document.getElementById('chatFullscreenBtn').textContent = isFullscreen ? '▣' : '⛶';
localStorage.setItem('bha_fullscreenMode', isFullscreen.toString());
}
function toggleLayout(forceState) {
const isFlexLayout = forceState ?? !StateManager.getState('ui.isFlexLayout');
StateManager.setState('ui.isFlexLayout', isFlexLayout);
document.querySelector('.chat-container').classList.toggle('flexbox', isFlexLayout);
localStorage.setItem('bha_flexboxMode', isFlexLayout.toString());
showToast(`Layout: ${isFlexLayout ? 'Flexible' : 'Fixed'}`, 'success', 1000);
}
function toggleTheme() {
const newTheme = StateManager.getState('ui.currentTheme') === 'light' ? 'dark' : 'light';
StateManager.setState('ui.currentTheme', newTheme);
document.body.dataset.theme = newTheme;
document.getElementById('themeIcon').textContent = newTheme === 'light' ? '☀️' : '🌙';
document.getElementById('themeText').textContent = newTheme.charAt(0).toUpperCase() + newTheme.slice(1);
localStorage.setItem('bha_selectedTheme', newTheme);
}
function startNewChat() {
if (confirm('Start a new conversation? This cannot be undone.')) {
StateManager.resetConversationContext('new_chat_button');
configManager.updateWelcomeMessage();
showToast('✨ New chat started', 'success');
}
}
function saveConversation() {
showToast('💾 Save feature coming soon', 'warning');
}
function clearChat() {
if (confirm('Clear all messages? This is permanent.')) {
StateManager.setState('chat.messages', []);
configManager.updateWelcomeMessage();
showToast('🗑️ Chat cleared', 'success');
}
}
function exportChat() {
const messages = StateManager.getState('chat.messages') || [];
if (messages.length === 0) {
showToast('No messages to export yet!', 'error');
return;
}
const currentPersonaId = StateManager.getState('chat.currentPersona');
const currentModel = StateManager.getState('chat.currentModel');
let content = `BrutallyHonest.ai Chat Export\n`;
content += `Exported: ${new Date().toLocaleString()}\n`;
content += `Model: ${currentModel}\n`;
content += `Persona: ${currentPersonaId}\n`;
content += '='.repeat(50) + '\n\n';
messages.forEach(msg => {
const timestamp = new Date(msg.timestamp).toLocaleString();
const speaker = msg.isUser ? 'You' : (msg.persona || 'AI');
content += `[${timestamp}] ${speaker}: ${msg.content}\n\n`;
});
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `BHA_Chat_${new Date().toISOString().split('T')[0]}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('Chat exported successfully', 'success');
}
function showCreditsModal() {
document.getElementById('creditsModal').classList.add('active');
}
function hideCreditsModal() {
document.getElementById('creditsModal').classList.remove('active');
}
async function purchaseMonthly() {
try {
showToast('Redirecting to secure checkout...', 'success', 2000);
const userId = StateManager.getState('user.userId');
const monthlyUrl = `https://buy.stripe.com/28E4gz9RM3vyd6r3gGcV20a?client_reference_id=${userId}`;
// Redirect directly to Stripe payment link
window.location.href = monthlyUrl;
} catch (error) {
console.error('Monthly purchase error:', error);
showToast('Payment error. Please try again.', 'error');
logError('Payment Error', error.message, '', '');
}
}
async function purchaseCredits() {
try {
showToast('Redirecting to secure checkout...', 'success', 2000);
const userId = StateManager.getState('user.userId');
const creditsUrl = `https://buy.stripe.com/00wfZhfc6c243vR04ucV20c?client_reference_id=${userId}`;
// Redirect directly to Stripe payment link
window.location.href = creditsUrl;
} catch (error) {
console.error('Credits purchase error:', error);
showToast('Payment error. Please try again.', 'error');
logError('Payment Error', error.message, '', '');
}
}
function copyMessage(button) {
const text = button.dataset.text;
navigator.clipboard.writeText(text).then(() => {
const originalText = button.textContent;
button.textContent = 'Copied!';
button.classList.add('copied');
setTimeout(() => {
button.textContent = originalText;
button.classList.remove('copied');
}, 2000);
showToast('Message copied to clipboard', 'success', 2000);
}).catch(err => {
console.error('Copy failed:', err);
showToast('Failed to copy message', 'error', 3000);
});
}
// ===================================================================================
// INITIALIZATION
// ===================================================================================
const configManager = new BHAConfigManager();
async function init() {
console.log("🔥 BrutallyHonest.ai Initializing (The Right Way)...");
// Apply saved UI state
const savedTheme = StateManager.getState('ui.currentTheme');
document.body.dataset.theme = savedTheme;
document.getElementById('themeIcon').textContent = savedTheme === 'light' ? '☀️' : '🌙';
document.getElementById('themeText').textContent = savedTheme.charAt(0).toUpperCase() + savedTheme.slice(1);
if (StateManager.getState('ui.isFlexLayout')) {
document.querySelector('.chat-container').classList.add('flexbox');
}
if (StateManager.getState('ui.isFullscreen')) {
toggleFullscreen(true);
}
// Event Listeners
document.getElementById('themeToggle').addEventListener('click', toggleTheme);
document.getElementById('newChatBtn').addEventListener('click', startNewChat);
document.getElementById('layoutToggle').addEventListener('click', () => toggleLayout());
document.getElementById('chatFullscreenBtn').addEventListener('click', () => toggleFullscreen());
document.getElementById('menuToggle').addEventListener('click', (e) => {
e.stopPropagation();
document.getElementById('menuDropdown').classList.toggle('active');
});
document.getElementById('saveBtn').addEventListener('click', saveConversation);
document.getElementById('clearBtn').addEventListener('click', clearChat);
document.getElementById('exportBtn').addEventListener('click', exportChat);
document.getElementById('sendButton').addEventListener('click', sendMessage);
// Credits system
document.getElementById('creditsDisplay').addEventListener('click', showCreditsModal);
document.getElementById('closeCreditsModal').addEventListener('click', hideCreditsModal);
// Quick Switch and Shortcuts
document.getElementById('quickSwitchBtn').addEventListener('click', (e) => {
e.stopPropagation();
document.getElementById('menuDropdown').classList.remove('active');
document.getElementById('quickSwitchPanel').classList.toggle('active');
});
document.getElementById('showShortcutsBtn').addEventListener('click', (e) => {
e.stopPropagation();
document.getElementById('menuDropdown').classList.remove('active');
const panel = document.getElementById('shortcutsPanel');
panel.classList.toggle('show');
setTimeout(() => panel.classList.remove('show'), 5000);
});
// Input handlers
const chatInput = document.getElementById('chatInput');
chatInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
chatInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
// Global click handlers
window.addEventListener('click', (e) => {
const creditsModal = document.getElementById('creditsModal');
const feedbackModal = document.getElementById('feedbackModal');
const paymentModal = document.getElementById('paymentModal');
if (e.target === creditsModal) hideCreditsModal();
if (e.target === feedbackModal) closeFeedback();
if (e.target === paymentModal) closePayment();
if (!e.target.closest('#menuDropdown')) {
document.getElementById('menuDropdown').classList.remove('active');
}
if (!e.target.closest('.floating-panel')) {
document.getElementById('quickSwitchPanel').classList.remove('active');
}
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
if (StateManager.getState('ui.isFullscreen')) toggleFullscreen(false);
document.getElementById('menuDropdown').classList.remove('active');
document.getElementById('quickSwitchPanel').classList.remove('active');
document.getElementById('shortcutsPanel').classList.remove('show');
hideCreditsModal();
closeFeedback();
closePayment();
}
if (e.key === 'F11') {
e.preventDefault();
toggleFullscreen();
}
if ((e.ctrlKey || e.metaKey) && e.key === '/') {
e.preventDefault();
toggleTheme();
}
if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
e.preventDefault();
startNewChat();
}
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('quickSwitchBtn').click();
}
});
initCredits();
checkOnboarding();
await configManager.loadConfiguration();
console.log('✅ BrutallyHonest.ai initialization complete!');
}
// Global function assignments
window.applyQuickSwitch = applyQuickSwitch;
window.copyMessage = copyMessage;
window.purchaseMonthly = purchaseMonthly;
window.purchaseCredits = purchaseCredits;
window.nextOnboardingStep = nextOnboardingStep;
window.completeOnboarding = completeOnboarding;
window.openFeedback = openFeedback;
window.closeFeedback = closeFeedback;
window.submitFeedback = submitFeedback;
window.closePayment = closePayment;
window.startPayment = startPayment;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
</script>
</body>
</html>
works like charm!
<!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" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<title>BrutallyHonest.ai - Transform Your Bullsh*t Into Breakthroughs</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
:root {
/* Light Theme (Default) */
--black: #FFFFFF; --black-soft: #F8F9FA; --black-card: #F1F3F4; --border: #E8EAED; --border-light: #DADCE0; --white: #000000; --gray: #5F6368; --gray-light: #3C4043; --gray-dark: #80868B;
--brand: #5D5CDE; --brand-light: #7C7CE8; --brand-dark: #4B4BC7; --accent: #EF4444; --success: #22C55E; --warning: #F59E0B; --gold: #FCD34D;
--gradient-brand: linear-gradient(135deg, #5D5CDE 0%, #7C7CE8 100%); --gradient-fire: linear-gradient(135deg, #EF4444 0%, #F59E0B 100%); --gradient-success: linear-gradient(135deg, #22C55E 0%, #4ADE80 100%);
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.08); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.12), 0 1px 2px -1px rgb(0 0 0 / 0.12); --shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06); --shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05); --shadow-glow: 0 0 0 1px rgba(93, 92, 222, 0.3), 0 0 20px rgba(93, 92, 222, 0.2);
}
[data-theme="dark"] {
--black: #0F0F0F; --black-soft: #1A1A1A; --black-card: #262626; --border: #404040; --border-light: #525252; --white: #FFFFFF; --gray: #A3A3A3; --gray-light: #D4D4D4; --gray-dark: #737373;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html { position: static; height: auto; overflow: auto; }
body { background: var(--black); color: var(--white); line-height: 1.6; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transition: background-color 0.5s ease, color 0.5s ease; min-height: 100vh; overflow-x: hidden; position: relative; }
.app { max-width: 1200px; margin: 0 auto; padding: 16px; min-height: 100vh; position: relative; width: 100%; transition: all 0.3s ease; }
/* Toast Container */
.toast-container { position: fixed; top: 20px; right: 20px; z-index: 10000; pointer-events: none; }
.toast { background: var(--black-soft); border: 1px solid var(--border); border-radius: 8px; padding: 12px 16px; margin-bottom: 8px; box-shadow: var(--shadow-lg); backdrop-filter: blur(8px); animation: toastSlideIn 0.3s ease-out; pointer-events: all; font-size: 14px; max-width: 300px; }
.toast.success { border-color: var(--success); background: rgba(34, 197, 94, 0.1); }
.toast.error { border-color: var(--accent); background: rgba(239, 68, 68, 0.1); }
@keyframes toastSlideIn { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } }
/* FULLSCREEN MODE */
.app.fullscreen { position: fixed; top: 0; left: 0; right: 0; bottom: 0; max-width: none; padding: 0; z-index: 1000; background: var(--black); }
.app.fullscreen .header,
.app.fullscreen .explainer,
.app.fullscreen .model-selection-container,
.app.fullscreen .persona-selection-container { display: none; }
.app.fullscreen .chat-container { height: 100vh; border-radius: 0; border: none; }
.floating-panel { position: fixed; top: 60px; right: 16px; width: 250px; background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 16px; z-index: 10002; display: none; box-shadow: var(--shadow-xl); }
.floating-panel.active { display: block; }
.floating-panel h3 { font-size: 14px; font-weight: 600; margin-bottom: 12px; }
.floating-panel select { width: 100%; background: var(--black-card); border: 1px solid var(--border); border-radius: 8px; padding: 8px; color: var(--white); font-size: 12px; margin-bottom: 8px; }
/* KEYBOARD SHORTCUTS PANEL */
.shortcuts-panel { position: fixed; bottom: 20px; left: 20px; background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 16px; z-index: 10000; opacity: 0; visibility: hidden; transition: all 0.3s ease; font-size: 12px; max-width: 300px; }
.shortcuts-panel.show { opacity: 1; visibility: visible; }
.shortcuts-panel h3 { font-size: 14px; font-weight: 600; margin-bottom: 8px; color: var(--brand); }
.shortcut-item { display: flex; justify-content: space-between; margin-bottom: 4px; color: var(--gray-light); }
.shortcut-key { background: var(--black-card); border: 1px solid var(--border); border-radius: 4px; padding: 2px 6px; font-family: monospace; font-size: 11px; }
/* --- UI COMPONENTS --- */
.copy-button { position: absolute; top: 8px; right: 8px; background: var(--black-card); border: 1px solid var(--border); border-radius: 4px; padding: 4px 6px; font-size: 10px; cursor: pointer; opacity: 0; transition: all 0.2s ease; color: var(--gray); font-weight: 500; }
.message.bot:hover .copy-button { opacity: 1; }
.copy-button:hover { background: var(--brand); color: var(--white); border-color: var(--brand); }
.copy-button.copied { background: var(--success); color: var(--white); border-color: var(--success); }
.credits-container { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; display: flex; align-items: center; gap: 12px; cursor: pointer; transition: all 0.2s ease; }
.credits-container:hover { border-color: var(--brand); transform: translateY(-1px); }
.credits-progress { flex: 1; min-width: 80px; }
.credits-bar { width: 100%; height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; margin-top: 4px; }
.credits-fill { height: 100%; background: var(--gradient-brand); border-radius: 2px; transition: width 0.5s ease; }
.credits-text { font-size: 12px; color: var(--gray); }
.credits-number { font-weight: 700; color: var(--brand); }
.loading-placeholder { background: var(--black-card); border-radius: 8px; padding: 16px; text-align: center; color: var(--gray); font-size: 14px; }
.loading-animation { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--border); border-radius: 50%; border-top-color: var(--brand); animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.premium-badge { position: absolute; top: -4px; right: -4px; background: var(--gradient-brand); color: var(--white); border-radius: 50%; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; }
.model-section { margin-bottom: 16px; }
.model-section-title { font-size: 10px; font-weight: 600; color: var(--gray); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; padding-left: 8px; }
.model-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
.model-option { background: var(--black-card); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 12px; text-align: center; position: relative; }
.model-option:hover { border-color: var(--brand); transform: translateY(-1px); }
.model-option.active { background: rgba(93, 92, 222, 0.1); border-color: var(--brand); box-shadow: var(--shadow-glow); }
.model-name { font-size: 16px; font-weight: 600; margin-bottom: 2px; }
.model-cost { font-size: 14px; text-transform: uppercase; opacity: 0.8; }
.model-cost.free { color: var(--success); } .model-cost.paid { color: var(--warning); }
.persona-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
.persona-card { background: var(--black-card); border: 2px solid var(--border); border-radius: 12px; padding: 16px 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; position: relative; }
.persona-card:hover { border-color: var(--brand); transform: translateY(-2px); }
.persona-card.active { border-color: var(--brand); background: rgba(93, 92, 222, 0.05); box-shadow: var(--shadow-glow); }
.persona-card.premium { border-color: var(--gold); }
.persona-avatar { width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; color: var(--white); position: relative; }
.persona-name { font-weight: 600; font-size: 18px; margin-bottom: 4px; }
.persona-desc { font-size: 13px; color: var(--gray); line-height: 1.3; }
/* --- CHAT INTERFACE --- */
.chat-container { background: var(--black-soft); border: 2px solid var(--border); border-radius: 20px; height: 500px; display: flex; flex-direction: column; overflow: hidden; position: relative; transition: all 0.3s ease; }
.chat-container.flexbox { height: 70vh; max-height: 800px; min-height: 400px; }
.chat-header { padding: 12px 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; background: var(--black-card); gap: 16px; }
.chat-title { font-weight: 600; display: flex; align-items: center; gap: 8px; font-size: 14px; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat-controls { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.control-group { display: flex; gap: 0; align-items: center; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.control-group .chat-control-btn { border: none; border-left: 1px solid var(--border); border-radius: 0; }
.control-group .chat-control-btn:first-child { border-left: none; }
.control-group .chat-control-btn:hover { background: var(--black); }
.menu-dropdown { position: relative; }
.menu-dropdown-content { position: absolute; top: calc(100% + 8px); right: 0; background: var(--black-soft); border: 1px solid var(--border); border-radius: 8px; min-width: 150px; box-shadow: var(--shadow-lg); z-index: 1010; opacity: 0; visibility: hidden; transform: translateY(-10px); transition: all 0.2s ease; }
.menu-dropdown.active .menu-dropdown-content { opacity: 1; visibility: visible; transform: translateY(0); }
.menu-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 12px; background: none; border: none; color: var(--white); font-size: 13px; text-align: left; cursor: pointer; transition: background-color 0.2s ease; }
.menu-item:hover { background: var(--black-card); }
.chat-control-btn { background: transparent; border: 1px solid var(--border); padding: 6px 10px; border-radius: 8px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 13px; white-space: nowrap; display: flex; align-items: center; gap: 6px; }
.chat-control-btn:hover:not(:disabled) { border-color: var(--brand); color: var(--brand); }
.chat-control-btn.new-chat { background: var(--gradient-brand); color: var(--white); border: none; font-weight: 600; padding: 6px 12px; }
[data-theme="light"] .chat-control-btn.new-chat { color: var(--black); }
.chat-control-btn.new-chat:hover:not(:disabled) { transform: translateY(-1px); box-shadow: var(--shadow-glow); }
.chat-control-btn.menu-toggle { padding: 6px; font-size: 18px; line-height: 1; }
.chat-messages { flex: 1; overflow-y: auto; padding: 16px; scroll-behavior: smooth; }
.message { margin-bottom: 16px; animation: slideIn 0.3s ease-out; }
@keyframes slideIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.message-header { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; font-size: 12px; color: var(--gray); }
.message-avatar { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 600; }
.message.user .message-avatar { background: var(--gradient-brand); color: var(--white); }
[data-theme="light"] .message.user .message-avatar { color: var(--black); }
.message.bot .message-avatar { background: var(--gradient-success); color: var(--white); }
[data-theme="light"] .message.bot .message-avatar { color: var(--black); }
.message-bubble { padding: 12px 16px; border-radius: 18px; font-size: 14px; line-height: 1.5; max-width: 85%; word-wrap: break-word; }
.message.user .message-bubble { margin-left: auto; background: var(--gradient-brand); color: var(--white); border-radius: 18px 18px 4px 18px; }
[data-theme="light"] .message.user .message-bubble { color: var(--black); }
.message.bot .message-bubble { background: var(--black-card); color: var(--gray-light); border: 1px solid var(--border-light); padding-right: 40px; border-radius: 18px 18px 18px 4px; position: relative; }
.message.system .message-bubble { background: rgba(93, 92, 222, 0.1); border: 1px solid var(--brand); color: var(--brand); font-size: 12px; padding: 8px 12px; font-style: italic; text-align: center; max-width: 100%; border-radius: 8px; }
.message.system .message-avatar { background: var(--gradient-brand); font-size: 12px; }
.chat-input-area { padding: 16px; border-top: 1px solid var(--border); background: var(--black-card); flex-shrink: 0; }
.chat-input-container { display: flex; gap: 8px; align-items: flex-end; }
.chat-input { flex: 1; background: var(--black); border: 1px solid var(--border); border-radius: 12px; padding: 12px; font-size: 16px; color: var(--white); resize: none; min-height: 48px; max-height: 120px; transition: all 0.2s ease; font-family: inherit; }
.chat-input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); }
.chat-input::placeholder { color: var(--gray-dark); }
.send-button { background: var(--gradient-brand); border: none; padding: 0 16px; border-radius: 12px; color: var(--white); font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; height: 48px; width: 48px;}
[data-theme="light"] .send-button { color: var(--black); }
.send-button:hover:not(:disabled) { transform: translateY(-1px); }
.send-button:disabled { opacity: 0.5; cursor: not-allowed; }
.send-button .send-icon { font-size: 20px; }
.error-message { background: rgba(239, 68, 68, 0.1); border: 1px solid var(--accent); border-radius: 8px; padding: 12px; margin: 8px 0; color: var(--accent); font-size: 14px; display: none; }
.error-message.show { display: block; animation: errorSlide 0.3s ease-out; }
@keyframes errorSlide { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
/* Typing indicator */
.typing-indicator { display: inline-flex; align-items: center; gap: 2px; }
.typing-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--brand); animation: typing 1.4s infinite; }
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing { 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; } 30% { transform: translateY(-10px); opacity: 1; } }
/* --- GLOBAL & RESPONSIVE --- */
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 16px; }
.logo { font-size: 20px; font-weight: 700; background: var(--gradient-brand); -webkit-background-clip: text; -webkit-text-fill-color: transparent; flex-shrink: 0; }
.header-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.theme-toggle { background: var(--black-card); border: 1px solid var(--border); border-radius: 50px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 14px; display: flex; align-items: center; gap: 8px; white-space: nowrap; flex-shrink: 0; }
.theme-toggle:hover { border-color: var(--brand); }
.explainer { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 24px; text-align: center; }
.explainer h3 { font-size: 20px; font-weight: 600; color: var(--brand); margin-bottom: 8px; }
.explainer p { font-size: 14px; color: var(--gray-light); line-height: 1.5; }
.modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 9000; opacity: 0; visibility: hidden; transition: all 0.3s ease; padding: 16px; }
.modal.active { opacity: 1; visibility: visible; }
.modal-content { background: var(--black-soft); border: 1px solid var(--border); border-radius: 20px; padding: 24px; max-width: 500px; width: 100%; max-height: 80vh; overflow-y: auto; }
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.modal-title { font-size: 20px; font-weight: 700; margin: 0; }
.modal-close { background: none; border: none; color: var(--gray); font-size: 20px; cursor: pointer; padding: 4px; }
.modal-close:hover { color: var(--white); }
.pricing-option { background: var(--black-card); border: 2px solid var(--border); border-radius: 16px; padding: 20px; margin-bottom: 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; }
.pricing-option:hover { border-color: var(--brand); transform: translateY(-2px); }
.pricing-option.recommended { border-color: var(--success); position: relative; }
.pricing-option.recommended::before { content: "RECOMMENDED"; position: absolute; top: -8px; left: 50%; transform: translateX(-50%); background: var(--success); color: var(--white); padding: 4px 8px; border-radius: 12px; font-size: 9px; font-weight: 700; }
.pricing-title { font-size: 16px; font-weight: 600; margin-bottom: 6px; }
.pricing-price { font-size: 24px; font-weight: 700; color: var(--success); margin-bottom: 8px; }
.pricing-features { list-style: none; text-align: left; color: var(--gray-light); margin-bottom: 12px; }
.pricing-features li { padding: 3px 0; display: flex; align-items: center; gap: 6px; font-size: 12px; }
.pricing-features li::before { content: "✓"; color: var(--success); font-weight: bold; }
.action-button { background: var(--gradient-brand); border: none; padding: 12px 12px; border-radius: 16px; color: var(--white); font-weight: 600; font-size: 16px; cursor: pointer; transition: all 0.2s ease; width: 70%; }
[data-theme="light"] .action-button { color: var(--black); }
.action-button:hover { transform: translateY(-2px); box-shadow: var(--shadow-glow); }
@media (max-width: 768px) {
.app { padding: 12px; }
.header { flex-direction: column; align-items: stretch; text-align: center; margin-bottom: 20px; }
.header-actions { justify-content: center; }
.chat-container { height: auto; flex-grow: 1; border-radius: 0; margin: 0 -12px; border-left: none; border-right: none; }
body, .app { display: flex; flex-direction: column; }
.chat-header { padding: 8px 12px; }
.chat-controls { gap: 6px; }
.chat-control-btn, .control-group .chat-control-btn { font-size: 11px; padding: 6px 8px; }
.chat-control-btn.new-chat .btn-text { display: none; } /* Hide text on mobile */
.floating-panel { width: calc(100vw - 32px); }
}
</style>
</head>
<body data-theme="light">
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<div class="app" id="app">
<!-- Quick Switch Panel -->
<div id="quickSwitchPanel" class="floating-panel">
<h3>Quick Switch</h3>
<select id="quickModelSelect"></select>
<select id="quickPersonaSelect"></select>
<button class="chat-control-btn new-chat" style="width: 100%; margin-top: 8px;" onclick="applyQuickSwitch()">Apply</button>
</div>
<!-- Keyboard Shortcuts Panel -->
<div id="shortcutsPanel" class="shortcuts-panel">
<h3>Keyboard Shortcuts</h3>
<div class="shortcut-item"><span>New Chat</span><span class="shortcut-key">Ctrl+N</span></div>
<div class="shortcut-item"><span>Fullscreen</span><span class="shortcut-key">F11</span></div>
<div class="shortcut-item"><span>Theme Toggle</span><span class="shortcut-key">Ctrl+/</span></div>
<div class="shortcut-item"><span>Send Message</span><span class="shortcut-key">Enter</span></div>
<div class="shortcut-item"><span>New Line</span><span class="shortcut-key">Shift+Enter</span></div>
<div class="shortcut-item"><span>Quick Switch</span><span class="shortcut-key">Ctrl+K</span></div>
</div>
<!-- Header -->
<header class="header">
<div class="logo">BrutallyHonest.ai</div>
<div class="header-actions">
<div class="credits-container" id="creditsDisplay">
<div class="credits-progress">
<div class="credits-text"><span class="credits-number" id="creditsAmount">10</span> / <span id="creditsMax">1000</span></div>
<div class="credits-bar"><div class="credits-fill" id="creditsFill" style="width: 1%"></div></div>
</div>
<div style="font-size: 12px; color: var(--brand); font-weight: 600;">Upgrade</div>
</div>
<div class="theme-toggle" id="themeToggle"><span id="themeIcon">☀️</span> <span id="themeText">Light</span></div>
</div>
</header>
<!-- Explainer -->
<section class="explainer">
<h3>🔥 The AI That Has Transformed Thousands</h3>
<p>Choose your AI personality, pick a model, and start chatting. <strong>Free models are unlimited.</strong> Premium costs credits.</p>
</section>
<!-- Error Display -->
<div class="error-message" id="errorMessage"></div>
<!-- Main Chat Interface -->
<div class="chat-container">
<div class="chat-header">
<div class="chat-title">
<span id="currentPersonaEmoji">🔥</span>
<span id="currentPersonaName">Loading...</span>
<span id="currentModelInfo" style="font-size: 12px; color: var(--gray);"></span>
</div>
<div class="chat-controls">
<button class="chat-control-btn new-chat" id="newChatBtn">✨ <span class="btn-text">New Chat</span></button>
<div class="control-group">
<button class="chat-control-btn" id="layoutToggle" title="Toggle Layout">📐</button>
<button class="chat-control-btn" id="chatFullscreenBtn" title="Toggle Fullscreen">⛶</button>
</div>
<div class="menu-dropdown" id="menuDropdown">
<button class="chat-control-btn menu-toggle" id="menuToggle" title="More Options">⋯</button>
<div class="menu-dropdown-content">
<button class="menu-item" id="quickSwitchBtn">⚡ Quick Switch</button>
<button class="menu-item" id="showShortcutsBtn">⌨️ Shortcuts</button>
<hr style="border: none; border-top: 1px solid var(--border); margin: 4px 0;">
<button class="menu-item" id="saveBtn">💾 Save Chat</button>
<button class="menu-item" id="clearBtn">🗑️ Clear Chat</button>
<button class="menu-item" id="exportBtn">📤 Export Chat</button>
</div>
</div>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<div class="message bot">
<div class="message-header">
<div class="message-avatar">🔥</div>
<span>Loading...</span>
<span>•</span>
<span>Just now</span>
</div>
<div class="message-bubble">
Welcome! Configuring your experience...
<button class="copy-button" onclick="copyMessage(this)" data-text="Welcome! Configuring your experience...">Copy</button>
</div>
</div>
</div>
<div class="chat-input-area">
<div class="chat-input-container">
<textarea class="chat-input" id="chatInput" placeholder="Type your message here..." rows="1"></textarea>
<button class="send-button" id="sendButton" title="Send (Enter)"><span class="send-icon">→</span></button>
</div>
</div>
</div>
<!-- Model & Persona Selection -->
<div class="model-selection-container" id="modelSelectionContainer">
<h4 style="font-weight: 600; margin: 24px 0 16px; color: var(--brand);">Select AI Model</h4>
<div class="loading-placeholder" id="modelsLoadingPlaceholder"><div class="loading-animation"></div> Loading models...</div>
<div id="modelsContainer"></div>
</div>
<div class="persona-selection-container" id="personaSelectionContainer">
<h4 style="font-weight: 600; margin: 24px 0 16px; color: var(--brand);">Choose Your AI Personality</h4>
<div class="loading-placeholder" id="personasLoadingPlaceholder"><div class="loading-animation"></div> Loading personalities...</div>
<div class="persona-grid" id="personaGrid"></div>
</div>
</div>
<!-- Credits Modal -->
<div class="modal" id="creditsModal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">💎 Get More Credits</h2>
<button class="modal-close" id="closeCreditsModal">×</button>
</div>
<div class="pricing-option recommended">
<div class="pricing-title">Monthly Subscription</div>
<div class="pricing-price">$9<span style="font-size: 12px; color: var(--gray);">/month</span></div>
<ul class="pricing-features">
<li>500 credits per month</li>
<li>Unlimited free models</li>
<li>Access to all premium models</li>
<li>Credits roll over (up to 1000)</li>
<li>Priority support</li>
</ul>
<button class="action-button" onclick="purchaseMonthly()">Subscribe Now ✨</button>
</div>
<div class="pricing-option">
<div class="pricing-title">Pay As You Go</div>
<div class="pricing-price">From $2.99</div>
<ul class="pricing-features">
<li>100 credits for $2.99</li>
<li>250 credits for $5.99</li>
<li>500 credits for $9.99</li>
<li>Credits never expire</li>
</ul>
<button class="action-button" onclick="purchaseCredits()">Buy Credits 💰</button>
</div>
</div>
</div>
<script>
// ===================================================================================
// CONFIG & STATE MANAGEMENT
// ===================================================================================
const CONFIG = {
mainWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/bha-mvp',
configWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/config'
};
class BHAStateManager {
constructor() {
this.state = {
user: { credits: 1000, maxCredits: 1000, userId: this.generateUserId() },
chat: { currentModel: null, currentPersona: null, currentModelCost: 0, currentModelName: '', conversationId: null, messages: [], isTyping: false },
ui: { currentTheme: 'light', isFullscreen: false, isFlexLayout: false },
config: { personas: [], models: [], modelsByTier: { free: [], standard: [], premium: [] }, isLoaded: false }
};
this.listeners = new Map();
this.loadPersistedState();
}
generateUserId() {
let stored = localStorage.getItem('bha_user_id');
if (stored) return stored;
let id = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('bha_user_id', id);
return id;
}
loadPersistedState() {
this.state.ui.currentTheme = localStorage.getItem('bha_selectedTheme') || 'light';
this.state.chat.currentPersona = localStorage.getItem('bha_selectedPersona');
this.state.chat.currentModel = localStorage.getItem('bha_selectedModel');
this.state.chat.conversationId = localStorage.getItem('bha_conversation_id') || this.generateConversationId();
this.state.ui.isFlexLayout = localStorage.getItem('bha_flexboxMode') === 'true';
}
generateConversationId() { return 'conv_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5); }
subscribe(key, callback) {
if (!this.listeners.has(key)) { this.listeners.set(key, []); }
this.listeners.get(key).push(callback);
}
setState(path, value) {
const keys = path.split('.');
let current = this.state;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
}
const finalKey = keys[keys.length - 1];
if (current[finalKey] === value) return;
console.log(`🔄 State Change: ${path} to "${value}"`);
current[finalKey] = value;
if (path.startsWith('chat.')) localStorage.setItem(`bha_${finalKey}`, value);
if (this.listeners.has(path)) this.listeners.get(path).forEach(cb => cb(value));
}
getState(path) { return path.split('.').reduce((acc, key) => acc?.[key], this.state); }
resetConversationContext(reason = 'manual') {
const newId = this.generateConversationId();
this.setState('chat.conversationId', newId);
this.setState('chat.messages', []);
console.log(`🔄 Context Reset (${reason}): New conversation ${newId}`);
return newId;
}
}
const StateManager = new BHAStateManager();
// ===================================================================================
// UTILITY FUNCTIONS
// ===================================================================================
function showToast(message, type = 'success', duration = 3000) {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(100%)';
setTimeout(() => toast.remove(), 300);
}, duration);
}
function showError(message) {
const errorEl = document.getElementById('errorMessage');
errorEl.textContent = message;
errorEl.classList.add('show');
setTimeout(() => errorEl.classList.remove('show'), 5000);
}
function escapeHtml(unsafe) {
return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
}
// ===================================================================================
// CONFIGURATION MANAGER
// ===================================================================================
class BHAConfigManager {
constructor() {
this.config = {
personas: [],
models: [],
modelsByTier: { free: [], standard: [], premium: [] },
features: {}
};
this.isLoaded = false;
}
async loadConfiguration() {
try {
console.log('🔄 Loading configuration from:', CONFIG.configWebhookUrl);
const response = await fetch(CONFIG.configWebhookUrl);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.config = await response.json();
this.isLoaded = true;
StateManager.setState('config', this.config);
StateManager.setState('config.isLoaded', true);
console.log('✅ Configuration loaded:', this.config);
this.renderDynamicContent();
this.initializeDefaults();
} catch (error) {
console.error('❌ Config load failed:', error);
this.loadFallbackConfig();
}
}
loadFallbackConfig() {
console.log('🔄 Loading fallback configuration...');
this.config = {
personas: [
{
id: 'QuickResonanceSpark_Persona_Haiku',
name: 'Brutally Honest',
emoji: '🔥',
description: 'No BS, just raw truth',
isPremium: false,
gradientColor: 'linear-gradient(135deg, #EF4444, #F59E0B)'
},
{
id: 'ResonanceSession_Orchestrator_Opus',
name: 'Deep Guide',
emoji: '🧠',
description: 'Profound insights & breakthrough clarity',
isPremium: false,
gradientColor: 'linear-gradient(135deg, #10B981, #06D6A0)'
}
],
models: [
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' },
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' }
],
modelsByTier: {
free: [
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' },
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' }
],
standard: [],
premium: []
}
};
this.isLoaded = true;
StateManager.setState('config', this.config);
StateManager.setState('config.isLoaded', true);
this.renderDynamicContent();
this.initializeDefaults();
}
renderDynamicContent() {
this.renderPersonas();
this.renderModels();
this.populateQuickSwitcher();
}
renderPersonas() {
const container = document.getElementById('personaGrid');
const loadingPlaceholder = document.getElementById('personasLoadingPlaceholder');
if (loadingPlaceholder) loadingPlaceholder.style.display = 'none';
container.innerHTML = this.config.personas.map(persona => `
<div class="persona-card ${persona.isPremium ? 'premium' : ''}"
data-persona-id="${persona.id}"
data-persona-name="${persona.name}"
data-persona-emoji="${persona.emoji}">
<div class="persona-avatar" style="background: ${persona.gradientColor};">
${persona.emoji}
${persona.isPremium ? '<div class="premium-badge">💎</div>' : ''}
</div>
<div class="persona-name">${persona.name}</div>
<div class="persona-desc">${persona.description}</div>
</div>
`).join('');
document.querySelectorAll('.persona-card').forEach(card => {
card.addEventListener('click', () => this.selectPersona(card));
});
}
renderModels() {
const container = document.getElementById('modelsContainer');
const loadingPlaceholder = document.getElementById('modelsLoadingPlaceholder');
if (loadingPlaceholder) loadingPlaceholder.style.display = 'none';
const tiers = ['free', 'standard', 'premium'];
const tierLabels = {
'free': 'Free Models',
'standard': 'Standard Models (5-15 credits)',
'premium': 'Premium Models (15+ credits)'
};
container.innerHTML = tiers.map(tier => {
const models = this.config.modelsByTier[tier] || [];
if (models.length === 0) return '';
return `
<div class="model-section" data-tier="${tier}">
<div class="model-section-title">${tierLabels[tier]}</div>
<div class="model-grid">
${models.map(model => `
<div class="model-option"
data-model-id="${model.id}"
data-model-name="${model.name}"
data-model-cost="${model.cost}">
<div class="model-name">${model.name}</div>
<div class="model-cost ${model.cost === 0 ? 'free' : 'paid'}">
${model.cost === 0 ? 'FREE' : `${model.cost} credits`}
</div>
</div>
`).join('')}
</div>
</div>
`;
}).join('');
document.querySelectorAll('.model-option').forEach(option => {
option.addEventListener('click', () => this.selectModel(option));
});
}
populateQuickSwitcher() {
if (!this.isLoaded) return;
const modelSelect = document.getElementById('quickModelSelect');
const personaSelect = document.getElementById('quickPersonaSelect');
modelSelect.innerHTML = this.config.models.map(m =>
`<option value="${m.id}">${m.name}</option>`
).join('');
personaSelect.innerHTML = this.config.personas.map(p =>
`<option value="${p.id}">${p.emoji} ${p.name}</option>`
).join('');
modelSelect.value = StateManager.getState('chat.currentModel') || '';
personaSelect.value = StateManager.getState('chat.currentPersona') || '';
}
selectPersona(personaElement) {
document.querySelectorAll('.persona-card').forEach(el => el.classList.remove('active'));
personaElement.classList.add('active');
const personaId = String(personaElement.dataset.personaId || '');
const personaName = String(personaElement.dataset.personaName || '');
const personaEmoji = String(personaElement.dataset.personaEmoji || '');
StateManager.setState('chat.currentPersona', personaId);
document.getElementById('currentPersonaEmoji').textContent = personaEmoji;
document.getElementById('currentPersonaName').textContent = personaName;
this.addSystemMessage(`🎭 Now chatting with ${personaName}`);
this.populateQuickSwitcher();
console.log('✅ Persona Selected:', personaId);
}
selectModel(modelElement) {
document.querySelectorAll('.model-option').forEach(el => el.classList.remove('active'));
modelElement.classList.add('active');
const modelId = String(modelElement.dataset.modelId || '');
const modelName = String(modelElement.dataset.modelName || '');
const modelCost = parseInt(modelElement.dataset.modelCost || '0');
StateManager.setState('chat.currentModel', modelId);
StateManager.setState('chat.currentModelCost', modelCost);
StateManager.setState('chat.currentModelName', modelName);
const modelInfo = document.getElementById('currentModelInfo');
if (modelInfo) {
modelInfo.textContent = `(${modelName})`;
}
this.addSystemMessage(`🤖 Now using ${modelName}`);
this.populateQuickSwitcher();
console.log('✅ Model Selected:', modelId);
}
addSystemMessage(content) {
const messagesContainer = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = 'message system';
messageDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">⚙️</div>
<span>System</span>
<span>•</span>
<span>${new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
</div>
<div class="message-bubble">
${content}
</div>
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
initializeDefaults() {
const savedPersonaId = localStorage.getItem('bha_selectedPersona');
const savedModelId = localStorage.getItem('bha_selectedModel');
if (this.config.personas.length > 0) {
let targetPersona = savedPersonaId ?
document.querySelector(`[data-persona-id="${savedPersonaId}"]`) :
document.querySelector('.persona-card');
if (targetPersona) {
this.selectPersona(targetPersona);
}
}
if (this.config.models.length > 0) {
let targetModel = savedModelId ?
document.querySelector(`[data-model-id="${savedModelId}"]`) :
document.querySelector('.model-option');
if (targetModel) {
this.selectModel(targetModel);
}
}
setTimeout(() => {
this.updateWelcomeMessage();
}, 100);
}
updateWelcomeMessage() {
const messagesContainer = document.getElementById('chatMessages');
const currentPersonaId = StateManager.getState('chat.currentPersona');
const currentPersona = this.config.personas.find(p => p.id === currentPersonaId);
if (currentPersona) {
messagesContainer.innerHTML = `
<div class="message bot">
<div class="message-header">
<div class="message-avatar">${currentPersona.emoji}</div>
<span>${currentPersona.name}</span>
<span>•</span>
<span>Just now</span>
</div>
<div class="message-bubble">
I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today?
<button class="copy-button" onclick="copyMessage(this)" data-text="I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today?">Copy</button>
</div>
</div>
`;
}
}
}
// ===================================================================================
// CHAT FUNCTIONS
// ===================================================================================
function addMessage(content, isUser = false, persona = null) {
const messagesContainer = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user' : 'bot'}`;
const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
const currentPersonaId = StateManager.getState('chat.currentPersona');
const personas = StateManager.getState('config.personas') || [];
const currentPersona = personas.find(p => p.id === currentPersonaId);
const sender = isUser ? 'You' : (persona || currentPersona?.name || 'AI');
const avatar = isUser ? '👤' : (currentPersona?.emoji || '🤖');
const processedContent = isUser ?
escapeHtml(content) :
(typeof marked !== 'undefined' ? marked.parse(content) : content);
const copyButton = isUser ? '' :
`<button class="copy-button" onclick="copyMessage(this)" data-text="${escapeHtml(content)}">Copy</button>`;
messageDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">${avatar}</div>
<span>${sender}</span>
<span>•</span>
<span>${timestamp}</span>
</div>
<div class="message-bubble">
${processedContent}
${copyButton}
</div>
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
const messages = StateManager.getState('chat.messages') || [];
messages.push({
content: content,
isUser: isUser,
timestamp: Date.now(),
persona: persona
});
StateManager.setState('chat.messages', messages);
}
function addTypingIndicator() {
const messagesContainer = document.getElementById('chatMessages');
const typingDiv = document.createElement('div');
typingDiv.id = 'typingIndicator';
typingDiv.className = 'message bot';
const currentPersonaId = StateManager.getState('chat.currentPersona');
const personas = StateManager.getState('config.personas') || [];
const currentPersona = personas.find(p => p.id === currentPersonaId);
typingDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">${currentPersona?.emoji || '🤖'}</div>
<span>${currentPersona?.name || 'AI'}</span>
<span>•</span>
<span>typing...</span>
</div>
<div class="message-bubble">
<div class="typing-indicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
`;
messagesContainer.appendChild(typingDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function removeTypingIndicator() {
const indicator = document.getElementById('typingIndicator');
if (indicator) {
indicator.remove();
}
}
async function sendMessage() {
const input = document.getElementById('chatInput');
const message = input.value.trim();
if (!message || StateManager.getState('chat.isTyping')) return;
const currentPersona = StateManager.getState('chat.currentPersona');
const currentModel = StateManager.getState('chat.currentModel');
let conversationId = StateManager.getState('chat.conversationId');
if (!conversationId) {
conversationId = StateManager.generateConversationId();
StateManager.setState('chat.conversationId', conversationId);
}
if (!currentPersona || !currentModel) {
showError('Please select a persona and model before sending a message.');
return;
}
const modelCost = StateManager.getState('chat.currentModelCost') || 0;
const userCredits = StateManager.getState('user.credits');
if (modelCost > 0 && userCredits < modelCost) {
showError(`Insufficient credits! You need ${modelCost} credits.`);
showCreditsModal();
return;
}
addMessage(message, true);
input.value = '';
input.style.height = 'auto';
addTypingIndicator();
StateManager.setState('chat.isTyping', true);
const sendBtn = document.getElementById('sendButton');
sendBtn.disabled = true;
try {
const payload = {
user_id: String(StateManager.getState('user.userId')),
bot_to_call: String(currentPersona),
full_prompt_for_llm: String(message),
raw_user_situation: String(message),
selected_model_for_openrouter: String(currentModel),
conversation_id: String(conversationId),
timestamp: new Date().toISOString(),
frontend_state_snapshot: JSON.stringify({
persona: currentPersona,
model: currentModel,
conversation_id: conversationId,
message_count: StateManager.getState('chat.messages').length || 0
})
};
console.log('🚀 Sending Payload:', payload);
const response = await fetch(CONFIG.mainWebhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log('✅ Response received:', data);
let aiResponseText = "I'm having trouble formulating a response right now.";
if (data && typeof data.response === 'string') {
aiResponseText = data.response;
} else if (data.bot_response && data.bot_response.analysis) {
aiResponseText = data.bot_response.analysis;
} else if (data.final_response_payload && data.final_response_payload.bot_response) {
aiResponseText = data.final_response_payload.bot_response.analysis;
}
removeTypingIndicator();
addMessage(aiResponseText, false);
if (data.credits_remaining !== undefined) {
StateManager.setState('user.credits', data.credits_remaining);
} else if (modelCost > 0) {
StateManager.setState('user.credits', Math.max(0, userCredits - modelCost));
}
updateCreditsDisplay();
if (data.tool_used) {
showToast('Enhanced response generated', 'success', 2000);
}
} catch (error) {
console.error('❌ Error sending message:', error);
removeTypingIndicator();
showError('Failed to send message. Please try again.');
} finally {
StateManager.setState('chat.isTyping', false);
sendBtn.disabled = false;
}
}
// ===================================================================================
// UI CONTROL FUNCTIONS
// ===================================================================================
function applyQuickSwitch() {
const modelId = document.getElementById('quickModelSelect').value;
const personaId = document.getElementById('quickPersonaSelect').value;
const modelElement = document.querySelector(`[data-model-id="${modelId}"]`);
const personaElement = document.querySelector(`[data-persona-id="${personaId}"]`);
if (modelElement) configManager.selectModel(modelElement);
if (personaElement) configManager.selectPersona(personaElement);
document.getElementById('quickSwitchPanel').classList.remove('active');
showToast('Settings applied!', 'success');
}
function toggleFullscreen(forceState) {
const isFullscreen = forceState ?? !StateManager.getState('ui.isFullscreen');
StateManager.setState('ui.isFullscreen', isFullscreen);
document.getElementById('app').classList.toggle('fullscreen', isFullscreen);
document.getElementById('chatFullscreenBtn').textContent = isFullscreen ? '▣' : '⛶';
localStorage.setItem('bha_fullscreenMode', isFullscreen.toString());
}
function toggleLayout(forceState) {
const isFlexLayout = forceState ?? !StateManager.getState('ui.isFlexLayout');
StateManager.setState('ui.isFlexLayout', isFlexLayout);
document.querySelector('.chat-container').classList.toggle('flexbox', isFlexLayout);
localStorage.setItem('bha_flexboxMode', isFlexLayout.toString());
showToast(`Layout: ${isFlexLayout ? 'Flexible' : 'Fixed'}`, 'success', 1000);
}
function toggleTheme() {
const newTheme = StateManager.getState('ui.currentTheme') === 'light' ? 'dark' : 'light';
StateManager.setState('ui.currentTheme', newTheme);
document.body.dataset.theme = newTheme;
document.getElementById('themeIcon').textContent = newTheme === 'light' ? '☀️' : '🌙';
document.getElementById('themeText').textContent = newTheme.charAt(0).toUpperCase() + newTheme.slice(1);
localStorage.setItem('bha_selectedTheme', newTheme);
}
function startNewChat() {
if (confirm('Start a new conversation? This cannot be undone.')) {
StateManager.resetConversationContext('new_chat_button');
configManager.updateWelcomeMessage();
showToast('✨ New chat started', 'success');
}
}
function saveConversation() {
showToast('💾 Save feature coming soon', 'warning');
}
function clearChat() {
if (confirm('Clear all messages? This is permanent.')) {
StateManager.setState('chat.messages', []);
configManager.updateWelcomeMessage();
showToast('🗑️ Chat cleared', 'success');
}
}
function exportChat() {
const messages = StateManager.getState('chat.messages') || [];
if (messages.length === 0) {
showToast('No messages to export yet!', 'error');
return;
}
const currentPersonaId = StateManager.getState('chat.currentPersona');
const currentModel = StateManager.getState('chat.currentModel');
let content = `BrutallyHonest.ai Chat Export\n`;
content += `Exported: ${new Date().toLocaleString()}\n`;
content += `Model: ${currentModel}\n`;
content += `Persona: ${currentPersonaId}\n`;
content += '='.repeat(50) + '\n\n';
messages.forEach(msg => {
const timestamp = new Date(msg.timestamp).toLocaleString();
const speaker = msg.isUser ? 'You' : (msg.persona || 'AI');
content += `[${timestamp}] ${speaker}: ${msg.content}\n\n`;
});
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `BHA_Chat_${new Date().toISOString().split('T')[0]}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('Chat exported successfully', 'success');
}
function updateCreditsDisplay() {
const credits = StateManager.getState('user.credits');
const maxCredits = StateManager.getState('user.maxCredits');
document.getElementById('creditsAmount').textContent = credits;
document.getElementById('creditsMax').textContent = maxCredits;
const percentage = (credits / maxCredits) * 100;
document.getElementById('creditsFill').style.width = percentage + '%';
}
function showCreditsModal() {
document.getElementById('creditsModal').classList.add('active');
}
function hideCreditsModal() {
document.getElementById('creditsModal').classList.remove('active');
}
function purchaseMonthly() {
const userId = StateManager.getState('user.userId');
window.open(`https://buy.stripe.com/YOUR_MONTHLY_LINK?client_reference_id=${userId}`, '_blank');
}
function purchaseCredits() {
const userId = StateManager.getState('user.userId');
window.open(`https://buy.stripe.com/YOUR_CREDIT_PACK_LINK?client_reference_id=${userId}`, '_blank');
}
function copyMessage(button) {
const text = button.dataset.text;
navigator.clipboard.writeText(text).then(() => {
const originalText = button.textContent;
button.textContent = 'Copied!';
button.classList.add('copied');
setTimeout(() => {
button.textContent = originalText;
button.classList.remove('copied');
}, 2000);
showToast('Message copied to clipboard', 'success', 2000);
}).catch(err => {
console.error('Copy failed:', err);
showToast('Failed to copy message', 'error', 3000);
});
}
// ===================================================================================
// INITIALIZATION
// ===================================================================================
const configManager = new BHAConfigManager();
async function init() {
console.log("🔥 BrutallyHonest.ai Initializing (The Right Way)...");
// Apply saved UI state
const savedTheme = StateManager.getState('ui.currentTheme');
document.body.dataset.theme = savedTheme;
document.getElementById('themeIcon').textContent = savedTheme === 'light' ? '☀️' : '🌙';
document.getElementById('themeText').textContent = savedTheme.charAt(0).toUpperCase() + savedTheme.slice(1);
if (StateManager.getState('ui.isFlexLayout')) {
document.querySelector('.chat-container').classList.add('flexbox');
}
if (StateManager.getState('ui.isFullscreen')) {
toggleFullscreen(true);
}
// Event Listeners - The Unified Command Center
document.getElementById('themeToggle').addEventListener('click', toggleTheme);
document.getElementById('newChatBtn').addEventListener('click', startNewChat);
document.getElementById('layoutToggle').addEventListener('click', () => toggleLayout());
document.getElementById('chatFullscreenBtn').addEventListener('click', () => toggleFullscreen());
document.getElementById('menuToggle').addEventListener('click', (e) => {
e.stopPropagation();
document.getElementById('menuDropdown').classList.toggle('active');
});
document.getElementById('saveBtn').addEventListener('click', saveConversation);
document.getElementById('clearBtn').addEventListener('click', clearChat);
document.getElementById('exportBtn').addEventListener('click', exportChat);
document.getElementById('sendButton').addEventListener('click', sendMessage);
// Credits system
document.getElementById('creditsDisplay').addEventListener('click', showCreditsModal);
document.getElementById('closeCreditsModal').addEventListener('click', hideCreditsModal);
// Quick Switch and Shortcuts from dropdown menu
document.getElementById('quickSwitchBtn').addEventListener('click', (e) => {
e.stopPropagation(); // Prevent dropdown from closing
document.getElementById('menuDropdown').classList.remove('active'); // Close dropdown
document.getElementById('quickSwitchPanel').classList.toggle('active'); // Open quick switch
});
document.getElementById('showShortcutsBtn').addEventListener('click', (e) => {
e.stopPropagation(); // Prevent dropdown from closing immediately
document.getElementById('menuDropdown').classList.remove('active'); // Close dropdown
const panel = document.getElementById('shortcutsPanel');
panel.classList.toggle('show');
setTimeout(() => panel.classList.remove('show'), 5000);
});
// Input handlers
const chatInput = document.getElementById('chatInput');
chatInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
chatInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
// Global click handlers for closing popups
window.addEventListener('click', (e) => {
const modal = document.getElementById('creditsModal');
if (e.target === modal) {
hideCreditsModal();
}
if (!e.target.closest('#menuDropdown')) {
document.getElementById('menuDropdown').classList.remove('active');
}
if (!e.target.closest('.floating-panel')) {
document.getElementById('quickSwitchPanel').classList.remove('active');
}
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
if (StateManager.getState('ui.isFullscreen')) toggleFullscreen(false);
document.getElementById('menuDropdown').classList.remove('active');
document.getElementById('quickSwitchPanel').classList.remove('active');
document.getElementById('shortcutsPanel').classList.remove('show');
hideCreditsModal();
}
if (e.key === 'F11') {
e.preventDefault();
toggleFullscreen();
}
if ((e.ctrlKey || e.metaKey) && e.key === '/') {
e.preventDefault();
toggleTheme();
}
if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
e.preventDefault();
startNewChat();
}
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('quickSwitchBtn').click();
}
});
updateCreditsDisplay();
await configManager.loadConfiguration();
console.log('✅ BrutallyHonest.ai initialization complete!');
}
// Global function assignments
window.applyQuickSwitch = applyQuickSwitch;
window.copyMessage = copyMessage;
window.purchaseMonthly = purchaseMonthly;
window.purchaseCredits = purchaseCredits;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
</script>
</body>
</html>
button shortended go sleep
<!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"
/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<title>
BrutallyHonest.ai - Transform Your Bullsh*t Into Breakthroughs
</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
:root {
/* Light Theme (Default) */
--black: #FFFFFF;
--black-soft: #F8F9FA;
--black-card: #F1F3F4;
--border: #E8EAED;
--border-light: #DADCE0;
--white: #000000;
--gray: #5F6368;
--gray-light: #3C4043;
--gray-dark: #80868B;
--brand: #5D5CDE;
--brand-light: #7C7CE8;
--brand-dark: #4B4BC7;
--accent: #EF4444;
--success: #22C55E;
--warning: #F59E0B;
--gold: #FCD34D;
--gradient-brand: linear-gradient(135deg, #5D5CDE 0%, #7C7CE8 100%);
--gradient-fire: linear-gradient(135deg, #EF4444 0%, #F59E0B 100%);
--gradient-success: linear-gradient(135deg, #22C55E 0%, #4ADE80 100%);
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.08);
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.12), 0 1px 2px -1px rgb(0 0 0 / 0.12);
--shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06);
--shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05);
--shadow-glow: 0 0 0 1px rgba(93, 92, 222, 0.3), 0 0 20px rgba(93, 92, 222, 0.2);
}
.app {
padding: 0 16px 16px 16px;
}
/* Dark Theme Override */
[data-theme="dark"] {
--black: #0F0F0F;
--black-soft: #1A1A1A;
--black-card: #262626;
--border: #404040;
--border-light: #525252;
--white: #FFFFFF;
--gray: #A3A3A3;
--gray-light: #D4D4D4;
--gray-dark: #737373;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
html {
position: static;
height: auto;
overflow: auto;
}
body {
background: var(--black);
color: var(--white);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background-color 0.5s ease, color 0.5s ease;
min-height: 100vh;
overflow-x: hidden;
position: relative;
}
.app {
max-width: 1200px;
margin: 0 auto;
padding: 16px;
min-height: 100vh;
position: relative;
width: 100%;
transition: all 0.3s ease;
}
/* Toast Container */
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
pointer-events: none;
}
.toast {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 8px;
box-shadow: var(--shadow-lg);
backdrop-filter: blur(8px);
animation: toastSlideIn 0.3s ease-out;
pointer-events: all;
font-size: 14px;
max-width: 300px;
}
.toast.success {
border-color: var(--success);
background: rgba(34, 197, 94, 0.1);
}
.toast.error {
border-color: var(--accent);
background: rgba(239, 68, 68, 0.1);
}
@keyframes toastSlideIn {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* FULLSCREEN MODE */
.app.fullscreen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
max-width: none;
padding: 0;
z-index: 10000;
background: var(--black);
}
.app.fullscreen .header,
.app.fullscreen .explainer,
.app.fullscreen .model-selection-container,
.app.fullscreen .persona-selection-container {
display: none;
}
.app.fullscreen .chat-container {
height: 100vh;
border-radius: 0;
border: none;
}
.fullscreen-toggle {
background: var(--black-card);
border: 1px solid var(--border);
padding: 6px 8px;
border-radius: 6px;
color: var(--gray);
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
}
.fullscreen-toggle:hover {
border-color: var(--brand);
color: var(--brand);
}
.app.fullscreen .fullscreen-toggle {
position: absolute;
top: 16px;
right: 16px;
z-index: 10001;
background: var(--black-soft);
border: 1px solid var(--border);
}
/* FLOATING CONTROLS FOR FULLSCREEN */
.floating-controls {
position: fixed;
top: 16px;
right: 16px;
z-index: 10001;
display: none;
gap: 8px;
}
.app.fullscreen .floating-controls {
display: flex;
}
.floating-panel {
position: fixed;
top: 60px;
right: 16px;
width: 250px;
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px;
z-index: 10002;
display: none;
box-shadow: var(--shadow-xl);
}
.floating-panel.active {
display: block;
}
.floating-panel h3 {
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
}
.floating-panel select {
width: 100%;
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px;
color: var(--white);
font-size: 12px;
margin-bottom: 8px;
}
/* KEYBOARD SHORTCUTS PANEL */
.shortcuts-panel {
position: fixed;
bottom: 20px;
left: 20px;
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px;
z-index: 10000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
font-size: 12px;
max-width: 300px;
}
.shortcuts-panel.show {
opacity: 1;
visibility: visible;
}
.shortcuts-panel h3 {
font-size: 14px;
font-weight: 600;
margin-bottom: 8px;
color: var(--brand);
}
.shortcut-item {
display: flex;
justify-content: space-between;
margin-bottom: 4px;
color: var(--gray-light);
}
.shortcut-key {
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 4px;
padding: 2px 6px;
font-family: monospace;
font-size: 11px;
}
/* COPY FUNCTIONALITY */
.message.bot .message-bubble {
position: relative;
}
.message.bot:hover .copy-button {
opacity: 1;
}
.copy-button {
position: absolute;
top: 8px;
right: 8px;
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 4px;
padding: 4px 6px;
font-size: 10px;
cursor: pointer;
opacity: 0;
transition: all 0.2s ease;
color: var(--gray);
font-weight: 500;
}
.copy-button:hover {
background: var(--brand);
color: var(--white);
border-color: var(--brand);
}
.copy-button.copied {
background: var(--success);
color: var(--white);
border-color: var(--success);
}
/* Credit System */
.credits-container {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 12px;
padding: 12px 16px;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.credits-container:hover {
border-color: var(--brand);
transform: translateY(-1px);
}
.credits-progress {
flex: 1;
min-width: 80px;
}
.credits-bar {
width: 100%;
height: 4px;
background: var(--border);
border-radius: 2px;
overflow: hidden;
margin-top: 4px;
}
.credits-fill {
height: 100%;
background: var(--gradient-brand);
border-radius: 2px;
transition: width 0.5s ease;
}
.credits-text {
font-size: 12px;
color: var(--gray);
}
.credits-number {
font-weight: 700;
color: var(--brand);
}
/* Dynamic Content Loading */
.loading-placeholder {
background: var(--black-card);
border-radius: 8px;
padding: 16px;
text-align: center;
color: var(--gray);
font-size: 14px;
}
.loading-animation {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid var(--border);
border-radius: 50%;
border-top-color: var(--brand);
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Premium Indicators */
.premium-badge {
position: absolute;
top: -4px;
right: -4px;
background: var(--gradient-brand);
color: var(--white);
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 700;
}
/* Model Organization */
.model-section {
margin-bottom: 16px;
}
.model-section-title {
font-size: 10px;
font-weight: 600;
color: var(--gray);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 8px;
padding-left: 8px;
}
.model-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 8px;
}
.model-option {
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px 12px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
text-align: center;
position: relative;
}
.model-option:hover {
border-color: var(--brand);
transform: translateY(-1px);
}
.model-option.active {
background: rgba(93, 92, 222, 0.1);
border-color: var(--brand);
box-shadow: var(--shadow-glow);
}
.model-name {
font-size: 16px;
font-weight: 600;
margin-bottom: 2px;
}
.model-cost {
font-size: 14px;
text-transform: uppercase;
opacity: 0.8;
}
.model-cost.free {
color: var(--success);
}
.model-cost.paid {
color: var(--warning);
}
/* Persona Cards */
.persona-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 12px;
}
.persona-card {
background: var(--black-card);
border: 2px solid var(--border);
border-radius: 12px;
padding: 16px 12px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
position: relative;
}
.persona-card:hover {
border-color: var(--brand);
transform: translateY(-2px);
}
.persona-card.active {
border-color: var(--brand);
background: rgba(93, 92, 222, 0.05);
box-shadow: var(--shadow-glow);
}
.persona-card.premium {
border-color: var(--gold);
}
.persona-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
margin: 0 auto 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: var(--white);
position: relative;
}
.persona-name {
font-weight: 600;
font-size: 18px;
margin-bottom: 4px;
}
.persona-desc {
font-size: 13px;
color: var(--gray);
line-height: 1.3;
}
/* Chat Interface */
.chat-container {
background: var(--black-soft);
border: 3px solid var(--border);
border-radius: 20px;
height: 500px;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.chat-header {
padding: 16px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
background: var(--black-card);
}
.chat-title {
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
flex: 1;
}
.chat-controls {
display: flex;
align-items: center;
gap: 8px;
}
.control-group {
display: flex;
gap: 4px;
align-items: center;
border: 1px solid var(--border);
border-radius: 8px;
padding: 2px;
}
.menu-dropdown {
position: relative;
}
.menu-dropdown-content {
position: absolute;
top: 100%;
right: 0;
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 8px;
min-width: 140px;
box-shadow: var(--shadow-lg);
z-index: 1000;
display: none;
margin-top: 4px;
}
.menu-dropdown.active .menu-dropdown-content {
display: block;
}
.menu-item {
display: block;
width: 100%;
padding: 8px 12px;
background: none;
border: none;
color: var(--white);
font-size: 12px;
text-align: left;
cursor: pointer;
transition: background-color 0.2s ease;
}
.menu-item:hover {
background: var(--black-card);
}
.menu-item:first-child {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.menu-item:last-child {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
.chat-control-btn {
background: transparent;
border: 1px solid var(--border);
padding: 6px 8px;
border-radius: 6px;
color: var(--gray);
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
white-space: nowrap;
}
.chat-control-btn:hover {
border-color: var(--brand);
color: var(--brand);
}
.chat-control-btn.new-chat {
background: var(--gradient-brand);
color: var(--white);
border: none;
font-weight: 600;
}
[data-theme="light"] .chat-control-btn.new-chat {
color: var(--black);
}
.chat-control-btn.new-chat:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-glow);
}
.control-group .chat-control-btn {
border: none;
padding: 4px 6px;
}
.control-group .chat-control-btn:hover {
background: var(--black-card);
}
.chat-container.flexbox {
height: 70vh;
max-height: 800px;
min-height: 400px;
}
.layout-toggle {
background: var(--black-card);
border: 1px solid var(--border);
padding: 6px 8px;
border-radius: 6px;
color: var(--gray);
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
scroll-behavior: smooth;
}
.message {
margin-bottom: 16px;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.message-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
font-size: 12px;
color: var(--gray);
}
.message-avatar {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 600;
}
.message.user .message-avatar {
background: var(--gradient-brand);
color: var(--white);
}
[data-theme="light"] .message.user .message-avatar {
color: var(--black);
}
.message.bot .message-avatar {
background: var(--gradient-success);
color: var(--white);
}
[data-theme="light"] .message.bot .message-avatar {
color: var(--black);
}
.message-bubble {
padding: 12px 16px;
border-radius: 18px;
font-size: 14px;
line-height: 1.5;
max-width: 85%;
word-wrap: break-word;
}
.message.user .message-bubble {
margin-left: auto;
background: var(--gradient-brand);
color: var(--white);
}
[data-theme="light"] .message.user .message-bubble {
color: var(--black);
}
.message.bot .message-bubble {
background: linear-gradient(135deg, var(--black-card) 0%, rgba(93, 92, 222, 0.05) 100%);
color: var(--gray-light);
border: 1px solid var(--border);
padding-right: 40px;
}
/* System Message Styling */
.message.system .message-bubble {
background: rgba(93, 92, 222, 0.1);
border: 1px solid var(--brand);
color: var(--brand);
font-size: 12px;
padding: 8px 12px;
font-style: italic;
text-align: center;
max-width: 100%;
}
.message.system .message-avatar {
background: var(--gradient-brand);
font-size: 12px;
}
.chat-input-area {
padding: 16px;
border-top: 1px solid var(--border);
background: var(--black-card);
flex-shrink: 0;
}
.chat-input-container {
display: flex;
gap: 8px;
align-items: flex-end;
}
.chat-input {
flex: 1;
background: var(--black);
border: 1px solid var(--border);
border-radius: 12px;
padding: 12px;
font-size: 16px;
color: var(--white);
resize: none;
min-height: 40px;
max-height: 100px;
transition: border-color 0.2s ease;
font-family: inherit;
}
.chat-input:focus {
outline: none;
border-color: var(--brand);
}
.chat-input::placeholder {
color: var(--gray-dark);
}
.send-button {
background: var(--gradient-brand);
border: none;
padding: 12px 16px;
border-radius: 12px;
color: var(--white);
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 6px;
min-height: 40px;
}
[data-theme="light"] .send-button {
color: var(--black);
}
.send-button:hover:not(:disabled) {
transform: translateY(-1px);
}
.send-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 16px;
}
.logo {
font-size: 20px;
font-weight: 700;
background: var(--gradient-brand);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
flex-shrink: 0;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.theme-toggle {
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 50px;
padding: 8px 12px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
flex-shrink: 0;
}
.theme-toggle:hover {
border-color: var(--brand);
}
/* Explainer Section */
.explainer {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
text-align: center;
}
.explainer h1 {
font-size: 20px;
font-weight: 600;
color: var(--brand);
margin-bottom: 8px;
}
.explainer p {
font-size: 14px;
color: var(--gray-light);
line-height: 1.5;
}
/* Credits Modal */
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 9000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
padding: 16px;
}
.modal.active {
opacity: 1;
visibility: visible;
}
.modal-content {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 20px;
padding: 24px;
max-width: 500px;
width: 100%;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-title {
font-size: 20px;
font-weight: 700;
margin: 0;
}
.modal-close {
background: none;
border: none;
color: var(--gray);
font-size: 20px;
cursor: pointer;
padding: 4px;
}
.modal-close:hover {
color: var(--white);
}
.pricing-option {
background: var(--black-card);
border: 2px solid var(--border);
border-radius: 16px;
padding: 20px;
margin-bottom: 12px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
}
.pricing-option:hover {
border-color: var(--brand);
transform: translateY(-2px);
}
.pricing-option.recommended {
border-color: var(--success);
position: relative;
}
.pricing-option.recommended::before {
content: "RECOMMENDED";
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
background: var(--success);
color: var(--white);
padding: 4px 8px;
border-radius: 12px;
font-size: 9px;
font-weight: 700;
}
.pricing-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 6px;
}
.pricing-price {
font-size: 24px;
font-weight: 700;
color: var(--success);
margin-bottom: 8px;
}
.pricing-features {
list-style: none;
text-align: left;
color: var(--gray-light);
margin-bottom: 12px;
}
.pricing-features li {
padding: 3px 0;
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
}
.pricing-features li::before {
content: "✓";
color: var(--success);
font-weight: bold;
}
.action-button {
background: var(--gradient-brand);
border: none;
padding: 12px 12px;
border-radius: 16px;
color: var(--white);
font-weight: 600;
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
width: 70%;
}
[data-theme="light"] .action-button {
color: var(--black);
}
.action-button:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-glow);
}
/* Typing indicator */
.typing-indicator {
display: inline-flex;
align-items: center;
gap: 2px;
}
.typing-dot {
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--brand);
animation: typing 1.4s infinite;
}
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-10px); opacity: 1; }
}
/* Error States */
.error-message {
background: rgba(239, 68, 68, 0.1);
border: 1px solid var(--accent);
border-radius: 8px;
padding: 12px;
margin: 8px 0;
color: var(--accent);
font-size: 14px;
display: none;
}
.error-message.show {
display: block;
animation: errorSlide 0.3s ease-out;
}
@keyframes errorSlide {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Mobile Optimizations */
@media (max-width: 768px) {
.app {
padding: 12px;
}
.header {
flex-direction: column;
align-items: stretch;
text-align: center;
margin-bottom: 20px;
}
.header-actions {
justify-content: center;
gap: 8px;
}
.chat-controls {
gap: 4px;
}
.chat-control-btn {
padding: 4px 6px;
font-size: 11px;
}
.control-group {
gap: 2px;
}
.model-grid {
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 6px;
}
.persona-grid {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 8px;
}
.chat-container {
height: 400px;
}
.explainer h3 {
font-size: 16px;
}
.explainer p {
font-size: 13px;
}
.app.fullscreen .chat-container {
height: 100vh;
}
.floating-controls {
flex-direction: column;
}
.floating-panel {
width: calc(100vw - 32px);
right: 16px;
left: 16px;
}
.menu-dropdown-content {
right: auto;
left: 0;
}
}
</style>
</head>
<body data-theme="light">
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<div class="app" id="app">
<!-- Fullscreen Toggle (Visible in fullscreen mode) -->
<button class="fullscreen-toggle" id="fullscreenToggle">
<span id="fullscreenIcon">⛶</span>
<span id="fullscreenText">Fullscreen</span>
</button>
<!-- Floating Controls for Fullscreen -->
<div class="floating-controls">
<button class="fullscreen-toggle" id="exitFullscreenBtn">⛶ Exit</button>
<button class="fullscreen-toggle" id="quickSwitchBtn">⚡ Switch</button>
<button class="fullscreen-toggle" id="showShortcutsBtn">⌨️ Keys</button>
</div>
<!-- Quick Switch Panel -->
<div id="quickSwitchPanel" class="floating-panel">
<h3>Quick Switch</h3>
<select id="quickModelSelect"></select>
<select id="quickPersonaSelect"></select>
<button class="send-button" style="width: 100%; margin-top: 8px;" onclick="applyQuickSwitch()">Apply</button>
</div>
<!-- Keyboard Shortcuts Panel -->
<div id="shortcutsPanel" class="shortcuts-panel">
<h3>Keyboard Shortcuts</h3>
<div class="shortcut-item">
<span>New Chat</span>
<span class="shortcut-key">Ctrl+N</span>
</div>
<div class="shortcut-item">
<span>Fullscreen</span>
<span class="shortcut-key">F11</span>
</div>
<div class="shortcut-item">
<span>Theme Toggle</span>
<span class="shortcut-key">Ctrl+/</span>
</div>
<div class="shortcut-item">
<span>Send Message</span>
<span class="shortcut-key">Enter</span>
</div>
<div class="shortcut-item">
<span>New Line</span>
<span class="shortcut-key">Shift+Enter</span>
</div>
<div class="shortcut-item">
<span>Quick Switch</span>
<span class="shortcut-key">Ctrl+K</span>
</div>
</div>
<!-- Header -->
<header class="header">
<div class="logo">BrutallyHonest.ai</div>
<div class="header-actions">
<!-- Credits Display -->
<div class="credits-container" id="creditsDisplay">
<div class="credits-progress">
<div class="credits-text">
<span class="credits-number" id="creditsAmount">10</span> /
<span id="creditsMax">1000</span> credits
</div>
<div class="credits-bar">
<div
class="credits-fill"
id="creditsFill"
style="width: 1%"
></div>
</div>
</div>
<div
style="font-size: 12px; color: var(--brand); font-weight: 600;"
>
Upgrade
</div>
</div>
<!-- Theme Toggle -->
<div class="theme-toggle" id="themeToggle">
<span id="themeIcon">☀️</span>
<span id="themeText">Light</span>
</div>
</div>
</header>
<!-- Explainer -->
<section class="explainer">
<h3>🔥 The AI That Has Transformed Thousands</h3>
<p>
Choose your AI personality below, pick a model that powers the
conversation, and start chatting.
<strong>Free models are unlimited.</strong> Premium models cost
credits but deliver breakthrough insights.
</p>
</section>
<!-- Error Display -->
<div class="error-message" id="errorMessage">
<!-- Error content will be populated by JavaScript -->
</div>
<!-- Main Chat Interface -->
<div class="chat-container">
<div class="chat-header">
<div class="chat-title">
<span id="currentPersonaEmoji">🔥</span>
<span id="currentPersonaName">Loading...</span>
<span id="currentModelInfo" style="font-size: 12px; color: var(--gray);"></span>
</div>
<div class="chat-controls">
<!-- Primary Action -->
<button class="chat-control-btn new-chat" id="newChatBtn">✨ New Chat</button>
<!-- Control Group -->
<div class="control-group">
<button class="chat-control-btn" id="chatFullscreenBtn" title="Fullscreen">⛶</button>
<button class="chat-control-btn" id="layoutToggle" title="Toggle Layout">📐</button>
</div>
<!-- Menu Dropdown -->
<div class="menu-dropdown" id="menuDropdown">
<button class="chat-control-btn menu-toggle" id="menuToggle">⋯</button>
<div class="menu-dropdown-content" id="menuDropdownContent">
<button class="menu-item" id="saveBtn">💾 Save Chat</button>
<button class="menu-item" id="clearBtn">🗑️ Clear Chat</button>
<button class="menu-item" id="exportBtn">📤 Export Chat</button>
</div>
</div>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<div class="message bot">
<div class="message-header">
<div class="message-avatar">🔥</div>
<span>Loading...</span>
<span>•</span>
<span>Just now</span>
</div>
<div class="message-bubble">
Welcome! Configuring your experience...
<button
class="copy-button"
onclick="copyMessage(this)"
data-text="Welcome! Configuring your experience..."
>
Copy
</button>
</div>
</div>
</div>
<div class="chat-input-area">
<div class="chat-input-container">
<textarea
class="chat-input"
id="chatInput"
placeholder="Type your message here..."
rows="1"
></textarea>
<button class="send-button" id="sendButton">
<span id="sendButtonText">Send</span>
<span id="sendButtonIcon">→</span>
</button>
</div>
</div>
</div>
<!-- Model Selection -->
<div
style="margin: 20px 0;"
class="model-selection-container"
id="modelSelectionContainer"
>
<h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);">
Select AI Model
</h4>
<div class="loading-placeholder" id="modelsLoadingPlaceholder">
<div class="loading-animation"></div>
Loading models...
</div>
<div id="modelsContainer"></div>
</div>
<!-- Persona Selection -->
<div
style="margin: 20px 0;"
class="persona-selection-container"
id="personaSelectionContainer"
>
<h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);">
Choose Your AI Personality
</h4>
<div class="loading-placeholder" id="personasLoadingPlaceholder">
<div class="loading-animation"></div>
Loading personalities...
</div>
<div class="persona-grid" id="personaGrid"></div>
</div>
</div>
<!-- Credits Modal -->
<div class="modal" id="creditsModal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">💎 Get More Credits</h2>
<button class="modal-close" id="closeCreditsModal">×</button>
</div>
<div class="pricing-option recommended">
<div class="pricing-title">Monthly Subscription</div>
<div class="pricing-price">
$9<span style="font-size: 12px; color: var(--gray);">/month</span>
</div>
<ul class="pricing-features">
<li>500 credits per month</li>
<li>Unlimited free models</li>
<li>Access to all premium models</li>
<li>Credits roll over (up to 1000)</li>
<li>Priority support</li>
</ul>
<button class="action-button" onclick="purchaseMonthly()">
Subscribe Now ✨
</button>
</div>
<div class="pricing-option">
<div class="pricing-title">Pay As You Go</div>
<div class="pricing-price">From $2.99</div>
<ul class="pricing-features">
<li>100 credits for $2.99</li>
<li>250 credits for $5.99</li>
<li>500 credits for $9.99</li>
<li>Credits never expire</li>
</ul>
<button class="action-button" onclick="purchaseCredits()">
Buy Credits 💰
</button>
</div>
</div>
</div>
<script>
// ===================================================================================
// CONFIGURATION - UPDATE THESE URLs
// ===================================================================================
const CONFIG = {
mainWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/bha-mvp',
configWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/config'
};
// ===================================================================================
// ENHANCED STATE MANAGEMENT SYSTEM
// ===================================================================================
class BHAStateManager {
constructor() {
this.state = {
user: {
credits: 1000,
maxCredits: 1000,
userId: this.generateUserId()
},
chat: {
currentModel: null,
currentPersona: null,
currentModelCost: 0,
currentModelName: '',
conversationId: null,
messages: [],
isTyping: false
},
ui: {
currentTheme: 'light',
isFullscreen: false
},
config: {
personas: [],
models: [],
modelsByTier: { free: [], standard: [], premium: [] },
isLoaded: false
}
};
this.listeners = new Map();
this.loadPersistedState();
}
generateUserId() {
const stored = localStorage.getItem('bha_user_id');
if (stored) return stored;
const id = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('bha_user_id', id);
return id;
}
loadPersistedState() {
this.state.ui.currentTheme = localStorage.getItem('bha_selectedTheme') || 'light';
this.state.chat.currentPersona = localStorage.getItem('bha_selectedPersona');
this.state.chat.currentModel = localStorage.getItem('bha_selectedModel');
this.state.chat.conversationId = localStorage.getItem('bha_conversation_id') || this.generateConversationId();
}
generateConversationId() {
return 'conv_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5);
}
subscribe(key, callback) {
if (!this.listeners.has(key)) {
this.listeners.set(key, []);
}
this.listeners.get(key).push(callback);
}
setState(path, value) {
if (['currentModel', 'currentPersona', 'conversationId'].includes(path)) {
value = String(value || '');
}
const keys = path.split('.');
let current = this.state;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
}
const finalKey = keys[keys.length - 1];
if (current[finalKey] === value) return;
console.log(`🔄 State Change: ${path} from "${current[finalKey]}" to "${value}"`);
current[finalKey] = value;
if (path.startsWith('chat.')) {
localStorage.setItem(`bha_${finalKey}`, value);
}
if (this.listeners.has(path)) {
this.listeners.get(path).forEach(callback => callback(value));
}
}
getState(path) {
const keys = path.split('.');
let current = this.state;
for (const key of keys) {
if (current === undefined || current === null) return undefined;
current = current[key];
}
return current;
}
resetConversationContext(reason = 'manual') {
const newConvId = this.generateConversationId();
this.setState('chat.conversationId', newConvId);
this.setState('chat.messages', []);
console.log(`🔄 Context Reset (${reason}): New conversation ${newConvId}`);
return newConvId;
}
getAllState() {
return this.state;
}
}
const StateManager = new BHAStateManager();
// ===================================================================================
// ENHANCED CONFIGURATION MANAGER
// ===================================================================================
class BHAConfigManager {
constructor() {
this.config = {
personas: [],
models: [],
modelsByTier: { free: [], standard: [], premium: [] },
features: {}
};
this.isLoaded = false;
}
async loadConfiguration() {
try {
console.log('🔄 Loading configuration from:', CONFIG.configWebhookUrl);
const response = await fetch(CONFIG.configWebhookUrl);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.config = await response.json();
this.isLoaded = true;
StateManager.setState('config', this.config);
StateManager.setState('config.isLoaded', true);
console.log('✅ Configuration loaded:', this.config);
this.renderDynamicContent();
this.initializeDefaults();
} catch (error) {
console.error('❌ Config load failed:', error);
this.loadFallbackConfig();
}
}
loadFallbackConfig() {
console.log('🔄 Loading fallback configuration...');
this.config = {
personas: [
{
id: 'QuickResonanceSpark_Persona_Haiku',
name: 'Brutally Honest',
emoji: '🔥',
description: 'No BS, just raw truth',
isPremium: false,
gradientColor: 'linear-gradient(135deg, #EF4444, #F59E0B)'
},
{
id: 'ResonanceSession_Orchestrator_Opus',
name: 'Deep Guide',
emoji: '🧠',
description: 'Profound insights & breakthrough clarity',
isPremium: false,
gradientColor: 'linear-gradient(135deg, #10B981, #06D6A0)'
}
],
models: [
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' },
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' }
],
modelsByTier: {
free: [
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' },
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' }
],
standard: [],
premium: []
}
};
this.isLoaded = true;
StateManager.setState('config', this.config);
StateManager.setState('config.isLoaded', true);
this.renderDynamicContent();
this.initializeDefaults();
}
renderDynamicContent() {
this.renderPersonas();
this.renderModels();
this.populateQuickSwitcher();
}
renderPersonas() {
const container = document.getElementById('personaGrid');
const loadingPlaceholder = document.getElementById('personasLoadingPlaceholder');
if (loadingPlaceholder) loadingPlaceholder.style.display = 'none';
container.innerHTML = this.config.personas.map(persona => `
<div class="persona-card ${persona.isPremium ? 'premium' : ''}"
data-persona-id="${persona.id}"
data-persona-name="${persona.name}"
data-persona-emoji="${persona.emoji}">
<div class="persona-avatar" style="background: ${persona.gradientColor};">
${persona.emoji}
${persona.isPremium ? '<div class="premium-badge">💎</div>' : ''}
</div>
<div class="persona-name">${persona.name}</div>
<div class="persona-desc">${persona.description}</div>
</div>
`).join('');
document.querySelectorAll('.persona-card').forEach(card => {
card.addEventListener('click', () => this.selectPersona(card));
});
}
renderModels() {
const container = document.getElementById('modelsContainer');
const loadingPlaceholder = document.getElementById('modelsLoadingPlaceholder');
if (loadingPlaceholder) loadingPlaceholder.style.display = 'none';
const tiers = ['free', 'standard', 'premium'];
const tierLabels = {
'free': 'Free Models',
'standard': 'Standard Models (5-15 credits)',
'premium': 'Premium Models (15+ credits)'
};
container.innerHTML = tiers.map(tier => {
const models = this.config.modelsByTier[tier] || [];
if (models.length === 0) return '';
return `
<div class="model-section" data-tier="${tier}">
<div class="model-section-title">${tierLabels[tier]}</div>
<div class="model-grid">
${models.map(model => `
<div class="model-option"
data-model-id="${model.id}"
data-model-name="${model.name}"
data-model-cost="${model.cost}">
<div class="model-name">${model.name}</div>
<div class="model-cost ${model.cost === 0 ? 'free' : 'paid'}">
${model.cost === 0 ? 'FREE' : `${model.cost} credits`}
</div>
</div>
`).join('')}
</div>
</div>
`;
}).join('');
document.querySelectorAll('.model-option').forEach(option => {
option.addEventListener('click', () => this.selectModel(option));
});
}
populateQuickSwitcher() {
if (!this.isLoaded) return;
const modelSelect = document.getElementById('quickModelSelect');
const personaSelect = document.getElementById('quickPersonaSelect');
modelSelect.innerHTML = this.config.models.map(m =>
`<option value="${m.id}">${m.name}</option>`
).join('');
personaSelect.innerHTML = this.config.personas.map(p =>
`<option value="${p.id}">${p.emoji} ${p.name}</option>`
).join('');
modelSelect.value = StateManager.getState('chat.currentModel') || '';
personaSelect.value = StateManager.getState('chat.currentPersona') || '';
}
selectPersona(personaElement) {
document.querySelectorAll('.persona-card').forEach(el => el.classList.remove('active'));
personaElement.classList.add('active');
const personaId = String(personaElement.dataset.personaId || '');
const personaName = String(personaElement.dataset.personaName || '');
const personaEmoji = String(personaElement.dataset.personaEmoji || '');
StateManager.setState('chat.currentPersona', personaId);
document.getElementById('currentPersonaEmoji').textContent = personaEmoji;
document.getElementById('currentPersonaName').textContent = personaName;
this.addSystemMessage(`🎭 Now chatting with ${personaName}`);
this.populateQuickSwitcher();
console.log('✅ Persona Selected:', personaId, typeof personaId);
}
selectModel(modelElement) {
document.querySelectorAll('.model-option').forEach(el => el.classList.remove('active'));
modelElement.classList.add('active');
const modelId = String(modelElement.dataset.modelId || '');
const modelName = String(modelElement.dataset.modelName || '');
const modelCost = parseInt(modelElement.dataset.modelCost || '0');
StateManager.setState('chat.currentModel', modelId);
StateManager.setState('chat.currentModelCost', modelCost);
StateManager.setState('chat.currentModelName', modelName);
const modelInfo = document.getElementById('currentModelInfo');
if (modelInfo) {
modelInfo.textContent = `(${modelName})`;
}
this.addSystemMessage(`🤖 Now using ${modelName}`);
this.populateQuickSwitcher();
console.log('✅ Model Selected:', modelId, typeof modelId);
}
addSystemMessage(content) {
const messagesContainer = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = 'message system';
messageDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">⚙️</div>
<span>System</span>
<span>•</span>
<span>${new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
</div>
<div class="message-bubble system-message">
${content}
</div>
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
initializeDefaults() {
const savedPersonaId = localStorage.getItem('bha_selectedPersona');
const savedModelId = localStorage.getItem('bha_selectedModel');
const savedLayout = localStorage.getItem('bha_flexboxMode') === 'true';
if (savedLayout) {
document.querySelector('.chat-container').classList.add('flexbox');
}
if (this.config.personas.length > 0) {
let targetPersona = savedPersonaId ?
document.querySelector(`[data-persona-id="${savedPersonaId}"]`) :
document.querySelector('.persona-card');
if (targetPersona) {
this.selectPersona(targetPersona);
}
}
if (this.config.models.length > 0) {
let targetModel = savedModelId ?
document.querySelector(`[data-model-id="${savedModelId}"]`) :
document.querySelector('.model-option');
if (targetModel) {
this.selectModel(targetModel);
}
}
setTimeout(() => {
this.updateWelcomeMessage();
}, 100);
setTimeout(() => {
if (!StateManager.getState('chat.currentPersona')) {
const firstPersona = document.querySelector('.persona-card');
if (firstPersona) {
console.log('🆘 Emergency persona selection');
this.selectPersona(firstPersona);
}
}
if (!StateManager.getState('chat.currentModel')) {
const firstModel = document.querySelector('.model-option');
if (firstModel) {
console.log('🆘 Emergency model selection');
this.selectModel(firstModel);
}
}
}, 500);
}
updateWelcomeMessage() {
const messagesContainer = document.getElementById('chatMessages');
const currentPersonaId = StateManager.getState('chat.currentPersona');
const currentPersona = this.config.personas.find(p => p.id === currentPersonaId);
if (currentPersona) {
messagesContainer.innerHTML = `
<div class="message bot">
<div class="message-header">
<div class="message-avatar">${currentPersona.emoji}</div>
<span>${currentPersona.name}</span>
<span>•</span>
<span>Just now</span>
</div>
<div class="message-bubble">
I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today?
<button class="copy-button" onclick="copyMessage(this)" data-text="I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today?">Copy</button>
</div>
</div>
`;
}
}
}
// ===================================================================================
// UTILITY FUNCTIONS
// ===================================================================================
function showToast(message, type = 'success', duration = 3000) {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(100%)';
setTimeout(() => {
if (toast.parentNode) {
container.removeChild(toast);
}
}, 300);
}, duration);
}
function showError(message) {
const errorEl = document.getElementById('errorMessage');
errorEl.textContent = message;
errorEl.classList.add('show');
setTimeout(() => {
errorEl.classList.remove('show');
}, 5000);
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// ===================================================================================
// UI FUNCTIONS
// ===================================================================================
function applyQuickSwitch() {
const modelId = document.getElementById('quickModelSelect').value;
const personaId = document.getElementById('quickPersonaSelect').value;
const modelElement = document.querySelector(`[data-model-id="${modelId}"]`);
const personaElement = document.querySelector(`[data-persona-id="${personaId}"]`);
if (modelElement) configManager.selectModel(modelElement);
if (personaElement) configManager.selectPersona(personaElement);
document.getElementById('quickSwitchPanel').classList.remove('active');
showToast('Settings applied!', 'success', 2000);
}
function toggleFullscreen() {
const app = document.getElementById('app');
const fullscreenIcon = document.getElementById('fullscreenIcon');
const fullscreenText = document.getElementById('fullscreenText');
const isFullscreen = !StateManager.getState('ui.isFullscreen');
StateManager.setState('ui.isFullscreen', isFullscreen);
app.classList.toggle('fullscreen', isFullscreen);
if (isFullscreen) {
fullscreenIcon.textContent = '⛶';
fullscreenText.textContent = 'Exit';
} else {
fullscreenIcon.textContent = '⛶';
fullscreenText.textContent = 'Fullscreen';
}
localStorage.setItem('bha_fullscreenMode', isFullscreen.toString());
}
function toggleLayout() {
const chatContainer = document.querySelector('.chat-container');
const isFlexbox = chatContainer.classList.toggle('flexbox');
localStorage.setItem('bha_flexboxMode', isFlexbox.toString());
showToast(`Layout: ${isFlexbox ? 'Flexible' : 'Fixed'}`, 'success', 2000);
}
function toggleTheme() {
const currentTheme = StateManager.getState('ui.currentTheme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
StateManager.setState('ui.currentTheme', newTheme);
document.body.dataset.theme = newTheme;
const icon = document.getElementById('themeIcon');
const text = document.getElementById('themeText');
if (newTheme === 'light') {
icon.textContent = '☀️';
text.textContent = 'Light';
} else {
icon.textContent = '🌙';
text.textContent = 'Dark';
}
localStorage.setItem('bha_selectedTheme', newTheme);
}
function copyMessage(button) {
const text = button.dataset.text;
navigator.clipboard.writeText(text).then(() => {
const originalText = button.textContent;
button.textContent = 'Copied!';
button.classList.add('copied');
setTimeout(() => {
button.textContent = originalText;
button.classList.remove('copied');
}, 2000);
showToast('Message copied to clipboard', 'success', 2000);
}).catch(err => {
console.error('Copy failed:', err);
showToast('Failed to copy message', 'error', 3000);
});
}
// ===================================================================================
// CREDITS SYSTEM
// ===================================================================================
function updateCreditsDisplay() {
const credits = StateManager.getState('user.credits');
const maxCredits = StateManager.getState('user.maxCredits');
document.getElementById('creditsAmount').textContent = credits;
document.getElementById('creditsMax').textContent = maxCredits;
const percentage = (credits / maxCredits) * 100;
document.getElementById('creditsFill').style.width = percentage + '%';
}
function showCreditsModal() {
document.getElementById('creditsModal').classList.add('active');
}
function hideCreditsModal() {
document.getElementById('creditsModal').classList.remove('active');
}
function purchaseMonthly() {
const userId = StateManager.getState('user.userId');
window.open(`https://buy.stripe.com/YOUR_MONTHLY_LINK?client_reference_id=${userId}`, '_blank');
}
function purchaseCredits() {
const userId = StateManager.getState('user.userId');
window.open(`https://buy.stripe.com/YOUR_CREDIT_PACK_LINK?client_reference_id=${userId}`, '_blank');
}
// ===================================================================================
// CHAT FUNCTIONS
// ===================================================================================
function addMessage(content, isUser = false, persona = null) {
const messagesContainer = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user' : 'bot'}`;
const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
const currentPersonaId = StateManager.getState('chat.currentPersona');
const personas = StateManager.getState('config.personas') || [];
const currentPersona = personas.find(p => p.id === currentPersonaId);
const sender = isUser ? 'You' : (persona || currentPersona?.name || 'AI');
const avatar = isUser ? '👤' : (currentPersona?.emoji || '🤖');
const processedContent = isUser ?
escapeHtml(content) :
(typeof marked !== 'undefined' ? marked.parse(content) : content);
const copyButton = isUser ? '' :
`<button class="copy-button" onclick="copyMessage(this)" data-text="${escapeHtml(content)}">Copy</button>`;
messageDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">${avatar}</div>
<span>${sender}</span>
<span>•</span>
<span>${timestamp}</span>
</div>
<div class="message-bubble">
${processedContent}
${copyButton}
</div>
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
const messages = StateManager.getState('chat.messages') || [];
messages.push({
content: content,
isUser: isUser,
timestamp: Date.now(),
persona: persona
});
StateManager.setState('chat.messages', messages);
}
function addTypingIndicator() {
const messagesContainer = document.getElementById('chatMessages');
const typingDiv = document.createElement('div');
typingDiv.id = 'typingIndicator';
typingDiv.className = 'message bot';
const currentPersonaId = StateManager.getState('chat.currentPersona');
const personas = StateManager.getState('config.personas') || [];
const currentPersona = personas.find(p => p.id === currentPersonaId);
typingDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">${currentPersona?.emoji || '🤖'}</div>
<span>${currentPersona?.name || 'AI'}</span>
<span>•</span>
<span>typing...</span>
</div>
<div class="message-bubble">
<div class="typing-indicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
`;
messagesContainer.appendChild(typingDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function removeTypingIndicator() {
const indicator = document.getElementById('typingIndicator');
if (indicator) {
indicator.remove();
}
}
// ===================================================================================
// BULLETPROOF SEND MESSAGE FUNCTION
// ===================================================================================
async function sendMessage() {
const input = document.getElementById('chatInput');
const message = input.value.trim();
if (!message || StateManager.getState('chat.isTyping')) return;
const currentPersona = StateManager.getState('chat.currentPersona');
const currentModel = StateManager.getState('chat.currentModel');
let conversationId = StateManager.getState('chat.conversationId');
if (!conversationId) {
conversationId = StateManager.generateConversationId();
StateManager.setState('chat.conversationId', conversationId);
}
if (!currentPersona || !currentModel) {
showError('Please select a persona and model before sending a message.');
return;
}
const modelCost = StateManager.getState('chat.currentModelCost') || 0;
const userCredits = StateManager.getState('user.credits');
if (modelCost > 0 && userCredits < modelCost) {
showError(`Insufficient credits! You need ${modelCost} credits.`);
showCreditsModal();
return;
}
addMessage(message, true);
input.value = '';
input.style.height = 'auto';
addTypingIndicator();
StateManager.setState('chat.isTyping', true);
const sendBtn = document.getElementById('sendButton');
sendBtn.disabled = true;
document.getElementById('sendButtonText').textContent = 'Sending';
document.getElementById('sendButtonIcon').innerHTML = '<div class="loading-animation"></div>';
try {
const payload = {
user_id: String(StateManager.getState('user.userId')),
bot_to_call: String(currentPersona),
full_prompt_for_llm: String(message),
raw_user_situation: String(message),
selected_model_for_openrouter: String(currentModel),
conversation_id: String(conversationId),
timestamp: new Date().toISOString(),
frontend_state_snapshot: JSON.stringify({
persona: currentPersona,
model: currentModel,
conversation_id: conversationId,
message_count: StateManager.getState('chat.messages').length || 0
})
};
console.log('🚀 Sending Payload:', payload);
const response = await fetch(CONFIG.mainWebhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log('✅ Response received:', data);
let aiResponseText = "I'm having trouble formulating a response right now.";
if (data && typeof data.response === 'string') {
aiResponseText = data.response;
} else if (data.bot_response && data.bot_response.analysis) {
aiResponseText = data.bot_response.analysis;
} else if (data.final_response_payload && data.final_response_payload.bot_response) {
aiResponseText = data.final_response_payload.bot_response.analysis;
}
removeTypingIndicator();
addMessage(aiResponseText, false);
if (data.credits_remaining !== undefined) {
StateManager.setState('user.credits', data.credits_remaining);
} else if (modelCost > 0) {
StateManager.setState('user.credits', Math.max(0, userCredits - modelCost));
}
updateCreditsDisplay();
if (data.tool_used) {
showToast('Enhanced response generated', 'success', 2000);
}
} catch (error) {
console.error('❌ Error sending message:', error);
removeTypingIndicator();
showError('Failed to send message. Please try again.');
} finally {
StateManager.setState('chat.isTyping', false);
sendBtn.disabled = false;
document.getElementById('sendButtonText').textContent = 'Send';
document.getElementById('sendButtonIcon').textContent = '→';
}
}
// ===================================================================================
// CHAT CONTROLS
// ===================================================================================
function startNewChat() {
if (confirm('Start a new conversation? Current chat will be cleared.')) {
StateManager.resetConversationContext('new_chat_button');
configManager.updateWelcomeMessage();
showToast('✨ New conversation started', 'success');
}
}
function saveConversation() {
const messages = StateManager.getState('chat.messages') || [];
if (messages.length === 0) {
showToast('No messages to save yet!', 'error');
return;
}
showToast('💾 Conversation saved! (Feature coming soon)', 'success');
}
function clearChat() {
if (confirm('Clear all messages? This cannot be undone!')) {
StateManager.setState('chat.messages', []);
configManager.updateWelcomeMessage();
showToast('Chat cleared', 'success');
}
}
function exportChat() {
const messages = StateManager.getState('chat.messages') || [];
if (messages.length === 0) {
showToast('No messages to export yet!', 'error');
return;
}
const currentPersonaId = StateManager.getState('chat.currentPersona');
const currentModel = StateManager.getState('chat.currentModel');
let content = `BrutallyHonest.ai Chat Export\n`;
content += `Exported: ${new Date().toLocaleString()}\n`;
content += `Model: ${currentModel}\n`;
content += `Persona: ${currentPersonaId}\n`;
content += '='.repeat(50) + '\n\n';
messages.forEach(msg => {
const timestamp = new Date(msg.timestamp).toLocaleString();
const speaker = msg.isUser ? 'You' : (msg.persona || 'AI');
content += `[${timestamp}] ${speaker}: ${msg.content}\n\n`;
});
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `BHA_Chat_${new Date().toISOString().split('T')[0]}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('Chat exported successfully', 'success');
}
// ===================================================================================
// INITIALIZATION
// ===================================================================================
const configManager = new BHAConfigManager();
async function init() {
console.log("🔥 BrutallyHonest.ai Initializing...");
const savedTheme = localStorage.getItem('bha_selectedTheme') || 'light';
const savedFullscreen = localStorage.getItem('bha_fullscreenMode') === 'true';
StateManager.setState('ui.currentTheme', savedTheme);
document.body.dataset.theme = savedTheme;
const icon = document.getElementById('themeIcon');
const text = document.getElementById('themeText');
if (savedTheme === 'dark') {
icon.textContent = '🌙';
text.textContent = 'Dark';
}
if (savedFullscreen) {
StateManager.setState('ui.isFullscreen', true);
document.getElementById('app').classList.add('fullscreen');
document.getElementById('fullscreenIcon').textContent = '⛶';
document.getElementById('fullscreenText').textContent = 'Exit';
}
// Event listeners
document.getElementById('themeToggle').addEventListener('click', toggleTheme);
document.getElementById('creditsDisplay').addEventListener('click', showCreditsModal);
document.getElementById('closeCreditsModal').addEventListener('click', hideCreditsModal);
document.getElementById('layoutToggle').addEventListener('click', toggleLayout);
document.getElementById('fullscreenToggle').addEventListener('click', toggleFullscreen);
document.getElementById('chatFullscreenBtn').addEventListener('click', toggleFullscreen);
document.getElementById('exitFullscreenBtn').addEventListener('click', toggleFullscreen);
document.getElementById('quickSwitchBtn').addEventListener('click', () => {
document.getElementById('quickSwitchPanel').classList.toggle('active');
});
document.getElementById('showShortcutsBtn').addEventListener('click', () => {
const panel = document.getElementById('shortcutsPanel');
panel.classList.toggle('show');
setTimeout(() => panel.classList.remove('show'), 5000);
});
document.getElementById('newChatBtn').addEventListener('click', startNewChat);
document.getElementById('saveBtn').addEventListener('click', saveConversation);
document.getElementById('clearBtn').addEventListener('click', clearChat);
document.getElementById('exportBtn').addEventListener('click', exportChat);
// Menu dropdown functionality
document.getElementById('menuToggle').addEventListener('click', (e) => {
e.stopPropagation();
document.getElementById('menuDropdown').classList.toggle('active');
});
document.getElementById('sendButton').addEventListener('click', sendMessage);
document.getElementById('chatInput').addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
document.getElementById('chatInput').addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 100) + 'px';
});
window.addEventListener('click', (e) => {
const modal = document.getElementById('creditsModal');
if (e.target === modal) {
hideCreditsModal();
}
if (!e.target.closest('#quickSwitchPanel') && !e.target.closest('#quickSwitchBtn')) {
document.getElementById('quickSwitchPanel').classList.remove('active');
}
if (!e.target.closest('#menuDropdown')) {
document.getElementById('menuDropdown').classList.remove('active');
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
if (StateManager.getState('ui.isFullscreen')) {
toggleFullscreen();
}
document.getElementById('quickSwitchPanel').classList.remove('active');
document.getElementById('shortcutsPanel').classList.remove('show');
document.getElementById('menuDropdown').classList.remove('active');
}
if (e.key === 'F11') {
e.preventDefault();
toggleFullscreen();
}
if ((e.ctrlKey || e.metaKey) && e.key === '/') {
e.preventDefault();
toggleTheme();
}
if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
e.preventDefault();
startNewChat();
}
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('quickSwitchPanel').classList.toggle('active');
}
});
updateCreditsDisplay();
await configManager.loadConfiguration();
console.log('✅ BrutallyHonest.ai initialization complete!');
}
window.purchaseMonthly = purchaseMonthly;
window.purchaseCredits = purchaseCredits;
window.copyMessage = copyMessage;
window.applyQuickSwitch = applyQuickSwitch;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
</script>
</body>
</html>
the-good-good
<!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"
/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<title>
BrutallyHonest.ai - Transform Your Bullsh*t Into Breakthroughs
</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
:root {
/* Light Theme (Default) */
--black: #FFFFFF;
--black-soft: #F8F9FA;
--black-card: #F1F3F4;
--border: #E8EAED;
--border-light: #DADCE0;
--white: #000000;
--gray: #5F6368;
--gray-light: #3C4043;
--gray-dark: #80868B;
--brand: #5D5CDE;
--brand-light: #7C7CE8;
--brand-dark: #4B4BC7;
--accent: #EF4444;
--success: #22C55E;
--warning: #F59E0B;
--gold: #FCD34D;
--gradient-brand: linear-gradient(135deg, #5D5CDE 0%, #7C7CE8 100%);
--gradient-fire: linear-gradient(135deg, #EF4444 0%, #F59E0B 100%);
--gradient-success: linear-gradient(135deg, #22C55E 0%, #4ADE80 100%);
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.08);
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.12), 0 1px 2px -1px rgb(0 0 0 / 0.12);
--shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06);
--shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05);
--shadow-glow: 0 0 0 1px rgba(93, 92, 222, 0.3), 0 0 20px rgba(93, 92, 222, 0.2);
}
.app {
padding: 0 16px 16px 16px;
}
/* Dark Theme Override */
[data-theme="dark"] {
--black: #0F0F0F;
--black-soft: #1A1A1A;
--black-card: #262626;
--border: #404040;
--border-light: #525252;
--white: #FFFFFF;
--gray: #A3A3A3;
--gray-light: #D4D4D4;
--gray-dark: #737373;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
html {
position: static;
height: auto;
overflow: auto;
}
body {
background: var(--black);
color: var(--white);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background-color 0.5s ease, color 0.5s ease;
min-height: 100vh;
overflow-x: hidden;
position: relative;
}
.app {
max-width: 1200px;
margin: 0 auto;
padding: 16px;
min-height: 100vh;
position: relative;
width: 100%;
transition: all 0.3s ease;
}
/* Toast Container */
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
pointer-events: none;
}
.toast {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 8px;
box-shadow: var(--shadow-lg);
backdrop-filter: blur(8px);
animation: toastSlideIn 0.3s ease-out;
pointer-events: all;
font-size: 14px;
max-width: 300px;
}
.toast.success {
border-color: var(--success);
background: rgba(34, 197, 94, 0.1);
}
.toast.error {
border-color: var(--accent);
background: rgba(239, 68, 68, 0.1);
}
@keyframes toastSlideIn {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* FULLSCREEN MODE */
.app.fullscreen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
max-width: none;
padding: 0;
z-index: 10000;
background: var(--black);
}
.app.fullscreen .header,
.app.fullscreen .explainer,
.app.fullscreen .model-selection-container,
.app.fullscreen .persona-selection-container {
display: none;
}
.app.fullscreen .chat-container {
height: 100vh;
border-radius: 0;
border: none;
}
.fullscreen-toggle {
background: var(--black-card);
border: 1px solid var(--border);
padding: 6px 8px;
border-radius: 6px;
color: var(--gray);
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
}
.fullscreen-toggle:hover {
border-color: var(--brand);
color: var(--brand);
}
.app.fullscreen .fullscreen-toggle {
position: absolute;
top: 16px;
right: 16px;
z-index: 10001;
background: var(--black-soft);
border: 1px solid var(--border);
}
/* COPY FUNCTIONALITY */
.message.bot .message-bubble {
position: relative;
}
.message.bot:hover .copy-button {
opacity: 1;
}
.copy-button {
position: absolute;
top: 8px;
right: 8px;
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 4px;
padding: 4px 6px;
font-size: 10px;
cursor: pointer;
opacity: 0;
transition: all 0.2s ease;
color: var(--gray);
font-weight: 500;
}
.copy-button:hover {
background: var(--brand);
color: var(--white);
border-color: var(--brand);
}
.copy-button.copied {
background: var(--success);
color: var(--white);
border-color: var(--success);
}
/* Credit System */
.credits-container {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 12px;
padding: 12px 16px;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.credits-container:hover {
border-color: var(--brand);
transform: translateY(-1px);
}
.credits-progress {
flex: 1;
min-width: 80px;
}
.credits-bar {
width: 100%;
height: 4px;
background: var(--border);
border-radius: 2px;
overflow: hidden;
margin-top: 4px;
}
.credits-fill {
height: 100%;
background: var(--gradient-brand);
border-radius: 2px;
transition: width 0.5s ease;
}
.credits-text {
font-size: 12px;
color: var(--gray);
}
.credits-number {
font-weight: 700;
color: var(--brand);
}
/* Dynamic Content Loading */
.loading-placeholder {
background: var(--black-card);
border-radius: 8px;
padding: 16px;
text-align: center;
color: var(--gray);
font-size: 14px;
}
.loading-animation {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid var(--border);
border-radius: 50%;
border-top-color: var(--brand);
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Premium Indicators */
.premium-badge {
position: absolute;
top: -4px;
right: -4px;
background: var(--gradient-brand);
color: var(--white);
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 700;
}
/* Model Organization */
.model-section {
margin-bottom: 16px;
}
.model-section-title {
font-size: 10px;
font-weight: 600;
color: var(--gray);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 8px;
padding-left: 8px;
}
.model-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 8px;
}
.model-option {
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px 12px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
text-align: center;
position: relative;
}
.model-option:hover {
border-color: var(--brand);
transform: translateY(-1px);
}
.model-option.active {
background: rgba(93, 92, 222, 0.1);
border-color: var(--brand);
box-shadow: var(--shadow-glow);
}
.model-name {
font-size: 16px;
font-weight: 600;
margin-bottom: 2px;
}
.model-cost {
font-size: 14px;
text-transform: uppercase;
opacity: 0.8;
}
.model-cost.free {
color: var(--success);
}
.model-cost.paid {
color: var(--warning);
}
/* Persona Cards */
.persona-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 12px;
}
.persona-card {
background: var(--black-card);
border: 2px solid var(--border);
border-radius: 12px;
padding: 16px 12px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
position: relative;
}
.persona-card:hover {
border-color: var(--brand);
transform: translateY(-2px);
}
.persona-card.active {
border-color: var(--brand);
background: rgba(93, 92, 222, 0.05);
box-shadow: var(--shadow-glow);
}
.persona-card.premium {
border-color: var(--gold);
}
.persona-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
margin: 0 auto 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: var(--white);
position: relative;
}
.persona-name {
font-weight: 600;
font-size: 18px;
margin-bottom: 4px;
}
.persona-desc {
font-size: 13px;
color: var(--gray);
line-height: 1.3;
}
/* Chat Interface */
.chat-container {
background: var(--black-soft);
border: 3px solid var(--border);
border-radius: 20px;
height: 500px;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.chat-header {
padding: 16px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
background: var(--black-card);
}
.chat-title {
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
flex: 1;
}
.chat-controls {
display: flex;
gap: 6px;
}
.chat-control-btn {
background: transparent;
border: 1px solid var(--border);
padding: 6px 8px;
border-radius: 6px;
color: var(--gray);
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
}
.chat-control-btn:hover {
border-color: var(--brand);
color: var(--brand);
}
.chat-control-btn.new-chat {
background: var(--gradient-brand);
color: var(--white);
border: none;
}
[data-theme="light"] .chat-control-btn.new-chat {
color: var(--black);
}
.chat-control-btn.new-chat:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-glow);
}
.chat-container.flexbox {
height: 70vh;
max-height: 800px;
min-height: 400px;
}
.layout-toggle {
background: var(--black-card);
border: 1px solid var(--border);
padding: 6px 8px;
border-radius: 6px;
color: var(--gray);
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
scroll-behavior: smooth;
}
.message {
margin-bottom: 16px;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.message-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
font-size: 12px;
color: var(--gray);
}
.message-avatar {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 600;
}
.message.user .message-avatar {
background: var(--gradient-brand);
color: var(--white);
}
[data-theme="light"] .message.user .message-avatar {
color: var(--black);
}
.message.bot .message-avatar {
background: var(--gradient-success);
color: var(--white);
}
[data-theme="light"] .message.bot .message-avatar {
color: var(--black);
}
.message-bubble {
padding: 12px 16px;
border-radius: 18px;
font-size: 14px;
line-height: 1.5;
max-width: 85%;
word-wrap: break-word;
}
.message.user .message-bubble {
margin-left: auto;
background: var(--gradient-brand);
color: var(--white);
}
[data-theme="light"] .message.user .message-bubble {
color: var(--black);
}
.message.bot .message-bubble {
background: linear-gradient(135deg, var(--black-card) 0%, rgba(93, 92, 222, 0.05) 100%);
color: var(--gray-light);
border: 1px solid var(--border);
padding-right: 40px; /* Space for copy button */
}
/* System Message Styling */
.message.system .message-bubble {
background: rgba(93, 92, 222, 0.1);
border: 1px solid var(--brand);
color: var(--brand);
font-size: 12px;
padding: 8px 12px;
font-style: italic;
text-align: center;
max-width: 100%;
}
.message.system .message-avatar {
background: var(--gradient-brand);
font-size: 12px;
}
.chat-input-area {
padding: 16px;
border-top: 1px solid var(--border);
background: var(--black-card);
flex-shrink: 0;
}
.chat-input-container {
display: flex;
gap: 8px;
align-items: flex-end;
}
.chat-input {
flex: 1;
background: var(--black);
border: 1px solid var(--border);
border-radius: 12px;
padding: 12px;
font-size: 16px;
color: var(--white);
resize: none;
min-height: 40px;
max-height: 100px;
transition: border-color 0.2s ease;
font-family: inherit;
}
.chat-input:focus {
outline: none;
border-color: var(--brand);
}
.chat-input::placeholder {
color: var(--gray-dark);
}
.send-button {
background: var(--gradient-brand);
border: none;
padding: 12px 16px;
border-radius: 12px;
color: var(--white);
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 6px;
min-height: 40px;
}
[data-theme="light"] .send-button {
color: var(--black);
}
.send-button:hover:not(:disabled) {
transform: translateY(-1px);
}
.send-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
flex-wrap: wrap;
gap: 16px;
}
.logo {
font-size: 20px;
font-weight: 700;
background: var(--gradient-brand);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
flex-shrink: 0;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.theme-toggle {
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 50px;
padding: 8px 12px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
flex-shrink: 0;
}
.theme-toggle:hover {
border-color: var(--brand);
}
/* Explainer Section */
.explainer {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
text-align: center;
}
.explainer h1 {
font-size: 20px;
font-weight: 600;
color: var(--brand);
margin-bottom: 8px;
}
.explainer p {
font-size: 14px;
color: var(--gray-light);
line-height: 1.5;
}
/* Credits Modal */
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 9000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
padding: 16px;
}
.modal.active {
opacity: 1;
visibility: visible;
}
.modal-content {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 20px;
padding: 24px;
max-width: 500px;
width: 100%;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-title {
font-size: 20px;
font-weight: 700;
margin: 0;
}
.modal-close {
background: none;
border: none;
color: var(--gray);
font-size: 20px;
cursor: pointer;
padding: 4px;
}
.modal-close:hover {
color: var(--white);
}
.pricing-option {
background: var(--black-card);
border: 2px solid var(--border);
border-radius: 16px;
padding: 20px;
margin-bottom: 12px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
}
.pricing-option:hover {
border-color: var(--brand);
transform: translateY(-2px);
}
.pricing-option.recommended {
border-color: var(--success);
position: relative;
}
.pricing-option.recommended::before {
content: "RECOMMENDED";
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
background: var(--success);
color: var(--white);
padding: 4px 8px;
border-radius: 12px;
font-size: 9px;
font-weight: 700;
}
.pricing-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 6px;
}
.pricing-price {
font-size: 24px;
font-weight: 700;
color: var(--success);
margin-bottom: 8px;
}
.pricing-features {
list-style: none;
text-align: left;
color: var(--gray-light);
margin-bottom: 12px;
}
.pricing-features li {
padding: 3px 0;
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
}
.pricing-features li::before {
content: "✓";
color: var(--success);
font-weight: bold;
}
.action-button {
background: var(--gradient-brand);
border: none;
padding: 12px 12px;
border-radius: 16px;
color: var(--white);
font-weight: 600;
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
width: 70%;
}
[data-theme="light"] .action-button {
color: var(--black);
}
.action-button:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-glow);
}
/* Typing indicator */
.typing-indicator {
display: inline-flex;
align-items: center;
gap: 2px;
}
.typing-dot {
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--brand);
animation: typing 1.4s infinite;
}
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-10px); opacity: 1; }
}
/* Error States */
.error-message {
background: rgba(239, 68, 68, 0.1);
border: 1px solid var(--accent);
border-radius: 8px;
padding: 12px;
margin: 8px 0;
color: var(--accent);
font-size: 14px;
display: none;
}
.error-message.show {
display: block;
animation: errorSlide 0.3s ease-out;
}
@keyframes errorSlide {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Mobile Optimizations */
@media (max-width: 768px) {
.app {
padding: 12px;
}
.header {
flex-direction: column;
align-items: stretch;
text-align: center;
margin-bottom: 20px;
}
.header-actions {
justify-content: center;
gap: 8px;
}
.model-grid {
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 6px;
}
.persona-grid {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 8px;
}
.chat-container {
height: 400px;
}
.explainer h3 {
font-size: 16px;
}
.explainer p {
font-size: 13px;
}
.app.fullscreen .chat-container {
height: 100vh;
}
}
</style>
</head>
<body data-theme="light">
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<div class="app" id="app">
<!-- Fullscreen Toggle (Visible in fullscreen mode) -->
<button class="fullscreen-toggle" id="fullscreenToggle">
<span id="fullscreenIcon">⛶</span>
<span id="fullscreenText">Fullscreen</span>
</button>
<!-- Header -->
<header class="header">
<div class="logo">BrutallyHonest.ai</div>
<div class="header-actions">
<!-- Credits Display -->
<div class="credits-container" id="creditsDisplay">
<div class="credits-progress">
<div class="credits-text">
<span class="credits-number" id="creditsAmount">10</span> /
<span id="creditsMax">1000</span> credits
</div>
<div class="credits-bar">
<div
class="credits-fill"
id="creditsFill"
style="width: 1%"
></div>
</div>
</div>
<div
style="font-size: 12px; color: var(--brand); font-weight: 600;"
>
Upgrade
</div>
</div>
<!-- Theme Toggle -->
<div class="theme-toggle" id="themeToggle">
<span id="themeIcon">☀️</span>
<span id="themeText">Light</span>
</div>
</div>
</header>
<!-- Explainer -->
<section class="explainer">
<h3>🔥 The AI That Has Transformed Thousands</h3>
<p>
Choose your AI personality below, pick a model that powers the
conversation, and start chatting.
<strong>Free models are unlimited.</strong> Premium models cost
credits but deliver breakthrough insights.
</p>
</section>
<!-- Error Display -->
<div class="error-message" id="errorMessage">
<!-- Error content will be populated by JavaScript -->
</div>
<!-- Main Chat Interface -->
<div class="chat-container">
<div class="chat-header">
<div class="chat-title">
<span id="currentPersonaEmoji">🔥</span>
<span id="currentPersonaName">Loading...</span>
<span id="currentModelInfo" style="font-size: 12px; color: var(--gray);"></span>
</div>
<div class="chat-controls">
<button class="chat-control-btn new-chat" id="newChatBtn">✨ New Chat</button>
<button
class="fullscreen-toggle chat-control-btn"
id="chatFullscreenBtn"
>
⛶
</button>
<button class="layout-toggle chat-control-btn" id="layoutToggle">📐</button>
<button class="chat-control-btn" id="saveBtn">💾</button>
<button class="chat-control-btn" id="clearBtn">🗑️</button>
<button class="chat-control-btn" id="exportBtn">📤</button>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<div class="message bot">
<div class="message-header">
<div class="message-avatar">🔥</div>
<span>Loading...</span>
<span>•</span>
<span>Just now</span>
</div>
<div class="message-bubble">
Welcome! Configuring your experience...
<button
class="copy-button"
onclick="copyMessage(this)"
data-text="Welcome! Configuring your experience..."
>
Copy
</button>
</div>
</div>
</div>
<div class="chat-input-area">
<div class="chat-input-container">
<textarea
class="chat-input"
id="chatInput"
placeholder="Type your message here..."
rows="1"
></textarea>
<button class="send-button" id="sendButton">
<span id="sendButtonText">Send</span>
<span id="sendButtonIcon">→</span>
</button>
</div>
</div>
</div>
<!-- Model Selection -->
<div
style="margin: 20px 0;"
class="model-selection-container"
id="modelSelectionContainer"
>
<h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);">
Select AI Model
</h4>
<div class="loading-placeholder" id="modelsLoadingPlaceholder">
<div class="loading-animation"></div>
Loading models...
</div>
<div id="modelsContainer"></div>
</div>
<!-- Persona Selection -->
<div
style="margin: 20px 0;"
class="persona-selection-container"
id="personaSelectionContainer"
>
<h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);">
Choose Your AI Personality
</h4>
<div class="loading-placeholder" id="personasLoadingPlaceholder">
<div class="loading-animation"></div>
Loading personalities...
</div>
<div class="persona-grid" id="personaGrid"></div>
</div>
</div>
<!-- Credits Modal -->
<div class="modal" id="creditsModal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">💎 Get More Credits</h2>
<button class="modal-close" id="closeCreditsModal">×</button>
</div>
<div class="pricing-option recommended">
<div class="pricing-title">Monthly Subscription</div>
<div class="pricing-price">
$9<span style="font-size: 12px; color: var(--gray);">/month</span>
</div>
<ul class="pricing-features">
<li>500 credits per month</li>
<li>Unlimited free models</li>
<li>Access to all premium models</li>
<li>Credits roll over (up to 1000)</li>
<li>Priority support</li>
</ul>
<button class="action-button" onclick="purchaseMonthly()">
Subscribe Now ✨
</button>
</div>
<div class="pricing-option">
<div class="pricing-title">Pay As You Go</div>
<div class="pricing-price">From $2.99</div>
<ul class="pricing-features">
<li>100 credits for $2.99</li>
<li>250 credits for $5.99</li>
<li>500 credits for $9.99</li>
<li>Credits never expire</li>
</ul>
<button class="action-button" onclick="purchaseCredits()">
Buy Credits 💰
</button>
</div>
</div>
</div>
<script>
// ===================================================================================
// CONFIGURATION - UPDATE THESE URLs
// ===================================================================================
const CONFIG = {
mainWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/bha-mvp',
configWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/config'
};
// ===================================================================================
// ENHANCED STATE MANAGEMENT SYSTEM
// ===================================================================================
class BHAStateManager {
constructor() {
this.state = {
user: {
credits: 1000,
maxCredits: 1000,
userId: this.generateUserId()
},
chat: {
currentModel: null,
currentPersona: null,
currentModelCost: 0,
currentModelName: '',
conversationId: null,
messages: [],
isTyping: false
},
ui: {
currentTheme: 'light',
isFullscreen: false
},
config: {
personas: [],
models: [],
modelsByTier: { free: [], standard: [], premium: [] },
isLoaded: false
}
};
this.listeners = new Map();
this.loadPersistedState();
}
generateUserId() {
const stored = localStorage.getItem('bha_user_id');
if (stored) return stored;
const id = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('bha_user_id', id);
return id;
}
loadPersistedState() {
// Restore saved preferences
this.state.ui.currentTheme = localStorage.getItem('bha_selectedTheme') || 'light';
this.state.chat.currentPersona = localStorage.getItem('bha_selectedPersona');
this.state.chat.currentModel = localStorage.getItem('bha_selectedModel');
this.state.chat.conversationId = localStorage.getItem('bha_conversation_id') || this.generateConversationId();
}
generateConversationId() {
return 'conv_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5);
}
subscribe(key, callback) {
if (!this.listeners.has(key)) {
this.listeners.set(key, []);
}
this.listeners.get(key).push(callback);
}
setState(path, value) {
// Ensure string values for critical fields
if (['currentModel', 'currentPersona', 'conversationId'].includes(path)) {
value = String(value || '');
}
const keys = path.split('.');
let current = this.state;
// Navigate to the parent object
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
}
const finalKey = keys[keys.length - 1];
if (current[finalKey] === value) return; // No change needed
console.log(`🔄 State Change: ${path} from "${current[finalKey]}" to "${value}"`);
current[finalKey] = value;
// Persist critical state
if (path.startsWith('chat.')) {
localStorage.setItem(`bha_${finalKey}`, value);
}
// Notify listeners
if (this.listeners.has(path)) {
this.listeners.get(path).forEach(callback => callback(value));
}
}
getState(path) {
const keys = path.split('.');
let current = this.state;
for (const key of keys) {
if (current === undefined || current === null) return undefined;
current = current[key];
}
return current;
}
resetConversationContext(reason = 'manual') {
const newConvId = this.generateConversationId();
this.setState('chat.conversationId', newConvId);
this.setState('chat.messages', []); // Clear messages for new conversation
console.log(`🔄 Context Reset (${reason}): New conversation ${newConvId}`);
return newConvId;
}
getAllState() {
return this.state;
}
}
// Initialize global state manager
const StateManager = new BHAStateManager();
// ===================================================================================
// ENHANCED CONFIGURATION MANAGER
// ===================================================================================
class BHAConfigManager {
constructor() {
this.config = {
personas: [],
models: [],
modelsByTier: { free: [], standard: [], premium: [] },
features: {}
};
this.isLoaded = false;
}
async loadConfiguration() {
try {
console.log('🔄 Loading configuration from:', CONFIG.configWebhookUrl);
const response = await fetch(CONFIG.configWebhookUrl);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.config = await response.json();
this.isLoaded = true;
// Update global state
StateManager.setState('config', this.config);
StateManager.setState('config.isLoaded', true);
console.log('✅ Configuration loaded:', this.config);
this.renderDynamicContent();
this.initializeDefaults();
} catch (error) {
console.error('❌ Config load failed:', error);
this.loadFallbackConfig();
}
}
loadFallbackConfig() {
console.log('🔄 Loading fallback configuration...');
this.config = {
personas: [
{
id: 'QuickResonanceSpark_Persona_Haiku',
name: 'Brutally Honest',
emoji: '🔥',
description: 'No BS, just raw truth',
isPremium: false,
gradientColor: 'linear-gradient(135deg, #EF4444, #F59E0B)'
},
{
id: 'ResonanceSession_Orchestrator_Opus',
name: 'Deep Guide',
emoji: '🧠',
description: 'Profound insights & breakthrough clarity',
isPremium: false,
gradientColor: 'linear-gradient(135deg, #10B981, #06D6A0)'
}
],
models: [
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' },
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' }
],
modelsByTier: {
free: [
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' },
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' }
],
standard: [],
premium: []
}
};
this.isLoaded = true;
StateManager.setState('config', this.config);
StateManager.setState('config.isLoaded', true);
this.renderDynamicContent();
this.initializeDefaults();
}
renderDynamicContent() {
this.renderPersonas();
this.renderModels();
}
renderPersonas() {
const container = document.getElementById('personaGrid');
const loadingPlaceholder = document.getElementById('personasLoadingPlaceholder');
if (loadingPlaceholder) loadingPlaceholder.style.display = 'none';
container.innerHTML = this.config.personas.map(persona => `
<div class="persona-card ${persona.isPremium ? 'premium' : ''}"
data-persona-id="${persona.id}"
data-persona-name="${persona.name}"
data-persona-emoji="${persona.emoji}">
<div class="persona-avatar" style="background: ${persona.gradientColor};">
${persona.emoji}
${persona.isPremium ? '<div class="premium-badge">💎</div>' : ''}
</div>
<div class="persona-name">${persona.name}</div>
<div class="persona-desc">${persona.description}</div>
</div>
`).join('');
// Add event listeners
document.querySelectorAll('.persona-card').forEach(card => {
card.addEventListener('click', () => this.selectPersona(card));
});
}
renderModels() {
const container = document.getElementById('modelsContainer');
const loadingPlaceholder = document.getElementById('modelsLoadingPlaceholder');
if (loadingPlaceholder) loadingPlaceholder.style.display = 'none';
const tiers = ['free', 'standard', 'premium'];
const tierLabels = {
'free': 'Free Models',
'standard': 'Standard Models (5-15 credits)',
'premium': 'Premium Models (15+ credits)'
};
container.innerHTML = tiers.map(tier => {
const models = this.config.modelsByTier[tier] || [];
if (models.length === 0) return '';
return `
<div class="model-section" data-tier="${tier}">
<div class="model-section-title">${tierLabels[tier]}</div>
<div class="model-grid">
${models.map(model => `
<div class="model-option"
data-model-id="${model.id}"
data-model-name="${model.name}"
data-model-cost="${model.cost}">
<div class="model-name">${model.name}</div>
<div class="model-cost ${model.cost === 0 ? 'free' : 'paid'}">
${model.cost === 0 ? 'FREE' : `${model.cost} credits`}
</div>
</div>
`).join('')}
</div>
</div>
`;
}).join('');
// Add event listeners
document.querySelectorAll('.model-option').forEach(option => {
option.addEventListener('click', () => this.selectModel(option));
});
}
selectPersona(personaElement) {
// Visual feedback
document.querySelectorAll('.persona-card').forEach(el => el.classList.remove('active'));
personaElement.classList.add('active');
// Extract data with type safety
const personaId = String(personaElement.dataset.personaId || '');
const personaName = String(personaElement.dataset.personaName || '');
const personaEmoji = String(personaElement.dataset.personaEmoji || '');
// Update state
StateManager.setState('chat.currentPersona', personaId);
// Update UI
document.getElementById('currentPersonaEmoji').textContent = personaEmoji;
document.getElementById('currentPersonaName').textContent = personaName;
// System notification (but don't reset conversation)
this.addSystemMessage(`🎭 Now chatting with ${personaName}`);
console.log('✅ Persona Selected:', personaId, typeof personaId);
}
selectModel(modelElement) {
// Visual feedback
document.querySelectorAll('.model-option').forEach(el => el.classList.remove('active'));
modelElement.classList.add('active');
// Extract data with type safety
const modelId = String(modelElement.dataset.modelId || '');
const modelName = String(modelElement.dataset.modelName || '');
const modelCost = parseInt(modelElement.dataset.modelCost || '0');
// Update state
StateManager.setState('chat.currentModel', modelId);
StateManager.setState('chat.currentModelCost', modelCost);
StateManager.setState('chat.currentModelName', modelName);
// Update UI
const modelInfo = document.getElementById('currentModelInfo');
if (modelInfo) {
modelInfo.textContent = `(${modelName})`;
}
// System notification (but don't reset conversation)
this.addSystemMessage(`🤖 Now using ${modelName}`);
console.log('✅ Model Selected:', modelId, typeof modelId);
}
addSystemMessage(content) {
const messagesContainer = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = 'message system';
messageDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">⚙️</div>
<span>System</span>
<span>•</span>
<span>${new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
</div>
<div class="message-bubble system-message">
${content}
</div>
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
initializeDefaults() {
// Restore selections from localStorage or use first available
const savedPersonaId = localStorage.getItem('bha_selectedPersona');
const savedModelId = localStorage.getItem('bha_selectedModel');
const savedLayout = localStorage.getItem('bha_flexboxMode') === 'true';
// Restore layout preference
if (savedLayout) {
document.querySelector('.chat-container').classList.add('flexbox');
}
// Initialize persona
if (this.config.personas.length > 0) {
let targetPersona = savedPersonaId ?
document.querySelector(`[data-persona-id="${savedPersonaId}"]`) :
document.querySelector('.persona-card');
if (targetPersona) {
this.selectPersona(targetPersona);
}
}
// Initialize model
if (this.config.models.length > 0) {
let targetModel = savedModelId ?
document.querySelector(`[data-model-id="${savedModelId}"]`) :
document.querySelector('.model-option');
if (targetModel) {
this.selectModel(targetModel);
}
}
// Update welcome message
setTimeout(() => {
this.updateWelcomeMessage();
}, 100);
// Emergency fallback if nothing is selected
setTimeout(() => {
if (!StateManager.getState('chat.currentPersona')) {
const firstPersona = document.querySelector('.persona-card');
if (firstPersona) {
console.log('🆘 Emergency persona selection');
this.selectPersona(firstPersona);
}
}
if (!StateManager.getState('chat.currentModel')) {
const firstModel = document.querySelector('.model-option');
if (firstModel) {
console.log('🆘 Emergency model selection');
this.selectModel(firstModel);
}
}
}, 500);
}
updateWelcomeMessage() {
const messagesContainer = document.getElementById('chatMessages');
const currentPersonaId = StateManager.getState('chat.currentPersona');
const currentPersona = this.config.personas.find(p => p.id === currentPersonaId);
if (currentPersona) {
messagesContainer.innerHTML = `
<div class="message bot">
<div class="message-header">
<div class="message-avatar">${currentPersona.emoji}</div>
<span>${currentPersona.name}</span>
<span>•</span>
<span>Just now</span>
</div>
<div class="message-bubble">
I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today?
<button class="copy-button" onclick="copyMessage(this)" data-text="I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today?">Copy</button>
</div>
</div>
`;
}
}
}
// ===================================================================================
// UTILITY FUNCTIONS
// ===================================================================================
function showToast(message, type = 'success', duration = 3000) {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(100%)';
setTimeout(() => {
if (toast.parentNode) {
container.removeChild(toast);
}
}, 300);
}, duration);
}
function showError(message) {
const errorEl = document.getElementById('errorMessage');
errorEl.textContent = message;
errorEl.classList.add('show');
setTimeout(() => {
errorEl.classList.remove('show');
}, 5000);
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// ===================================================================================
// UI FUNCTIONS
// ===================================================================================
function toggleFullscreen() {
const app = document.getElementById('app');
const fullscreenIcon = document.getElementById('fullscreenIcon');
const fullscreenText = document.getElementById('fullscreenText');
const isFullscreen = !StateManager.getState('ui.isFullscreen');
StateManager.setState('ui.isFullscreen', isFullscreen);
app.classList.toggle('fullscreen', isFullscreen);
if (isFullscreen) {
fullscreenIcon.textContent = '⛶';
fullscreenText.textContent = 'Exit';
} else {
fullscreenIcon.textContent = '⛶';
fullscreenText.textContent = 'Fullscreen';
}
localStorage.setItem('bha_fullscreenMode', isFullscreen.toString());
}
function toggleLayout() {
const chatContainer = document.querySelector('.chat-container');
const isFlexbox = chatContainer.classList.toggle('flexbox');
localStorage.setItem('bha_flexboxMode', isFlexbox.toString());
showToast(`Layout: ${isFlexbox ? 'Flexible' : 'Fixed'}`, 'success', 2000);
}
function toggleTheme() {
const currentTheme = StateManager.getState('ui.currentTheme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
StateManager.setState('ui.currentTheme', newTheme);
document.body.dataset.theme = newTheme;
const icon = document.getElementById('themeIcon');
const text = document.getElementById('themeText');
if (newTheme === 'light') {
icon.textContent = '☀️';
text.textContent = 'Light';
} else {
icon.textContent = '🌙';
text.textContent = 'Dark';
}
localStorage.setItem('bha_selectedTheme', newTheme);
}
function copyMessage(button) {
const text = button.dataset.text;
navigator.clipboard.writeText(text).then(() => {
const originalText = button.textContent;
button.textContent = 'Copied!';
button.classList.add('copied');
setTimeout(() => {
button.textContent = originalText;
button.classList.remove('copied');
}, 2000);
showToast('Message copied to clipboard', 'success', 2000);
}).catch(err => {
console.error('Copy failed:', err);
showToast('Failed to copy message', 'error', 3000);
});
}
// ===================================================================================
// CREDITS SYSTEM
// ===================================================================================
function updateCreditsDisplay() {
const credits = StateManager.getState('user.credits');
const maxCredits = StateManager.getState('user.maxCredits');
document.getElementById('creditsAmount').textContent = credits;
document.getElementById('creditsMax').textContent = maxCredits;
const percentage = (credits / maxCredits) * 100;
document.getElementById('creditsFill').style.width = percentage + '%';
}
function showCreditsModal() {
document.getElementById('creditsModal').classList.add('active');
}
function hideCreditsModal() {
document.getElementById('creditsModal').classList.remove('active');
}
function purchaseMonthly() {
const userId = StateManager.getState('user.userId');
window.open(`https://buy.stripe.com/YOUR_MONTHLY_LINK?client_reference_id=${userId}`, '_blank');
}
function purchaseCredits() {
const userId = StateManager.getState('user.userId');
window.open(`https://buy.stripe.com/YOUR_CREDIT_PACK_LINK?client_reference_id=${userId}`, '_blank');
}
// ===================================================================================
// CHAT FUNCTIONS
// ===================================================================================
function addMessage(content, isUser = false, persona = null) {
const messagesContainer = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user' : 'bot'}`;
const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
const currentPersonaId = StateManager.getState('chat.currentPersona');
const personas = StateManager.getState('config.personas') || [];
const currentPersona = personas.find(p => p.id === currentPersonaId);
const sender = isUser ? 'You' : (persona || currentPersona?.name || 'AI');
const avatar = isUser ? '👤' : (currentPersona?.emoji || '🤖');
// Process markdown for bot messages
const processedContent = isUser ?
escapeHtml(content) :
(typeof marked !== 'undefined' ? marked.parse(content) : content);
const copyButton = isUser ? '' :
`<button class="copy-button" onclick="copyMessage(this)" data-text="${escapeHtml(content)}">Copy</button>`;
messageDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">${avatar}</div>
<span>${sender}</span>
<span>•</span>
<span>${timestamp}</span>
</div>
<div class="message-bubble">
${processedContent}
${copyButton}
</div>
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Store in state
const messages = StateManager.getState('chat.messages') || [];
messages.push({
content: content,
isUser: isUser,
timestamp: Date.now(),
persona: persona
});
StateManager.setState('chat.messages', messages);
}
function addTypingIndicator() {
const messagesContainer = document.getElementById('chatMessages');
const typingDiv = document.createElement('div');
typingDiv.id = 'typingIndicator';
typingDiv.className = 'message bot';
const currentPersonaId = StateManager.getState('chat.currentPersona');
const personas = StateManager.getState('config.personas') || [];
const currentPersona = personas.find(p => p.id === currentPersonaId);
typingDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">${currentPersona?.emoji || '🤖'}</div>
<span>${currentPersona?.name || 'AI'}</span>
<span>•</span>
<span>typing...</span>
</div>
<div class="message-bubble">
<div class="typing-indicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
`;
messagesContainer.appendChild(typingDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function removeTypingIndicator() {
const indicator = document.getElementById('typingIndicator');
if (indicator) {
indicator.remove();
}
}
// ===================================================================================
// BULLETPROOF SEND MESSAGE FUNCTION
// ===================================================================================
async function sendMessage() {
const input = document.getElementById('chatInput');
const message = input.value.trim();
if (!message || StateManager.getState('chat.isTyping')) return;
// Get current state values
const currentPersona = StateManager.getState('chat.currentPersona');
const currentModel = StateManager.getState('chat.currentModel');
let conversationId = StateManager.getState('chat.conversationId');
// If no conversation ID exists, create one
if (!conversationId) {
conversationId = StateManager.generateConversationId();
StateManager.setState('chat.conversationId', conversationId);
}
// Validation
if (!currentPersona || !currentModel) {
showError('Please select a persona and model before sending a message.');
return;
}
// Credit check
const modelCost = StateManager.getState('chat.currentModelCost') || 0;
const userCredits = StateManager.getState('user.credits');
if (modelCost > 0 && userCredits < modelCost) {
showError(`Insufficient credits! You need ${modelCost} credits.`);
showCreditsModal();
return;
}
// Add user message
addMessage(message, true);
input.value = '';
input.style.height = 'auto';
// Show typing
addTypingIndicator();
StateManager.setState('chat.isTyping', true);
// Disable send button
const sendBtn = document.getElementById('sendButton');
sendBtn.disabled = true;
document.getElementById('sendButtonText').textContent = 'Sending';
document.getElementById('sendButtonIcon').innerHTML = '<div class="loading-animation"></div>';
try {
// Construct bulletproof payload
const payload = {
user_id: String(StateManager.getState('user.userId')),
bot_to_call: String(currentPersona),
full_prompt_for_llm: String(message),
raw_user_situation: String(message),
selected_model_for_openrouter: String(currentModel),
conversation_id: String(conversationId),
timestamp: new Date().toISOString(),
frontend_state_snapshot: JSON.stringify({
persona: currentPersona,
model: currentModel,
conversation_id: conversationId,
message_count: StateManager.getState('chat.messages').length || 0
})
};
console.log('🚀 Sending Payload:', payload);
const response = await fetch(CONFIG.mainWebhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log('✅ Response received:', data);
// Extract response text
let aiResponseText = "I'm having trouble formulating a response right now.";
if (data && typeof data.response === 'string') {
aiResponseText = data.response;
} else if (data.bot_response && data.bot_response.analysis) {
aiResponseText = data.bot_response.analysis;
} else if (data.final_response_payload && data.final_response_payload.bot_response) {
aiResponseText = data.final_response_payload.bot_response.analysis;
}
removeTypingIndicator();
addMessage(aiResponseText, false);
// Update credits
if (data.credits_remaining !== undefined) {
StateManager.setState('user.credits', data.credits_remaining);
} else if (modelCost > 0) {
StateManager.setState('user.credits', Math.max(0, userCredits - modelCost));
}
updateCreditsDisplay();
// Show tool indicator if tool was used
if (data.tool_used) {
showToast('Enhanced response generated', 'success', 2000);
}
} catch (error) {
console.error('❌ Error sending message:', error);
removeTypingIndicator();
showError('Failed to send message. Please try again.');
} finally {
StateManager.setState('chat.isTyping', false);
sendBtn.disabled = false;
document.getElementById('sendButtonText').textContent = 'Send';
document.getElementById('sendButtonIcon').textContent = '→';
}
}
// ===================================================================================
// CHAT CONTROLS
// ===================================================================================
function startNewChat() {
if (confirm('Start a new conversation? Current chat will be cleared.')) {
StateManager.resetConversationContext('new_chat_button');
configManager.updateWelcomeMessage();
showToast('✨ New conversation started', 'success');
}
}
function saveConversation() {
const messages = StateManager.getState('chat.messages') || [];
if (messages.length === 0) {
showToast('No messages to save yet!', 'error');
return;
}
showToast('💾 Conversation saved! (Feature coming soon)', 'success');
}
function clearChat() {
if (confirm('Clear all messages? This cannot be undone!')) {
StateManager.setState('chat.messages', []);
configManager.updateWelcomeMessage();
showToast('Chat cleared', 'success');
}
}
function exportChat() {
const messages = StateManager.getState('chat.messages') || [];
if (messages.length === 0) {
showToast('No messages to export yet!', 'error');
return;
}
const currentPersonaId = StateManager.getState('chat.currentPersona');
const currentModel = StateManager.getState('chat.currentModel');
let content = `BrutallyHonest.ai Chat Export\n`;
content += `Exported: ${new Date().toLocaleString()}\n`;
content += `Model: ${currentModel}\n`;
content += `Persona: ${currentPersonaId}\n`;
content += '='.repeat(50) + '\n\n';
messages.forEach(msg => {
const timestamp = new Date(msg.timestamp).toLocaleString();
const speaker = msg.isUser ? 'You' : (msg.persona || 'AI');
content += `[${timestamp}] ${speaker}: ${msg.content}\n\n`;
});
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `BHA_Chat_${new Date().toISOString().split('T')[0]}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('Chat exported successfully', 'success');
}
// ===================================================================================
// INITIALIZATION
// ===================================================================================
const configManager = new BHAConfigManager();
async function init() {
console.log("🔥 BrutallyHonest.ai Initializing...");
// Restore saved preferences
const savedTheme = localStorage.getItem('bha_selectedTheme') || 'light';
const savedFullscreen = localStorage.getItem('bha_fullscreenMode') === 'true';
// Apply theme
StateManager.setState('ui.currentTheme', savedTheme);
document.body.dataset.theme = savedTheme;
const icon = document.getElementById('themeIcon');
const text = document.getElementById('themeText');
if (savedTheme === 'dark') {
icon.textContent = '🌙';
text.textContent = 'Dark';
}
// Apply fullscreen if previously set
if (savedFullscreen) {
StateManager.setState('ui.isFullscreen', true);
document.getElementById('app').classList.add('fullscreen');
document.getElementById('fullscreenIcon').textContent = '⛶';
document.getElementById('fullscreenText').textContent = 'Exit';
}
// Set up event listeners
document.getElementById('themeToggle').addEventListener('click', toggleTheme);
document.getElementById('creditsDisplay').addEventListener('click', showCreditsModal);
document.getElementById('closeCreditsModal').addEventListener('click', hideCreditsModal);
document.getElementById('layoutToggle').addEventListener('click', toggleLayout);
// Fullscreen toggles
document.getElementById('fullscreenToggle').addEventListener('click', toggleFullscreen);
document.getElementById('chatFullscreenBtn').addEventListener('click', toggleFullscreen);
// Chat controls
document.getElementById('newChatBtn').addEventListener('click', startNewChat);
document.getElementById('saveBtn').addEventListener('click', saveConversation);
document.getElementById('clearBtn').addEventListener('click', clearChat);
document.getElementById('exportBtn').addEventListener('click', exportChat);
// Send message
document.getElementById('sendButton').addEventListener('click', sendMessage);
document.getElementById('chatInput').addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Auto-resize textarea
document.getElementById('chatInput').addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 100) + 'px';
});
// Close modal when clicking outside
window.addEventListener('click', (e) => {
const modal = document.getElementById('creditsModal');
if (e.target === modal) {
hideCreditsModal();
}
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Escape to exit fullscreen
if (e.key === 'Escape' && StateManager.getState('ui.isFullscreen')) {
toggleFullscreen();
}
// F11 for fullscreen toggle
if (e.key === 'F11') {
e.preventDefault();
toggleFullscreen();
}
// Ctrl/Cmd + / for theme toggle
if ((e.ctrlKey || e.metaKey) && e.key === '/') {
e.preventDefault();
toggleTheme();
}
});
// Initialize displays
updateCreditsDisplay();
// Load configuration and render dynamic content
await configManager.loadConfiguration();
console.log('✅ BrutallyHonest.ai initialization complete!');
}
// Global functions for HTML onclick events
window.purchaseMonthly = purchaseMonthly;
window.purchaseCredits = purchaseCredits;
window.copyMessage = copyMessage;
// Initialize when ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
</script>
</body>
</html>
Works
er-embed: <!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" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <title> BrutallyHonest.ai - Transform Your Bullsh*t Into Breakthroughs </title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <style> :root { /* Light Theme (Default) */ --black: #FFFFFF; --black-soft: #F8F9FA; --black-card: #F1F3F4; --border: #E8EAED; --border-light: #DADCE0; --white: #000000; --gray: #5F6368; --gray-light: #3C4043; --gray-dark: #80868B; --brand: #5D5CDE; --brand-light: #7C7CE8; --brand-dark: #4B4BC7; --accent: #EF4444; --success: #22C55E; --warning: #F59E0B; --gold: #FCD34D; --gradient-brand: linear-gradient(135deg, #5D5CDE 0%, #7C7CE8 100%); --gradient-fire: linear-gradient(135deg, #EF4444 0%, #F59E0B 100%); --gradient-success: linear-gradient(135deg, #22C55E 0%, #4ADE80 100%); --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.08); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.12), 0 1px 2px -1px rgb(0 0 0 / 0.12); --shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06); --shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05); --shadow-glow: 0 0 0 1px rgba(93, 92, 222, 0.3), 0 0 20px rgba(93, 92, 222, 0.2); } .app { /* ... other styles ... */ padding: 0 16px 16px 16px; /* Top padding is 0, others remain 16px */ /* Or more explicitly: */ /* padding-top: 0; */ /* padding-left: 16px; */ /* padding-right: 16px; */ /* padding-bottom: 16px; */ /* ... other styles ... */ } /* Dark Theme Override */ [data-theme="dark"] { --black: #0F0F0F; --black-soft: #1A1A1A; --black-card: #262626; --border: #404040; --border-light: #525252; --white: #FFFFFF; --gray: #A3A3A3; --gray-light: #D4D4D4; --gray-dark: #737373; --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); } * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; } html { position: static; height: auto; overflow: auto; } body { background: var(--black); color: var(--white); line-height: 1.6; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transition: background-color 0.5s ease, color 0.5s ease; min-height: 100vh; overflow-x: hidden; position: relative; } .app { max-width: 1200px; margin: 0 auto; padding: 16px; min-height: 100vh; position: relative; width: 100%; transition: all 0.3s ease; } /* Toast Container */ .toast-container { position: fixed; top: 20px; right: 20px; z-index: 10000; pointer-events: none; } .toast { background: var(--black-soft); border: 1px solid var(--border); border-radius: 8px; padding: 12px 16px; margin-bottom: 8px; box-shadow: var(--shadow-lg); backdrop-filter: blur(8px); animation: toastSlideIn 0.3s ease-out; pointer-events: all; font-size: 14px; max-width: 300px; } .toast.success { border-color: var(--success); background: rgba(34, 197, 94, 0.1); } .toast.error { border-color: var(--accent); background: rgba(239, 68, 68, 0.1); } @keyframes toastSlideIn { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } } /* FULLSCREEN MODE */ .app.fullscreen { position: fixed; top: 0; left: 0; right: 0; bottom: 0; max-width: none; padding: 0; z-index: 10000; background: var(--black); } .app.fullscreen .header, .app.fullscreen .explainer, .app.fullscreen .model-selection-container, .app.fullscreen .persona-selection-container { display: none; } .app.fullscreen .chat-container { height: 100vh; border-radius: 0; border: none; } .fullscreen-toggle { background: var(--black-card); border: 1px solid var(--border); padding: 6px 8px; border-radius: 6px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 12px; } .fullscreen-toggle:hover { border-color: var(--brand); color: var(--brand); } .app.fullscreen .fullscreen-toggle { position: absolute; top: 16px; right: 16px; z-index: 10001; background: var(--black-soft); border: 1px solid var(--border); } /* COPY FUNCTIONALITY */ .message.bot .message-bubble { position: relative; } .message.bot:hover .copy-button { opacity: 1; } .copy-button { position: absolute; top: 8px; right: 8px; background: var(--black-card); border: 1px solid var(--border); border-radius: 4px; padding: 4px 6px; font-size: 10px; cursor: pointer; opacity: 0; transition: all 0.2s ease; color: var(--gray); font-weight: 500; } .copy-button:hover { background: var(--brand); color: var(--white); border-color: var(--brand); } .copy-button.copied { background: var(--success); color: var(--white); border-color: var(--success); } /* Credit System */ .credits-container { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; display: flex; align-items: center; gap: 12px; cursor: pointer; transition: all 0.2s ease; } .credits-container:hover { border-color: var(--brand); transform: translateY(-1px); } .credits-progress { flex: 1; min-width: 80px; } .credits-bar { width: 100%; height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; margin-top: 4px; } .credits-fill { height: 100%; background: var(--gradient-brand); border-radius: 2px; transition: width 0.5s ease; } .credits-text { font-size: 12px; color: var(--gray); } .credits-number { font-weight: 700; color: var(--brand); } /* Dynamic Content Loading */ .loading-placeholder { background: var(--black-card); border-radius: 8px; padding: 16px; text-align: center; color: var(--gray); font-size: 14px; } .loading-animation { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--border); border-radius: 50%; border-top-color: var(--brand); animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Premium Indicators */ .premium-badge { position: absolute; top: -4px; right: -4px; background: var(--gradient-brand); color: var(--white); border-radius: 50%; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; } /* Model Organization */ .model-section { margin-bottom: 16px; } .model-section-title { font-size: 10px; font-weight: 600; color: var(--gray); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; padding-left: 8px; } .model-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; } .model-option { background: var(--black-card); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 12px; text-align: center; position: relative; } .model-option:hover { border-color: var(--brand); transform: translateY(-1px); } .model-option.active { background: rgba(93, 92, 222, 0.1); border-color: var(--brand); box-shadow: var(--shadow-glow); } .model-name { font-size: 16px; font-weight: 600; margin-bottom: 2px; } .model-cost { font-size: 14px; text-transform: uppercase; opacity: 0.8; } .model-cost.free { color: var(--success); } .model-cost.paid { color: var(--warning); } /* Persona Cards */ .persona-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; } .persona-card { background: var(--black-card); border: 2px solid var(--border); border-radius: 12px; padding: 16px 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; position: relative; } .persona-card:hover { border-color: var(--brand); transform: translateY(-2px); } .persona-card.active { border-color: var(--brand); background: rgba(93, 92, 222, 0.05); box-shadow: var(--shadow-glow); } .persona-card.premium { border-color: var(--gold); } .persona-avatar { width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; color: var(--white); position: relative; } .persona-name { font-weight: 600; font-size: 18px; margin-bottom: 4px; } .persona-desc { font-size: 13px; color: var(--gray); line-height: 1.3; } /* Chat Interface */ .chat-container { background: var(--black-soft); border: 3px solid var(--border); border-radius: 20px; height: 500px; display: flex; flex-direction: column; overflow: hidden; position: relative; } .chat-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; background: var(--black-card); } .chat-title { font-weight: 600; display: flex; align-items: center; gap: 8px; font-size: 14px; flex: 1; } .chat-controls { display: flex; gap: 6px; } .chat-control-btn { background: transparent; border: 1px solid var(--border); padding: 6px 8px; border-radius: 6px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 12px; } .chat-control-btn:hover { border-color: var(--brand); color: var(--brand); } .chat-container.flexbox { height: 70vh; max-height: 800px; min-height: 400px; } .layout-toggle { background: var(--black-card); border: 1px solid var(--border); padding: 6px 8px; border-radius: 6px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 12px; } .chat-messages { flex: 1; overflow-y: auto; padding: 16px; scroll-behavior: smooth; } .message { margin-bottom: 16px; animation: slideIn 0.3s ease-out; } @keyframes slideIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } .message-header { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; font-size: 12px; color: var(--gray); } .message-avatar { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 600; } .message.user .message-avatar { background: var(--gradient-brand); color: var(--white); } [data-theme="light"] .message.user .message-avatar { color: var(--black); } .message.bot .message-avatar { background: var(--gradient-success); color: var(--white); } [data-theme="light"] .message.bot .message-avatar { color: var(--black); } .message-bubble { padding: 12px 16px; border-radius: 18px; font-size: 14px; line-height: 1.5; max-width: 85%; word-wrap: break-word; } .message.user .message-bubble { margin-left: auto; background: var(--gradient-brand); color: var(--white); } [data-theme="light"] .message.user .message-bubble { color: var(--black); } .message.bot .message-bubble { background: linear-gradient(135deg, var(--black-card) 0%, rgba(93, 92, 222, 0.05) 100%); color: var(--gray-light); border: 1px solid var(--border); padding-right: 40px; /* Space for copy button */ } .chat-input-area { padding: 16px; border-top: 1px solid var(--border); background: var(--black-card); flex-shrink: 0; } .chat-input-container { display: flex; gap: 8px; align-items: flex-end; } .chat-input { flex: 1; background: var(--black); border: 1px solid var(--border); border-radius: 12px; padding: 12px; font-size: 16px; color: var(--white); resize: none; min-height: 40px; max-height: 100px; transition: border-color 0.2s ease; font-family: inherit; } .chat-input:focus { outline: none; border-color: var(--brand); } .chat-input::placeholder { color: var(--gray-dark); } .send-button { background: var(--gradient-brand); border: none; padding: 12px 16px; border-radius: 12px; color: var(--white); font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; gap: 6px; min-height: 40px; } [data-theme="light"] .send-button { color: var(--black); } .send-button:hover:not(:disabled) { transform: translateY(-1px); } .send-button:disabled { opacity: 0.5; cursor: not-allowed; } /* Header */ .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px; flex-wrap: wrap; gap: 16px; } .logo { font-size: 20px; font-weight: 700; background: var(--gradient-brand); -webkit-background-clip: text; -webkit-text-fill-color: transparent; flex-shrink: 0; } .header-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } .theme-toggle { background: var(--black-card); border: 1px solid var(--border); border-radius: 50px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 14px; display: flex; align-items: center; gap: 8px; white-space: nowrap; flex-shrink: 0; } .theme-toggle:hover { border-color: var(--brand); } /* Explainer Section */ .explainer { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 24px; text-align: center; } .explainer h1 { font-size: 20px; font-weight: 600; color: var(--brand); margin-bottom: 8px; } .explainer p { font-size: 14px; color: var(--gray-light); line-height: 1.5; } /* Credits Modal */ .modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 9000; opacity: 0; visibility: hidden; transition: all 0.3s ease; padding: 16px; } .modal.active { opacity: 1; visibility: visible; } .modal-content { background: var(--black-soft); border: 1px solid var(--border); border-radius: 20px; padding: 24px; max-width: 500px; width: 100%; max-height: 80vh; overflow-y: auto; } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .modal-title { font-size: 20px; font-weight: 700; margin: 0; } .modal-close { background: none; border: none; color: var(--gray); font-size: 20px; cursor: pointer; padding: 4px; } .modal-close:hover { color: var(--white); } .pricing-option { background: var(--black-card); border: 2px solid var(--border); border-radius: 16px; padding: 20px; margin-bottom: 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; } .pricing-option:hover { border-color: var(--brand); transform: translateY(-2px); } .pricing-option.recommended { border-color: var(--success); position: relative; } .pricing-option.recommended::before { content: "RECOMMENDED"; position: absolute; top: -8px; left: 50%; transform: translateX(-50%); background: var(--success); color: var(--white); padding: 4px 8px; border-radius: 12px; font-size: 9px; font-weight: 700; } .pricing-title { font-size: 16px; font-weight: 600; margin-bottom: 6px; } .pricing-price { font-size: 24px; font-weight: 700; color: var(--success); margin-bottom: 8px; } .pricing-features { list-style: none; text-align: left; color: var(--gray-light); margin-bottom: 12px; } .pricing-features li { padding: 3px 0; display: flex; align-items: center; gap: 6px; font-size: 12px; } .pricing-features li::before { content: "✓"; color: var(--success); font-weight: bold; } .action-button { background: var(--gradient-brand); border: none; padding: 12px 12px; border-radius: 16px; color: var(--white); font-weight: 600; font-size: 16px; cursor: pointer; transition: all 0.2s ease; width: 70%; } [data-theme="light"] .action-button { color: var(--black); } .action-button:hover { transform: translateY(-2px); box-shadow: var(--shadow-glow); } /* Typing indicator */ .typing-indicator { display: inline-flex; align-items: center; gap: 2px; } .typing-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--brand); animation: typing 1.4s infinite; } .typing-dot:nth-child(2) { animation-delay: 0.2s; } .typing-dot:nth-child(3) { animation-delay: 0.4s; } @keyframes typing { 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; } 30% { transform: translateY(-10px); opacity: 1; } } /* Error States */ .error-message { background: rgba(239, 68, 68, 0.1); border: 1px solid var(--accent); border-radius: 8px; padding: 12px; margin: 8px 0; color: var(--accent); font-size: 14px; display: none; } .error-message.show { display: block; animation: errorSlide 0.3s ease-out; } @keyframes errorSlide { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } /* Mobile Optimizations */ @media (max-width: 768px) { .app { padding: 12px; } .header { flex-direction: column; align-items: stretch; text-align: center; margin-bottom: 20px; } .header-actions { justify-content: center; gap: 8px; } .model-grid { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 6px; } .persona-grid { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; } .chat-container { height: 400px; } .explainer h3 { font-size: 16px; } .explainer p { font-size: 13px; } .app.fullscreen .chat-container { height: 100vh; } } </style> </head> <body data-theme="light"> <!-- Toast Container --> <div class="toast-container" id="toastContainer"></div> <div class="app" id="app"> <!-- Fullscreen Toggle (Visible in fullscreen mode) --> <button class="fullscreen-toggle" id="fullscreenToggle"> <span id="fullscreenIcon">⛶</span> <span id="fullscreenText">Fullscreen</span> </button> <!-- Header --> <header class="header"> <div class="logo">BrutallyHonest.ai</div> <div class="header-actions"> <!-- Credits Display --> <div class="credits-container" id="creditsDisplay"> <div class="credits-progress"> <div class="credits-text"> <span class="credits-number" id="creditsAmount">10</span> / <span id="creditsMax">1000</span> credits </div> <div class="credits-bar"> <div class="credits-fill" id="creditsFill" style="width: 1%" ></div> </div> </div> <div style="font-size: 12px; color: var(--brand); font-weight: 600;" > Upgrade </div> </div> <!-- Theme Toggle --> <div class="theme-toggle" id="themeToggle"> <span id="themeIcon">☀️</span> <span id="themeText">Light</span> </div> </div> </header> <!-- Explainer --> <section class="explainer"> <h3>🔥 The AI That Has Transformed Thousands</h3> <p> Choose your AI personality below, pick a model that powers the conversation, and start chatting. <strong>Free models are unlimited.</strong> Premium models cost credits but deliver breakthrough insights. </p> </section> <!-- Error Display --> <div class="error-message" id="errorMessage"> <!-- Error content will be populated by JavaScript --> </div> <!-- Main Chat Interface --> <div class="chat-container"> <div class="chat-header"> <div class="chat-title"> <span id="currentPersonaEmoji">🔥</span> <span id="currentPersonaName">Loading...</span> </div> <div class="chat-controls"> <button class="fullscreen-toggle chat-control-btn" id="chatFullscreenBtn" > ⛶ </button> <button class="layout-toggle chat-control-btn" id="layoutToggle">📐</button> <button class="chat-control-btn" id="saveBtn">💾</button> <button class="chat-control-btn" id="clearBtn">🗑️</button> <button class="chat-control-btn" id="exportBtn">📤</button> </div> </div> <div class="chat-messages" id="chatMessages"> <div class="message bot"> <div class="message-header"> <div class="message-avatar">🔥</div> <span>Loading...</span> <span>•</span> <span>Just now</span> </div> <div class="message-bubble"> Welcome! Configuring your experience... <button class="copy-button" onclick="copyMessage(this)" data-text="Welcome! Configuring your experience..." > Copy </button> </div> </div> </div> <div class="chat-input-area"> <div class="chat-input-container"> <textarea class="chat-input" id="chatInput" placeholder="Type your message here..." rows="1" ></textarea> <button class="send-button" id="sendButton"> <span id="sendButtonText">Send</span> <span id="sendButtonIcon">→</span> </button> </div> </div> </div> <!-- Model Selection --> <div style="margin: 20px 0;" class="model-selection-container" id="modelSelectionContainer" > <h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);"> Select AI Model </h4> <div class="loading-placeholder" id="modelsLoadingPlaceholder"> <div class="loading-animation"></div> Loading models... </div> <div id="modelsContainer"></div> </div> <!-- Persona Selection --> <div style="margin: 20px 0;" class="persona-selection-container" id="personaSelectionContainer" > <h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);"> Choose Your AI Personality </h4> <div class="loading-placeholder" id="personasLoadingPlaceholder"> <div class="loading-animation"></div> Loading personalities... </div> <div class="persona-grid" id="personaGrid"></div> </div> </div> <!-- Credits Modal --> <div class="modal" id="creditsModal"> <div class="modal-content"> <div class="modal-header"> <h2 class="modal-title">💎 Get More Credits</h2> <button class="modal-close" id="closeCreditsModal">×</button> </div> <div class="pricing-option recommended"> <div class="pricing-title">Monthly Subscription</div> <div class="pricing-price"> $9<span style="font-size: 12px; color: var(--gray);">/month</span> </div> <ul class="pricing-features"> <li>500 credits per month</li> <li>Unlimited free models</li> <li>Access to all premium models</li> <li>Credits roll over (up to 1000)</li> <li>Priority support</li> </ul> <button class="action-button" onclick="purchaseMonthly()"> Subscribe Now ✨ </button> </div> <div class="pricing-option"> <div class="pricing-title">Pay As You Go</div> <div class="pricing-price">From $2.99</div> <ul class="pricing-features"> <li>100 credits for $2.99</li> <li>250 credits for $5.99</li> <li>500 credits for $9.99</li> <li>Credits never expire</li> </ul> <button class="action-button" onclick="purchaseCredits()"> Buy Credits 💰 </button> </div> </div> </div> <script> // Configuration - UPDATE THESE URLs const CONFIG = { mainWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/bha-mvp', configWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/config', toolRouterUrl: 'https://photobar.app.n8n.cloud/webhook/tool-router' } // Global state with enhanced persistence const state = { user: { credits: 10, maxCredits: 1000, userId: generateUserId() }, chat: { currentModel: null, currentModelCost: 0, currentPersona: null, messages: [], isTyping: false }, ui: { currentTheme: 'light', isFullscreen: false }, config: { personas: [], models: [], modelsByTier: { free: [], standard: [], premium: [] }, isLoaded: false } }; // Toast System function showToast(message, type = 'success', duration = 3000) { const container = document.getElementById('toastContainer'); const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateX(100%)'; setTimeout(() => { if (toast.parentNode) { container.removeChild(toast); } }, 300); }, duration); } // Enhanced Configuration Manager with Fixed Persistence class BHAConfigManager { constructor() { this.config = { personas: [], models: [], modelsByTier: { free: [], standard: [], premium: [] }, features: {} }; this.isLoaded = false; } async loadConfiguration() { try { console.log('🔄 Loading configuration from:', CONFIG.configWebhookUrl); const response = await fetch(CONFIG.configWebhookUrl); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } this.config = await response.json(); this.isLoaded = true; state.config = this.config; // Fixed: Add this line that was missing console.log('✅ Configuration loaded:', this.config); this.renderDynamicContent(); this.initializeDefaults(); } catch (error) { console.error('❌ Config load failed:', error); this.loadFallbackConfig(); } } loadFallbackConfig() { console.log('🔄 Loading fallback configuration...'); this.config = { personas: [ { id: 'QuickResonanceSpark_Persona_Haiku', name: 'Brutally Honest', emoji: '🔥', description: 'No BS, just raw truth', isPremium: false, gradientColor: 'linear-gradient(135deg, #EF4444, #F59E0B)' }, { id: 'ResonanceSession_Orchestrator_Opus', name: 'Deep Guide', emoji: '🧠', description: 'Profound insights & breakthrough clarity', isPremium: false, gradientColor: 'linear-gradient(135deg, #10B981, #06D6A0)' } ], models: [ { id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' }, { id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' } ], modelsByTier: { free: [ { id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' }, { id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' } ], standard: [], premium: [] }, features: { darkModeEnabled: true, creditsSystemEnabled: true } }; this.isLoaded = true; state.config = this.config; this.renderDynamicContent(); this.initializeDefaults(); } renderDynamicContent() { this.renderPersonas(); this.renderModels(); } renderPersonas() { const container = document.getElementById('personaGrid'); const loadingPlaceholder = document.getElementById('personasLoadingPlaceholder'); if (loadingPlaceholder) { loadingPlaceholder.style.display = 'none'; } container.innerHTML = this.config.personas.map(persona => ` <div class="persona-card ${persona.isPremium ? 'premium' : ''}" data-persona-id="${persona.id}" data-persona-name="${persona.name}" data-persona-emoji="${persona.emoji}"> <div class="persona-avatar" style="background: ${persona.gradientColor};"> ${persona.emoji} ${persona.isPremium ? '<div class="premium-badge">💎</div>' : ''} </div> <div class="persona-name">${persona.name}</div> <div class="persona-desc">${persona.description}</div> </div> `).join(''); // Add event listeners with proper data binding document.querySelectorAll('.persona-card').forEach(card => { card.addEventListener('click', () => this.selectPersona(card)); }); console.log('✅ Personas rendered:', this.config.personas.length); } renderModels() { const container = document.getElementById('modelsContainer'); const loadingPlaceholder = document.getElementById('modelsLoadingPlaceholder'); if (loadingPlaceholder) { loadingPlaceholder.style.display = 'none'; } const tiers = ['free', 'standard', 'premium']; const tierLabels = { 'free': 'Free Models', 'standard': 'Standard Models (5-15 credits)', 'premium': 'Premium Models (15+ credits)' }; container.innerHTML = tiers.map(tier => { const models = this.config.modelsByTier[tier] || []; if (models.length === 0) return ''; return ` <div class="model-section" data-tier="${tier}"> <div class="model-section-title">${tierLabels[tier]}</div> <div class="model-grid"> ${models.map(model => ` <div class="model-option" data-model-id="${model.id}" data-model-name="${model.name}" data-model-cost="${model.cost}"> <div class="model-name">${model.name}</div> <div class="model-cost ${model.cost === 0 ? 'free' : 'paid'}"> ${model.cost === 0 ? 'FREE' : `${model.cost} credits`} </div> </div> `).join('')} </div> </div> `; }).join(''); // Add event listeners with proper data binding document.querySelectorAll('.model-option').forEach(option => { option.addEventListener('click', () => this.selectModel(option)); }); console.log('✅ Models rendered:', this.config.models.length); } selectPersona(personaElement) { // Remove active from all document.querySelectorAll('.persona-card').forEach(el => el.classList.remove('active')); personaElement.classList.add('active'); // BULLETPROOF string extraction - FIX THE BUG HERE const personaId = String(personaElement.dataset.personaId || ''); const personaName = String(personaElement.dataset.personaName || ''); const personaEmoji = String(personaElement.dataset.personaEmoji || ''); // CRITICAL: Store as primitive string, not object reference state.chat.currentPersona = personaId; // <- THIS IS THE FIX // Update UI document.getElementById('currentPersonaEmoji').textContent = personaEmoji; document.getElementById('currentPersonaName').textContent = personaName; // Verification console.log('✅ Persona FIXED:', typeof state.chat.currentPersona, '=', state.chat.currentPersona); // Save to localStorage localStorage.setItem('bha_selectedPersona', personaId); } selectModel(modelElement) { // Remove active from all document.querySelectorAll('.model-option').forEach(el => el.classList.remove('active')); modelElement.classList.add('active'); // BULLETPROOF string extraction - FIX THE BUG HERE const modelId = String(modelElement.dataset.modelId || ''); const modelName = String(modelElement.dataset.modelName || ''); const modelCost = parseInt(modelElement.dataset.modelCost || '0'); // CRITICAL: Store as primitive string, not object reference state.chat.currentModel = modelId; // <- THIS IS THE FIX state.chat.currentModelCost = modelCost; // Verification console.log('✅ Model FIXED:', typeof state.chat.currentModel, '=', state.chat.currentModel); // Save to localStorage localStorage.setItem('bha_selectedModel', modelId); } initializeDefaults() { // Restore from localStorage or select first available const savedPersonaId = localStorage.getItem('bha_selectedPersona'); const savedModelId = localStorage.getItem('bha_selectedModel'); // Restore layout preference const savedLayout = localStorage.getItem('bha_flexboxMode') === 'true'; if (savedLayout) { document.querySelector('.chat-container').classList.add('flexbox'); } // Initialize persona if (this.config.personas.length > 0) { let targetPersona = null; if (savedPersonaId) { targetPersona = document.querySelector(`[data-persona-id="${savedPersonaId}"]`); } if (!targetPersona) { targetPersona = document.querySelector('.persona-card'); } if (targetPersona) { this.selectPersona(targetPersona); } } // Initialize model if (this.config.models.length > 0) { let targetModel = null; if (savedModelId) { targetModel = document.querySelector(`[data-model-id="${savedModelId}"]`); } if (!targetModel) { targetModel = document.querySelector('.model-option'); } if (targetModel) { this.selectModel(targetModel); } } // Update welcome message setTimeout(() => { this.updateWelcomeMessage(); }, 100); // Add this at the end of initializeDefaults() setTimeout(() => { // Emergency fallback if nothing is selected if (!state.chat.currentPersona) { const firstPersona = document.querySelector('.persona-card'); if (firstPersona) { console.log('🆘 Emergency persona selection'); this.selectPersona(firstPersona); } } if (!state.chat.currentModel) { const firstModel = document.querySelector('.model-option'); if (firstModel) { console.log('🆘 Emergency model selection'); this.selectModel(firstModel); } } }, 500); } updateWelcomeMessage() { const messagesContainer = document.getElementById('chatMessages'); const currentPersona = this.config.personas.find(p => p.id === state.chat.currentPersona); if (currentPersona) { messagesContainer.innerHTML = ` <div class="message bot"> <div class="message-header"> <div class="message-avatar">${currentPersona.emoji}</div> <span>${currentPersona.name}</span> <span>•</span> <span>Just now</span> </div> <div class="message-bubble"> I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today? <button class="copy-button" onclick="copyMessage(this)" data-text="I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today?">Copy</button> </div> </div> `; } } } // Utility functions function generateUserId() { const stored = localStorage.getItem('bha_user_id'); if (stored) return stored; const id = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); localStorage.setItem('bha_user_id', id); return id; } function showError(message) { const errorEl = document.getElementById('errorMessage'); errorEl.textContent = message; errorEl.classList.add('show'); setTimeout(() => { errorEl.classList.remove('show'); }, 5000); } // Fullscreen functionality function toggleFullscreen() { const app = document.getElementById('app'); const fullscreenIcon = document.getElementById('fullscreenIcon'); const fullscreenText = document.getElementById('fullscreenText'); state.ui.isFullscreen = !state.ui.isFullscreen; app.classList.toggle('fullscreen', state.ui.isFullscreen); if (state.ui.isFullscreen) { fullscreenIcon.textContent = '⛶'; fullscreenText.textContent = 'Exit'; } else { fullscreenIcon.textContent = '⛶'; fullscreenText.textContent = 'Fullscreen'; } // Save preference localStorage.setItem('bha_fullscreenMode', state.ui.isFullscreen.toString()); } // Layout toggle functionality function toggleLayout() { const chatContainer = document.querySelector('.chat-container'); const isFlexbox = chatContainer.classList.toggle('flexbox'); localStorage.setItem('bha_flexboxMode', isFlexbox.toString()); showToast(`Layout: ${isFlexbox ? 'Flexible' : 'Fixed'}`, 'success', 2000); console.log('Layout toggled to:', isFlexbox ? 'flexbox' : 'fixed'); } // Copy functionality function copyMessage(button) { const text = button.dataset.text; navigator.clipboard.writeText(text).then(() => { const originalText = button.textContent; button.textContent = 'Copied!'; button.classList.add('copied'); setTimeout(() => { button.textContent = originalText; button.classList.remove('copied'); }, 2000); showToast('Message copied to clipboard', 'success', 2000); }).catch(err => { console.error('Copy failed:', err); showToast('Failed to copy message', 'error', 3000); }); } // Theme system function toggleTheme() { const newTheme = state.ui.currentTheme === 'light' ? 'dark' : 'light'; state.ui.currentTheme = newTheme; document.body.dataset.theme = newTheme; const icon = document.getElementById('themeIcon'); const text = document.getElementById('themeText'); if (newTheme === 'light') { icon.textContent = '☀️'; text.textContent = 'Light'; } else { icon.textContent = '🌙'; text.textContent = 'Dark'; } localStorage.setItem('bha_selectedTheme', newTheme); } // Credits system function updateCreditsDisplay() { const creditsAmount = document.getElementById('creditsAmount'); const creditsMax = document.getElementById('creditsMax'); const creditsFill = document.getElementById('creditsFill'); creditsAmount.textContent = state.user.credits; creditsMax.textContent = state.user.maxCredits; const percentage = (state.user.credits / state.user.maxCredits) * 100; creditsFill.style.width = percentage + '%'; } function showCreditsModal() { document.getElementById('creditsModal').classList.add('active'); } function hideCreditsModal() { document.getElementById('creditsModal').classList.remove('active'); } function purchaseMonthly() { window.open(`https://buy.stripe.com/YOUR_MONTHLY_LINK?client_reference_id=${state.user.userId}`, '_blank'); } function purchaseCredits() { window.open(`https://buy.stripe.com/YOUR_CREDIT_PACK_LINK?client_reference_id=${state.user.userId}`, '_blank'); } // Chat functions with copy buttons function addMessage(content, isUser = false, persona = null) { const messagesContainer = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); messageDiv.className = `message ${isUser ? 'user' : 'bot'}`; const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); const sender = isUser ? 'You' : (persona || 'AI'); const avatar = isUser ? '👤' : (getPersonaEmoji(state.chat.currentPersona) || '🤖'); // Process markdown for bot messages const processedContent = isUser ? escapeHtml(content) : (typeof marked !== 'undefined' ? marked.parse(content) : content); const copyButton = isUser ? '' : `<button class="copy-button" onclick="copyMessage(this)" data-text="${escapeHtml(content)}">Copy</button>`; messageDiv.innerHTML = ` <div class="message-header"> <div class="message-avatar">${avatar}</div> <span>${sender}</span> <span>•</span> <span>${timestamp}</span> </div> <div class="message-bubble"> ${processedContent} ${copyButton} </div> `; messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; // Store in state state.chat.messages.push({ content: content, isUser: isUser, timestamp: Date.now(), persona: persona }); } function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function addTypingIndicator() { const messagesContainer = document.getElementById('chatMessages'); const typingDiv = document.createElement('div'); typingDiv.id = 'typingIndicator'; typingDiv.className = 'message bot'; const persona = getPersonaName(state.chat.currentPersona); const avatar = getPersonaEmoji(state.chat.currentPersona); typingDiv.innerHTML = ` <div class="message-header"> <div class="message-avatar">${avatar}</div> <span>${persona}</span> <span>•</span> <span>typing...</span> </div> <div class="message-bubble"> <div class="typing-indicator"> <div class="typing-dot"></div> <div class="typing-dot"></div> <div class="typing-dot"></div> </div> </div> `; messagesContainer.appendChild(typingDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; } function removeTypingIndicator() { const indicator = document.getElementById('typingIndicator'); if (indicator) { indicator.remove(); } } function getPersonaName(personaId) { if (!state.config.personas) return 'AI'; const persona = state.config.personas.find(p => p.id === personaId); return persona ? persona.name : 'AI'; } function getPersonaEmoji(personaId) { if (!state.config.personas) return '🤖'; const persona = state.config.personas.find(p => p.id === personaId); return persona ? persona.emoji : '🤖'; } function validateStateBeforeSend() { // Type checking and correction if (typeof state.chat.currentPersona !== 'string') { console.warn('🔧 Fixing persona type:', typeof state.chat.currentPersona); state.chat.currentPersona = String(state.chat.currentPersona || ''); } if (typeof state.chat.currentModel !== 'string') { console.warn('🔧 Fixing model type:', typeof state.chat.currentModel); state.chat.currentModel = String(state.chat.currentModel || ''); } // Log final verification console.log('🔍 Pre-send validation:'); console.log(' Persona:', typeof state.chat.currentPersona, '=', state.chat.currentPersona); console.log(' Model:', typeof state.chat.currentModel, '=', state.chat.currentModel); return { personaValid: state.chat.currentPersona && state.chat.currentPersona !== '', modelValid: state.chat.currentModel && state.chat.currentModel !== '' }; } // Enhanced send message with proper state validation async function sendMessage() { const input = document.getElementById('chatInput'); const message = input.value.trim(); if (!message || state.chat.isTyping) return; // Validate that we have selections if (!state.chat.currentPersona || !state.chat.currentModel) { showError('Please select a persona and model before sending a message.'); return; } // Check credits if (state.chat.currentModelCost > 0 && state.user.credits < state.chat.currentModelCost) { showError(`Insufficient credits! You need ${state.chat.currentModelCost} credits to use this model.`); showCreditsModal(); return; } // Add user message addMessage(message, true); // Clear input input.value = ''; input.style.height = 'auto'; // Show typing indicator addTypingIndicator(); state.chat.isTyping = true; // Disable send button const sendBtn = document.getElementById('sendButton'); sendBtn.disabled = true; document.getElementById('sendButtonText').textContent = 'Sending'; document.getElementById('sendButtonIcon').innerHTML = '<div class="loading-animation"></div>'; try { // Pre-send validation const validation = validateStateBeforeSend(); if (!validation.personaValid || !validation.modelValid) { showError('Selection validation failed. Please reselect your persona and model.'); return; } // BULLETPROOF payload construction const payload = { user_id: String(state.user.userId), bot_to_call: String(state.chat.currentPersona), full_prompt_for_llm: String(message), raw_user_situation: String(message), selected_model_for_openrouter: String(state.chat.currentModel), conversation_id: 'conv_' + Date.now(), timestamp: new Date().toISOString() }; // Final payload verification console.log('🚀 Final payload verification:'); Object.keys(payload).forEach(key => { console.log(` ${key}: ${typeof payload[key]} = ${payload[key]}`); }); // Send to webhook const response = await fetch(CONFIG.mainWebhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); console.log('✅ Response received:', data); let aiResponseText = "I'm having trouble formulating a response right now."; // Default let opoeratorInsightHTML = ""; // Assuming opoerator insight might come in a different structure or not at all with this simple response // --- CORRECTED EXTRACTION LOGIC --- if (data && typeof data.response === 'string') { // CHECK IF 'data' EXISTS AND 'data.response' IS A STRING aiResponseText = data.response; // If opoerator_insight_html is also sent at the root level by N8N, access it: // opoeratorInsightHTML = data.opoerator_insight_html || ""; } else if (data.upsell_message) { // Keep your upsell logic if N8N can send this structure too aiResponseText = `${data.upsell_message} ${data.analysis || ''}`; } // REMOVE or COMMENT OUT the old 'else if' conditions that look for 'bot_response' or 'final_response_payload' // if N8N is no longer sending those structures for this particular response. // else if (data.bot_response && data.bot_response.analysis) { ... } // else if (data.final_response_payload && data.final_response_payload.bot_response && data.final_response_payload.bot_response.analysis) { ... } // --- CRITICAL DEBUG LOG (keep this for a moment) --- console.log(">>>> FINAL aiResponseText to be displayed:", aiResponseText); console.log(">>>> Type of aiResponseText:", typeof aiResponseText); // --- END CRITICAL DEBUG LOG --- removeTypingIndicator(); addMessage(aiResponseText, false); // Persona name is handled by addMessage if (opoeratorInsightHTML) { // If you plan to send this addSystemMessage("✨ oPOErator's Insight: " + opoeratorInsightHTML); } // Update credits if provided if (data.credits_remaining !== undefined) { state.user.credits = data.credits_remaining; } else if (state.chat.currentModelCost > 0) { state.user.credits = Math.max(0, state.user.credits - state.chat.currentModelCost); } updateCreditsDisplay(); // Show tool indicator if tool was used if (data.tool_used) { console.log('🔧 Tool was used in this response'); showToast('Enhanced response generated', 'success', 2000); } } catch (error) { console.error('❌ Error sending message:', error); removeTypingIndicator(); showError('Failed to send message. Please check your connection and try again.'); } finally { state.chat.isTyping = false; sendBtn.disabled = false; document.getElementById('sendButtonText').textContent = 'Send'; document.getElementById('sendButtonIcon').textContent = '→'; } } // Chat controls function saveConversation() { if (state.chat.messages.length === 0) { showToast('No messages to save yet!', 'error'); return; } showToast('💾 Conversation saved! (Feature coming soon)', 'success'); } function clearChat() { if (confirm('Clear all messages? This cannot be undone!')) { state.chat.messages = []; if (configManager.isLoaded) { configManager.updateWelcomeMessage(); } showToast('Chat cleared', 'success'); } } function exportChat() { if (state.chat.messages.length === 0) { showToast('No messages to export yet!', 'error'); return; } let content = `BrutallyHonest.ai Chat Export\n`; content += `Exported: ${new Date().toLocaleString()}\n`; content += `Model: ${state.chat.currentModel}\n`; content += `Persona: ${getPersonaName(state.chat.currentPersona)}\n`; content += '='.repeat(50) + '\n\n'; state.chat.messages.forEach(msg => { const timestamp = new Date(msg.timestamp).toLocaleString(); const speaker = msg.isUser ? 'You' : (msg.persona || 'AI'); content += `[${timestamp}] ${speaker}: ${msg.content}\n\n`; }); const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `BHA_Chat_${new Date().toISOString().split('T')[0]}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showToast('Chat exported successfully', 'success'); } // Initialize configuration manager const configManager = new BHAConfigManager(); // Enhanced initialization async function init() { console.log("🔥 BrutallyHonest.ai Initializing..."); // Restore saved preferences const savedTheme = localStorage.getItem('bha_selectedTheme') || 'light'; const savedFullscreen = localStorage.getItem('bha_fullscreenMode') === 'true'; // Apply theme state.ui.currentTheme = savedTheme; document.body.dataset.theme = savedTheme; const icon = document.getElementById('themeIcon'); const text = document.getElementById('themeText'); if (savedTheme === 'dark') { icon.textContent = '🌙'; text.textContent = 'Dark'; } // Apply fullscreen if previously set if (savedFullscreen) { state.ui.isFullscreen = true; document.getElementById('app').classList.add('fullscreen'); document.getElementById('fullscreenIcon').textContent = '⛶'; document.getElementById('fullscreenText').textContent = 'Exit'; } // Set up enhanced event listeners document.getElementById('themeToggle').addEventListener('click', toggleTheme); document.getElementById('creditsDisplay').addEventListener('click', showCreditsModal); document.getElementById('closeCreditsModal').addEventListener('click', hideCreditsModal); document.getElementById('layoutToggle').addEventListener('click', toggleLayout); // Fullscreen toggles document.getElementById('fullscreenToggle').addEventListener('click', toggleFullscreen); document.getElementById('chatFullscreenBtn').addEventListener('click', toggleFullscreen); // Chat controls document.getElementById('saveBtn').addEventListener('click', saveConversation); document.getElementById('clearBtn').addEventListener('click', clearChat); document.getElementById('exportBtn').addEventListener('click', exportChat); // Send message document.getElementById('sendButton').addEventListener('click', sendMessage); document.getElementById('chatInput').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); // Auto-resize textarea document.getElementById('chatInput').addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 100) + 'px'; }); // Close modal when clicking outside window.addEventListener('click', (e) => { const modal = document.getElementById('creditsModal'); if (e.target === modal) { hideCreditsModal(); } }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { // Escape to exit fullscreen if (e.key === 'Escape' && state.ui.isFullscreen) { toggleFullscreen(); } // F11 for fullscreen toggle if (e.key === 'F11') { e.preventDefault(); toggleFullscreen(); } // Ctrl/Cmd + / for theme toggle if ((e.ctrlKey || e.metaKey) && e.key === '/') { e.preventDefault(); toggleTheme(); } }); // Initialize displays updateCreditsDisplay(); // Load configuration and render dynamic content await configManager.loadConfiguration(); console.log('✅ BrutallyHonest.ai initialization complete!'); } // Global functions for HTML onclick events window.purchaseMonthly = purchaseMonthly; window.purchaseCredits = purchaseCredits; window.copyMessage = copyMessage; // Initialize when ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } </script> </body> </html> ---
super-embed: <!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" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <title> BrutallyHonest.ai - Transform Your Bullsh*t Into Breakthroughs </title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <style> :root { /* Light Theme (Default) */ --black: #FFFFFF; --black-soft: #F8F9FA; --black-card: #F1F3F4; --border: #E8EAED; --border-light: #DADCE0; --white: #000000; --gray: #5F6368; --gray-light: #3C4043; --gray-dark: #80868B; --brand: #5D5CDE; --brand-light: #7C7CE8; --brand-dark: #4B4BC7; --accent: #EF4444; --success: #22C55E; --warning: #F59E0B; --gold: #FCD34D; --gradient-brand: linear-gradient(135deg, #5D5CDE 0%, #7C7CE8 100%); --gradient-fire: linear-gradient(135deg, #EF4444 0%, #F59E0B 100%); --gradient-success: linear-gradient(135deg, #22C55E 0%, #4ADE80 100%); --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.08); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.12), 0 1px 2px -1px rgb(0 0 0 / 0.12); --shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06); --shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05); --shadow-glow: 0 0 0 1px rgba(93, 92, 222, 0.3), 0 0 20px rgba(93, 92, 222, 0.2); } .app { /* ... other styles ... */ padding: 0 16px 16px 16px; /* Top padding is 0, others remain 16px */ /* Or more explicitly: */ /* padding-top: 0; */ /* padding-left: 16px; */ /* padding-right: 16px; */ /* padding-bottom: 16px; */ /* ... other styles ... */ } /* Dark Theme Override */ [data-theme="dark"] { --black: #0F0F0F; --black-soft: #1A1A1A; --black-card: #262626; --border: #404040; --border-light: #525252; --white: #FFFFFF; --gray: #A3A3A3; --gray-light: #D4D4D4; --gray-dark: #737373; --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); } * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; } html { position: static; height: auto; overflow: auto; } body { background: var(--black); color: var(--white); line-height: 1.6; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transition: background-color 0.5s ease, color 0.5s ease; min-height: 100vh; overflow-x: hidden; position: relative; } .app { max-width: 1200px; margin: 0 auto; padding: 16px; min-height: 100vh; position: relative; width: 100%; transition: all 0.3s ease; } /* Toast Container */ .toast-container { position: fixed; top: 20px; right: 20px; z-index: 10000; pointer-events: none; } .toast { background: var(--black-soft); border: 1px solid var(--border); border-radius: 8px; padding: 12px 16px; margin-bottom: 8px; box-shadow: var(--shadow-lg); backdrop-filter: blur(8px); animation: toastSlideIn 0.3s ease-out; pointer-events: all; font-size: 14px; max-width: 300px; } .toast.success { border-color: var(--success); background: rgba(34, 197, 94, 0.1); } .toast.error { border-color: var(--accent); background: rgba(239, 68, 68, 0.1); } @keyframes toastSlideIn { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } } /* FULLSCREEN MODE */ .app.fullscreen { position: fixed; top: 0; left: 0; right: 0; bottom: 0; max-width: none; padding: 0; z-index: 10000; background: var(--black); } .app.fullscreen .header, .app.fullscreen .explainer, .app.fullscreen .model-selection-container, .app.fullscreen .persona-selection-container, .app.fullscreen .premium-section { display: none; } .app.fullscreen .chat-container { height: 100vh; border-radius: 0; border: none; } .fullscreen-toggle { background: var(--black-card); border: 1px solid var(--border); padding: 6px 8px; border-radius: 6px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 12px; } .fullscreen-toggle:hover { border-color: var(--brand); color: var(--brand); } .app.fullscreen .fullscreen-toggle { position: absolute; top: 16px; right: 16px; z-index: 10001; background: var(--black-soft); border: 1px solid var(--border); } /* COPY FUNCTIONALITY */ .message.bot .message-bubble { position: relative; } .message.bot:hover .copy-button { opacity: 1; } .copy-button { position: absolute; top: 8px; right: 8px; background: var(--black-card); border: 1px solid var(--border); border-radius: 4px; padding: 4px 6px; font-size: 10px; cursor: pointer; opacity: 0; transition: all 0.2s ease; color: var(--gray); font-weight: 500; } .copy-button:hover { background: var(--brand); color: var(--white); border-color: var(--brand); } .copy-button.copied { background: var(--success); color: var(--white); border-color: var(--success); } /* Credit System */ .credits-container { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; display: flex; align-items: center; gap: 12px; cursor: pointer; transition: all 0.2s ease; } .credits-container:hover { border-color: var(--brand); transform: translateY(-1px); } .credits-progress { flex: 1; min-width: 80px; } .credits-bar { width: 100%; height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; margin-top: 4px; } .credits-fill { height: 100%; background: var(--gradient-brand); border-radius: 2px; transition: width 0.5s ease; } .credits-text { font-size: 12px; color: var(--gray); } .credits-number { font-weight: 700; color: var(--brand); } /* Dynamic Content Loading */ .loading-placeholder { background: var(--black-card); border-radius: 8px; padding: 16px; text-align: center; color: var(--gray); font-size: 14px; } .loading-animation { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--border); border-radius: 50%; border-top-color: var(--brand); animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Premium Indicators */ .premium-badge { position: absolute; top: -4px; right: -4px; background: var(--gradient-brand); color: var(--white); border-radius: 50%; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; } /* Model Organization */ .model-section { margin-bottom: 16px; } .model-section-title { font-size: 10px; font-weight: 600; color: var(--gray); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; padding-left: 8px; } .model-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; } .model-option { background: var(--black-card); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 12px; text-align: center; position: relative; } .model-option:hover { border-color: var(--brand); transform: translateY(-1px); } .model-option.active { background: rgba(93, 92, 222, 0.1); border-color: var(--brand); box-shadow: var(--shadow-glow); } .model-name { font-size: 16px; font-weight: 600; margin-bottom: 2px; } .model-cost { font-size: 14px; text-transform: uppercase; opacity: 0.8; } .model-cost.free { color: var(--success); } .model-cost.paid { color: var(--warning); } /* Persona Cards */ .persona-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; } .persona-card { background: var(--black-card); border: 2px solid var(--border); border-radius: 12px; padding: 16px 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; position: relative; } .persona-card:hover { border-color: var(--brand); transform: translateY(-2px); } .persona-card.active { border-color: var(--brand); background: rgba(93, 92, 222, 0.05); box-shadow: var(--shadow-glow); } .persona-card.premium { border-color: var(--gold); } .persona-avatar { width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; color: var(--white); position: relative; } .persona-name { font-weight: 600; font-size: 18px; margin-bottom: 4px; } .persona-desc { font-size: 13px; color: var(--gray); line-height: 1.3; } /* Chat Interface */ .chat-container { background: var(--black-soft); border: 3px solid var(--border); border-radius: 20px; height: 500px; display: flex; flex-direction: column; overflow: hidden; position: relative; } .chat-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; background: var(--black-card); } .chat-title { font-weight: 600; display: flex; align-items: center; gap: 8px; font-size: 14px; flex: 1; } .chat-controls { display: flex; gap: 6px; } .chat-control-btn { background: transparent; border: 1px solid var(--border); padding: 6px 8px; border-radius: 6px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 12px; } .chat-control-btn:hover { border-color: var(--brand); color: var(--brand); } .chat-container.flexbox { height: 70vh; max-height: 800px; min-height: 400px; } .layout-toggle { background: var(--black-card); border: 1px solid var(--border); padding: 6px 8px; border-radius: 6px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 12px; } .chat-messages { flex: 1; overflow-y: auto; padding: 16px; scroll-behavior: smooth; } .message { margin-bottom: 16px; animation: slideIn 0.3s ease-out; } @keyframes slideIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } .message-header { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; font-size: 12px; color: var(--gray); } .message-avatar { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 600; } .message.user .message-avatar { background: var(--gradient-brand); color: var(--white); } [data-theme="light"] .message.user .message-avatar { color: var(--black); } .message.bot .message-avatar { background: var(--gradient-success); color: var(--white); } [data-theme="light"] .message.bot .message-avatar { color: var(--black); } .message-bubble { padding: 12px 16px; border-radius: 18px; font-size: 14px; line-height: 1.5; max-width: 85%; word-wrap: break-word; } .message.user .message-bubble { margin-left: auto; background: var(--gradient-brand); color: var(--white); } [data-theme="light"] .message.user .message-bubble { color: var(--black); } .message.bot .message-bubble { background: linear-gradient(135deg, var(--black-card) 0%, rgba(93, 92, 222, 0.05) 100%); color: var(--gray-light); border: 1px solid var(--border); padding-right: 40px; /* Space for copy button */ } .message.system .message-bubble { background: rgba(93, 92, 222, 0.1); border: 1px solid var(--brand); color: var(--brand); font-size: 12px; padding: 8px 12px; font-style: italic; text-align: center; max-width: 100%; } .chat-input-area { padding: 16px; border-top: 1px solid var(--border); background: var(--black-card); flex-shrink: 0; } .chat-input-container { display: flex; gap: 8px; align-items: flex-end; } .chat-input { flex: 1; background: var(--black); border: 1px solid var(--border); border-radius: 12px; padding: 12px; font-size: 16px; color: var(--white); resize: none; min-height: 40px; max-height: 100px; transition: border-color 0.2s ease; font-family: inherit; } .chat-input:focus { outline: none; border-color: var(--brand); } .chat-input::placeholder { color: var(--gray-dark); } .send-button { background: var(--gradient-brand); border: none; padding: 12px 16px; border-radius: 12px; color: var(--white); font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; gap: 6px; min-height: 40px; } [data-theme="light"] .send-button { color: var(--black); } .send-button:hover:not(:disabled) { transform: translateY(-1px); } .send-button:disabled { opacity: 0.5; cursor: not-allowed; } /* Header */ .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px; flex-wrap: wrap; gap: 16px; } .logo { font-size: 20px; font-weight: 700; background: var(--gradient-brand); -webkit-background-clip: text; -webkit-text-fill-color: transparent; flex-shrink: 0; } .header-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } .theme-toggle { background: var(--black-card); border: 1px solid var(--border); border-radius: 50px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 14px; display: flex; align-items: center; gap: 8px; white-space: nowrap; flex-shrink: 0; } .theme-toggle:hover { border-color: var(--brand); } /* Explainer Section */ .explainer { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 24px; text-align: center; } .explainer h1 { font-size: 20px; font-weight: 600; color: var(--brand); margin-bottom: 8px; } .explainer p { font-size: 14px; color: var(--gray-light); line-height: 1.5; } /* Credits Modal */ .modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 9000; opacity: 0; visibility: hidden; transition: all 0.3s ease; padding: 16px; } .modal.active { opacity: 1; visibility: visible; } .modal-content { background: var(--black-soft); border: 1px solid var(--border); border-radius: 20px; padding: 24px; max-width: 500px; width: 100%; max-height: 80vh; overflow-y: auto; } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .modal-title { font-size: 20px; font-weight: 700; margin: 0; } .modal-close { background: none; border: none; color: var(--gray); font-size: 20px; cursor: pointer; padding: 4px; } .modal-close:hover { color: var(--white); } .pricing-option { background: var(--black-card); border: 2px solid var(--border); border-radius: 16px; padding: 20px; margin-bottom: 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; } .pricing-option:hover { border-color: var(--brand); transform: translateY(-2px); } .pricing-option.recommended { border-color: var(--success); position: relative; } .pricing-option.recommended::before { content: "RECOMMENDED"; position: absolute; top: -8px; left: 50%; transform: translateX(-50%); background: var(--success); color: var(--white); padding: 4px 8px; border-radius: 12px; font-size: 9px; font-weight: 700; } .pricing-title { font-size: 16px; font-weight: 600; margin-bottom: 6px; } .pricing-price { font-size: 24px; font-weight: 700; color: var(--success); margin-bottom: 8px; } .pricing-features { list-style: none; text-align: left; color: var(--gray-light); margin-bottom: 12px; } .pricing-features li { padding: 3px 0; display: flex; align-items: center; gap: 6px; font-size: 12px; } .pricing-features li::before { content: "✓"; color: var(--success); font-weight: bold; } .action-button { background: var(--gradient-brand); border: none; padding: 12px 12px; border-radius: 16px; color: var(--white); font-weight: 600; font-size: 16px; cursor: pointer; transition: all 0.2s ease; width: 70%; } [data-theme="light"] .action-button { color: var(--black); } .action-button:hover { transform: translateY(-2px); box-shadow: var(--shadow-glow); } /* Typing indicator */ .typing-indicator { display: inline-flex; align-items: center; gap: 2px; } .typing-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--brand); animation: typing 1.4s infinite; } .typing-dot:nth-child(2) { animation-delay: 0.2s; } .typing-dot:nth-child(3) { animation-delay: 0.4s; } @keyframes typing { 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; } 30% { transform: translateY(-10px); opacity: 1; } } /* Error States */ .error-message { background: rgba(239, 68, 68, 0.1); border: 1px solid var(--accent); border-radius: 8px; padding: 12px; margin: 8px 0; color: var(--accent); font-size: 14px; display: none; } .error-message.show { display: block; animation: errorSlide 0.3s ease-out; } @keyframes errorSlide { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } /* NEW: Floating Controls for Fullscreen */ .floating-controls { position: fixed; top: 20px; right: 20px; z-index: 10001; display: none; flex-direction: column; gap: 8px; } .app.fullscreen .floating-controls { display: flex; } .floating-control { background: var(--black-soft); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 12px; backdrop-filter: blur(8px); } .floating-control:hover { border-color: var(--brand); color: var(--brand); } .quick-switcher { position: fixed; top: 70px; right: 20px; z-index: 10000; background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 16px; display: none; backdrop-filter: blur(8px); min-width: 280px; } .quick-switcher.active { display: block; } .quick-switcher h3 { font-size: 14px; font-weight: 600; margin-bottom: 12px; color: var(--gray-light); } .quick-select { width: 100%; background: var(--black); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; color: var(--white); font-size: 14px; margin-bottom: 12px; } .quick-select:focus { outline: none; border-color: var(--brand); } /* NEW: Premium Features Section */ .premium-section { background: linear-gradient(135deg, rgba(252, 211, 77, 0.1) 0%, rgba(245, 158, 11, 0.05) 100%); border: 1px solid var(--gold); border-radius: 12px; padding: 20px; margin: 20px 0; position: relative; display: none; } .premium-section.active { display: block; } .premium-badge-section { position: absolute; top: -8px; left: 16px; background: var(--gold); color: var(--black); padding: 4px 12px; border-radius: 12px; font-size: 10px; font-weight: 700; text-transform: uppercase; } .create-persona-form { display: none; background: var(--black-card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-top: 16px; } .create-persona-form.active { display: block; } .form-group { margin-bottom: 16px; } .form-label { display: block; font-size: 14px; font-weight: 600; margin-bottom: 6px; color: var(--gray-light); } .form-input, .form-textarea { width: 100%; background: var(--black); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; color: var(--white); font-size: 14px; font-family: inherit; } .form-textarea { min-height: 80px; resize: vertical; } .form-input:focus, .form-textarea:focus { outline: none; border-color: var(--brand); } .suggestions-panel { background: var(--black-card); border: 1px solid var(--border); border-radius: 12px; padding: 16px; margin-top: 16px; display: none; } .suggestions-panel.active { display: block; } .suggestion-item { background: var(--black-soft); border: 1px solid var(--border); border-radius: 8px; padding: 12px; margin-bottom: 8px; cursor: pointer; transition: all 0.2s ease; } .suggestion-item:hover { border-color: var(--brand); background: rgba(93, 92, 222, 0.05); } .suggestion-text { font-size: 14px; line-height: 1.4; } .suggestion-meta { font-size: 12px; color: var(--gray); margin-top: 4px; } /* Mobile Optimizations */ @media (max-width: 768px) { .app { padding: 12px; } .header { flex-direction: column; align-items: stretch; text-align: center; margin-bottom: 20px; } .header-actions { justify-content: center; gap: 8px; } .model-grid { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 6px; } .persona-grid { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; } .chat-container { height: 400px; } .explainer h3 { font-size: 16px; } .explainer p { font-size: 13px; } .app.fullscreen .chat-container { height: 100vh; } .quick-switcher { top: 60px; right: 10px; left: 10px; min-width: auto; } } </style> </head> <body data-theme="light"> <!-- Toast Container --> <div class="toast-container" id="toastContainer"></div> <!-- NEW: Floating Controls (for fullscreen) --> <div class="floating-controls"> <div class="floating-control" id="exitFullscreen">⛶ Exit</div> <div class="floating-control" id="switcherToggle">🔄 Switch</div> </div> <!-- NEW: Quick Switcher (for fullscreen) --> <div class="quick-switcher" id="quickSwitcher"> <h3>Quick Switch</h3> <select class="quick-select" id="quickPersonaSelect"> <option>Loading personas...</option> </select> <select class="quick-select" id="quickModelSelect"> <option>Loading models...</option> </select> <button class="action-button" style="width: 100%; font-size: 12px;" onclick="applyQuickSwitch()">Apply</button> </div> <div class="app" id="app"> <!-- Fullscreen Toggle (Visible in fullscreen mode) --> <button class="fullscreen-toggle" id="fullscreenToggle"> <span id="fullscreenIcon">⛶</span> <span id="fullscreenText">Fullscreen</span> </button> <!-- Header --> <header class="header"> <div class="logo">BrutallyHonest.ai</div> <div class="header-actions"> <!-- Credits Display --> <div class="credits-container" id="creditsDisplay"> <div class="credits-progress"> <div class="credits-text"> <span class="credits-number" id="creditsAmount">10</span> / <span id="creditsMax">1000</span> credits </div> <div class="credits-bar"> <div class="credits-fill" id="creditsFill" style="width: 1%" ></div> </div> </div> <div style="font-size: 12px; color: var(--brand); font-weight: 600;" > Upgrade </div> </div> <!-- Theme Toggle --> <div class="theme-toggle" id="themeToggle"> <span id="themeIcon">☀️</span> <span id="themeText">Light</span> </div> </div> </header> <!-- Explainer --> <section class="explainer"> <h3>🔥 The AI That Has Transformed Thousands</h3> <p> Choose your AI personality below, pick a model that powers the conversation, and start chatting. <strong>Free models are unlimited.</strong> Premium models cost credits but deliver breakthrough insights. </p> </section> <!-- Error Display --> <div class="error-message" id="errorMessage"> <!-- Error content will be populated by JavaScript --> </div> <!-- Main Chat Interface --> <div class="chat-container"> <div class="chat-header"> <div class="chat-title"> <span id="currentPersonaEmoji">🔥</span> <span id="currentPersonaName">Loading...</span> <span style="font-size: 12px; color: var(--gray);" id="currentModelInfo"></span> </div> <div class="chat-controls"> <button class="fullscreen-toggle chat-control-btn" id="chatFullscreenBtn" > ⛶ </button> <button class="chat-control-btn" id="newChatBtn">🆕</button> <button class="layout-toggle chat-control-btn" id="layoutToggle">📐</button> <button class="chat-control-btn" id="saveBtn">💾</button> <button class="chat-control-btn" id="clearBtn">🗑️</button> <button class="chat-control-btn" id="exportBtn">📤</button> </div> </div> <div class="chat-messages" id="chatMessages"> <div class="message bot"> <div class="message-header"> <div class="message-avatar">🔥</div> <span>Loading...</span> <span>•</span> <span>Just now</span> </div> <div class="message-bubble"> Welcome! Configuring your experience... <button class="copy-button" onclick="copyMessage(this)" data-text="Welcome! Configuring your experience..." > Copy </button> </div> </div> </div> <div class="chat-input-area"> <div class="chat-input-container"> <textarea class="chat-input" id="chatInput" placeholder="Type your message here..." rows="1" ></textarea> <button class="send-button" id="sendButton"> <span id="sendButtonText">Send</span> <span id="sendButtonIcon">→</span> </button> </div> </div> </div> <!-- Model Selection --> <div style="margin: 20px 0;" class="model-selection-container" id="modelSelectionContainer" > <h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);"> Select AI Model </h4> <div class="loading-placeholder" id="modelsLoadingPlaceholder"> <div class="loading-animation"></div> Loading models... </div> <div id="modelsContainer"></div> </div> <!-- Persona Selection --> <div style="margin: 20px 0;" class="persona-selection-container" id="personaSelectionContainer" > <h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);"> Choose Your AI Personality </h4> <div class="loading-placeholder" id="personasLoadingPlaceholder"> <div class="loading-animation"></div> Loading personalities... </div> <div class="persona-grid" id="personaGrid"></div> </div> <!-- NEW: Premium Features Section --> <div class="premium-section" id="premiumSection"> <div class="premium-badge-section">PREMIUM</div> <h3 style="font-size: 18px; font-weight: 600; margin-bottom: 16px; color: var(--gold);"> Premium Features </h3> <!-- Create Custom Persona --> <div style="margin-bottom: 20px;"> <button class="action-button" style="width: auto; font-size: 14px;" onclick="toggleCreatePersona()"> ✨ Create Custom Persona </button> <div class="create-persona-form" id="createPersonaForm"> <div class="form-group"> <label class="form-label">Persona Name</label> <input type="text" class="form-input" id="customPersonaName" placeholder="e.g., My Business Coach"> </div> <div class="form-group"> <label class="form-label">Emoji</label> <input type="text" class="form-input" id="customPersonaEmoji" placeholder="🚀" maxlength="2"> </div> <div class="form-group"> <label class="form-label">Description</label> <input type="text" class="form-input" id="customPersonaDesc" placeholder="Helps with strategic business decisions"> </div> <div class="form-group"> <label class="form-label">System Prompt</label> <textarea class="form-textarea" id="customPersonaPrompt" placeholder="You are a strategic business advisor who helps entrepreneurs make data-driven decisions..."></textarea> </div> <button class="action-button" style="width: 100%; font-size: 14px;" onclick="saveCustomPersona()"> Save Persona </button> </div> </div> <!-- AI-Powered Suggestions --> <div> <button class="action-button" style="width: auto; font-size: 14px;" onclick="getAISuggestions()"> 🧠 Get AI Suggestions </button> <p style="font-size: 12px; color: var(--gray); margin-top: 4px;"> AI analyzes your conversation patterns to suggest personalized prompts </p> <div class="suggestions-panel" id="suggestionsPanel"> <h4 style="font-size: 14px; font-weight: 600; margin-bottom: 12px;"> Suggested for you: </h4> <div id="suggestionsList"></div> </div> </div> </div> </div> <!-- Credits Modal - Updated with Tiers --> <div class="modal" id="creditsModal"> <div class="modal-content"> <div class="modal-header"> <h2 class="modal-title">💎 Upgrade Your Experience</h2> <button class="modal-close" id="closeCreditsModal">×</button> </div> <div class="pricing-option recommended"> <div class="pricing-title">Premium Monthly</div> <div class="pricing-price"> $15<span style="font-size: 12px; color: var(--gray);">/month</span> </div> <ul class="pricing-features"> <li>500 credits per month</li> <li>Access to all premium models</li> <li>Create custom personas</li> <li>AI-powered suggestions</li> <li>Priority support</li> </ul> <button class="action-button" onclick="purchaseSubscription('premium')"> Subscribe Now ✨ </button> </div> <div class="pricing-option"> <div class="pricing-title">Elite Monthly</div> <div class="pricing-price">$39<span style="font-size: 12px; color: var(--gray);">/month</span></div> <ul class="pricing-features"> <li>1000 credits per month</li> <li>Create workflow chains</li> <li>Advanced analytics</li> <li>Custom integrations</li> </ul> <button class="action-button" onclick="purchaseSubscription('elite')"> Upgrade to Elite 🚀 </button> </div> <div class="pricing-option"> <div class="pricing-title">Pay As You Go</div> <div class="pricing-price">From $2.99</div> <ul class="pricing-features"> <li>100 credits for $2.99</li> <li>250 credits for $5.99</li> <li>500 credits for $9.99</li> <li>Credits never expire</li> </ul> <button class="action-button" onclick="purchaseCredits()"> Buy Credits 💰 </button> </div> </div> </div> <script> // Configuration - UPDATE THESE URLs const CONFIG = { mainWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/bha-mvp', configWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/config', toolRouterUrl: 'https://photobar.app.n8n.cloud/webhook/tool-router', suggestionsWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/ai-suggestions', // stripePublishableKey: 'pk_live_xxx', // Add when ready // apiKey: 'xxx' // Add when implementing security } // Global state with enhanced persistence const state = { user: { credits: 10, maxCredits: 1000, userId: generateUserId(), tier: 'Free' // NEW: Track user tier }, chat: { currentModel: null, currentModelCost: 0, currentModelName: '', currentPersona: null, conversationId: null, // NEW: Track conversation ID messages: [], isTyping: false }, ui: { currentTheme: 'light', isFullscreen: false }, config: { personas: [], models: [], customPersonas: [], // NEW: Store custom personas modelsByTier: { free: [], standard: [], premium: [] }, isLoaded: false } }; // Toast System function showToast(message, type = 'success', duration = 3000) { const container = document.getElementById('toastContainer'); const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateX(100%)'; setTimeout(() => { if (toast.parentNode) { container.removeChild(toast); } }, 300); }, duration); } // Enhanced Configuration Manager with Fixed Persistence class BHAConfigManager { constructor() { this.config = { personas: [], models: [], modelsByTier: { free: [], standard: [], premium: [] }, features: {} }; this.isLoaded = false; } async loadConfiguration() { try { console.log('🔄 Loading configuration from:', CONFIG.configWebhookUrl); const response = await fetch(CONFIG.configWebhookUrl); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } this.config = await response.json(); this.isLoaded = true; state.config = this.config; // Fixed: Add this line that was missing console.log('✅ Configuration loaded:', this.config); this.renderDynamicContent(); this.initializeDefaults(); } catch (error) { console.error('❌ Config load failed:', error); this.loadFallbackConfig(); } } loadFallbackConfig() { console.log('🔄 Loading fallback configuration...'); this.config = { personas: [ { id: 'QuickResonanceSpark_Persona_Haiku', name: 'Brutally Honest', emoji: '🔥', description: 'No BS, just raw truth', isPremium: false, requiredTier: 'Free', gradientColor: 'linear-gradient(135deg, #EF4444, #F59E0B)' }, { id: 'ResonanceSession_Orchestrator_Opus', name: 'Deep Guide', emoji: '🧠', description: 'Profound insights & breakthrough clarity', isPremium: false, requiredTier: 'Premium', gradientColor: 'linear-gradient(135deg, #10B981, #06D6A0)' } ], models: [ { id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' }, { id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' }, { id: 'openai/gpt-4', name: 'GPT-4', cost: 5, tier: 'Standard', provider: 'OpenAI' }, { id: 'anthropic/claude-3-opus', name: 'Claude 3 Opus', cost: 15, tier: 'Premium', provider: 'Anthropic' } ], modelsByTier: { free: [ { id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' }, { id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' } ], standard: [ { id: 'openai/gpt-4', name: 'GPT-4', cost: 5, tier: 'Standard', provider: 'OpenAI' } ], premium: [ { id: 'anthropic/claude-3-opus', name: 'Claude 3 Opus', cost: 15, tier: 'Premium', provider: 'Anthropic' } ] }, features: { darkModeEnabled: true, creditsSystemEnabled: true } }; this.isLoaded = true; state.config = this.config; this.renderDynamicContent(); this.initializeDefaults(); } renderDynamicContent() { this.renderPersonas(); this.renderModels(); this.updatePremiumSection(); // NEW this.populateQuickSwitcher(); // NEW } renderPersonas() { const container = document.getElementById('personaGrid'); const loadingPlaceholder = document.getElementById('personasLoadingPlaceholder'); if (loadingPlaceholder) { loadingPlaceholder.style.display = 'none'; } // Combine regular personas with custom personas const allPersonas = [...this.config.personas, ...state.config.customPersonas]; container.innerHTML = allPersonas.map(persona => { const tierBadge = persona.requiredTier && persona.requiredTier !== 'Free' ? '<div class="premium-badge">💎</div>' : ''; return ` <div class="persona-card ${persona.requiredTier !== 'Free' ? 'premium' : ''}" data-persona-id="${persona.id}" data-persona-name="${persona.name}" data-persona-emoji="${persona.emoji}" data-required-tier="${persona.requiredTier || 'Free'}"> <div class="persona-avatar" style="background: ${persona.gradientColor};"> ${persona.emoji} ${tierBadge} </div> <div class="persona-name">${persona.name}</div> <div class="persona-desc">${persona.description}</div> </div> `; }).join(''); document.querySelectorAll('.persona-card').forEach(card => { card.addEventListener('click', () => this.selectPersona(card)); }); console.log('✅ Personas rendered:', allPersonas.length); } renderModels() { const container = document.getElementById('modelsContainer'); const loadingPlaceholder = document.getElementById('modelsLoadingPlaceholder'); if (loadingPlaceholder) { loadingPlaceholder.style.display = 'none'; } const tiers = ['free', 'standard', 'premium']; const tierLabels = { 'free': 'Free Models', 'standard': 'Standard Models (5-15 credits)', 'premium': 'Premium Models (15+ credits)' }; container.innerHTML = tiers.map(tier => { const models = this.config.modelsByTier[tier] || []; if (models.length === 0) return ''; return ` <div class="model-section" data-tier="${tier}"> <div class="model-section-title">${tierLabels[tier]}</div> <div class="model-grid"> ${models.map(model => ` <div class="model-option" data-model-id="${model.id}" data-model-name="${model.name}" data-model-cost="${model.cost}" data-model-tier="${model.tier}"> <div class="model-name">${model.name}</div> <div class="model-cost ${model.cost === 0 ? 'free' : 'paid'}"> ${model.cost === 0 ? 'FREE' : `${model.cost} credits`} </div> </div> `).join('')} </div> </div> `; }).join(''); // Add event listeners with proper data binding document.querySelectorAll('.model-option').forEach(option => { option.addEventListener('click', () => this.selectModel(option)); }); console.log('✅ Models rendered:', this.config.models.length); } selectPersona(personaElement) { // Check tier access const requiredTier = personaElement.dataset.requiredTier || 'Free'; const userTier = state.user.tier; if (!this.checkTierAccess(userTier, requiredTier)) { showError(`This persona requires ${requiredTier} tier. Please upgrade to access.`); showCreditsModal(); return; } // Remove active from all document.querySelectorAll('.persona-card').forEach(el => el.classList.remove('active')); personaElement.classList.add('active'); // BULLETPROOF string extraction - FIX THE BUG HERE const personaId = String(personaElement.dataset.personaId || ''); const personaName = String(personaElement.dataset.personaName || ''); const personaEmoji = String(personaElement.dataset.personaEmoji || ''); // CRITICAL: Store as primitive string, not object reference state.chat.currentPersona = personaId; // <- THIS IS THE FIX // Update UI document.getElementById('currentPersonaEmoji').textContent = personaEmoji; document.getElementById('currentPersonaName').textContent = personaName; // Verification console.log('✅ Persona FIXED:', typeof state.chat.currentPersona, '=', state.chat.currentPersona); // Save to localStorage localStorage.setItem('bha_selectedPersona', personaId); } selectModel(modelElement) { // Check tier access const modelTier = modelElement.dataset.modelTier || 'Free'; const userTier = state.user.tier; if (!this.checkTierAccess(userTier, modelTier)) { showError(`This model requires ${modelTier} tier. Please upgrade to access.`); showCreditsModal(); return; } // Remove active from all document.querySelectorAll('.model-option').forEach(el => el.classList.remove('active')); modelElement.classList.add('active'); // BULLETPROOF string extraction - FIX THE BUG HERE const modelId = String(modelElement.dataset.modelId || ''); const modelName = String(modelElement.dataset.modelName || ''); const modelCost = parseInt(modelElement.dataset.modelCost || '0'); // CRITICAL: Store as primitive string, not object reference state.chat.currentModel = modelId; // <- THIS IS THE FIX state.chat.currentModelCost = modelCost; state.chat.currentModelName = modelName; // Update UI document.getElementById('currentModelInfo').textContent = `(${modelName})`; // Verification console.log('✅ Model FIXED:', typeof state.chat.currentModel, '=', state.chat.currentModel); // Save to localStorage localStorage.setItem('bha_selectedModel', modelId); } checkTierAccess(userTier, requiredTier) { const tierHierarchy = ['Free', 'Premium', 'Elite', 'Legend']; const userLevel = tierHierarchy.indexOf(userTier); const requiredLevel = tierHierarchy.indexOf(requiredTier); return userLevel >= requiredLevel; } updatePremiumSection() { const userTier = state.user.tier; const premiumSection = document.getElementById('premiumSection'); if (userTier !== 'Free') { premiumSection.classList.add('active'); } } populateQuickSwitcher() { const personaSelect = document.getElementById('quickPersonaSelect'); const modelSelect = document.getElementById('quickModelSelect'); // Populate personas const allPersonas = [...this.config.personas, ...state.config.customPersonas]; personaSelect.innerHTML = allPersonas.map(persona => `<option value="${persona.id}">${persona.emoji} ${persona.name}</option>` ).join(''); // Populate models const allModels = this.config.models || []; modelSelect.innerHTML = allModels.map(model => `<option value="${model.id}">${model.name}</option>` ).join(''); } initializeDefaults() { // Restore from localStorage or select first available const savedPersonaId = localStorage.getItem('bha_selectedPersona'); const savedModelId = localStorage.getItem('bha_selectedModel'); const savedConvId = localStorage.getItem('bha_conversation_id'); // Restore conversation ID or create new state.chat.conversationId = savedConvId || newConversationId(); // Restore layout preference const savedLayout = localStorage.getItem('bha_flexboxMode') === 'true'; if (savedLayout) { document.querySelector('.chat-container').classList.add('flexbox'); } // Initialize persona const allPersonas = [...this.config.personas, ...state.config.customPersonas]; if (allPersonas.length > 0) { let targetPersona = null; if (savedPersonaId) { targetPersona = document.querySelector(`[data-persona-id="${savedPersonaId}"]`); } if (!targetPersona) { targetPersona = document.querySelector('.persona-card'); } if (targetPersona) { this.selectPersona(targetPersona); } } // Initialize model if (this.config.models.length > 0) { let targetModel = null; if (savedModelId) { targetModel = document.querySelector(`[data-model-id="${savedModelId}"]`); } if (!targetModel) { targetModel = document.querySelector('.model-option'); } if (targetModel) { this.selectModel(targetModel); } } // Update welcome message setTimeout(() => { this.updateWelcomeMessage(); }, 100); // Add this at the end of initializeDefaults() setTimeout(() => { // Emergency fallback if nothing is selected if (!state.chat.currentPersona) { const firstPersona = document.querySelector('.persona-card'); if (firstPersona) { console.log('🆘 Emergency persona selection'); this.selectPersona(firstPersona); } } if (!state.chat.currentModel) { const firstModel = document.querySelector('.model-option'); if (firstModel) { console.log('🆘 Emergency model selection'); this.selectModel(firstModel); } } }, 500); } updateWelcomeMessage() { const messagesContainer = document.getElementById('chatMessages'); const allPersonas = [...this.config.personas, ...state.config.customPersonas]; const currentPersona = allPersonas.find(p => p.id === state.chat.currentPersona); if (currentPersona) { messagesContainer.innerHTML = ` <div class="message bot"> <div class="message-header"> <div class="message-avatar">${currentPersona.emoji}</div> <span>${currentPersona.name}</span> <span>•</span> <span>Just now</span> </div> <div class="message-bubble"> I'm ${currentPersona.name}. ${currentPersona.description}. What would you like to explore today? <button class="copy-button" onclick="copyMessage(this)" data-text="I'm ${currentPersona.name}. ${currentPersona.description}. What would you like to explore today?">Copy</button> </div> </div> `; } } } // Utility functions function generateUserId() { const stored = localStorage.getItem('bha_user_id'); if (stored) return stored; const id = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); localStorage.setItem('bha_user_id', id); return id; } function newConversationId() { const id = 'conv_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5); localStorage.setItem('bha_conversation_id', id); return id; } function showError(message) { const errorEl = document.getElementById('errorMessage'); errorEl.textContent = message; errorEl.classList.add('show'); setTimeout(() => { errorEl.classList.remove('show'); }, 5000); } // Fullscreen functionality function toggleFullscreen() { const app = document.getElementById('app'); const fullscreenIcon = document.getElementById('fullscreenIcon'); const fullscreenText = document.getElementById('fullscreenText'); state.ui.isFullscreen = !state.ui.isFullscreen; app.classList.toggle('fullscreen', state.ui.isFullscreen); if (state.ui.isFullscreen) { fullscreenIcon.textContent = '⛶'; fullscreenText.textContent = 'Exit'; } else { fullscreenIcon.textContent = '⛶'; fullscreenText.textContent = 'Fullscreen'; } // Save preference localStorage.setItem('bha_fullscreenMode', state.ui.isFullscreen.toString()); } // Layout toggle functionality function toggleLayout() { const chatContainer = document.querySelector('.chat-container'); const isFlexbox = chatContainer.classList.toggle('flexbox'); localStorage.setItem('bha_flexboxMode', isFlexbox.toString()); showToast(`Layout: ${isFlexbox ? 'Flexible' : 'Fixed'}`, 'success', 2000); console.log('Layout toggled to:', isFlexbox ? 'flexbox' : 'fixed'); } // Copy functionality function copyMessage(button) { const text = button.dataset.text; navigator.clipboard.writeText(text).then(() => { const originalText = button.textContent; button.textContent = 'Copied!'; button.classList.add('copied'); setTimeout(() => { button.textContent = originalText; button.classList.remove('copied'); }, 2000); showToast('Message copied to clipboard', 'success', 2000); }).catch(err => { console.error('Copy failed:', err); showToast('Failed to copy message', 'error', 3000); }); } // Theme system function toggleTheme() { const newTheme = state.ui.currentTheme === 'light' ? 'dark' : 'light'; state.ui.currentTheme = newTheme; document.body.dataset.theme = newTheme; const icon = document.getElementById('themeIcon'); const text = document.getElementById('themeText'); if (newTheme === 'light') { icon.textContent = '☀️'; text.textContent = 'Light'; } else { icon.textContent = '🌙'; text.textContent = 'Dark'; } localStorage.setItem('bha_selectedTheme', newTheme); } // Credits system function updateCreditsDisplay() { const creditsAmount = document.getElementById('creditsAmount'); const creditsMax = document.getElementById('creditsMax'); const creditsFill = document.getElementById('creditsFill'); creditsAmount.textContent = state.user.credits; creditsMax.textContent = state.user.maxCredits; const percentage = (state.user.credits / state.user.maxCredits) * 100; creditsFill.style.width = percentage + '%'; } function showCreditsModal() { document.getElementById('creditsModal').classList.add('active'); } function hideCreditsModal() { document.getElementById('creditsModal').classList.remove('active'); } function purchaseSubscription(tier) { const userId = state.user.userId; // TODO: Add actual Stripe integration showToast(`Stripe integration coming soon! (${tier} tier)`, 'success'); // window.location.href = `https://buy.stripe.com/your-checkout-link?prefilled_email=&client_reference_id=${userId}&metadata[tier]=${tier}&metadata[user_id]=${userId}`; } function purchaseCredits() { const userId = state.user.userId; // TODO: Add actual Stripe integration showToast('Credit purchase coming soon!', 'success'); // window.location.href = `https://buy.stripe.com/your-credit-pack-link?client_reference_id=${userId}&metadata[user_id]=${userId}`; } // NEW: Quick switcher functions function applyQuickSwitch() { const personaSelect = document.getElementById('quickPersonaSelect'); const modelSelect = document.getElementById('quickModelSelect'); const selectedPersonaId = personaSelect.value; const selectedModelId = modelSelect.value; // Find and select the persona const personaCard = document.querySelector(`[data-persona-id="${selectedPersonaId}"]`); if (personaCard) { configManager.selectPersona(personaCard); } // Find and select the model const modelOption = document.querySelector(`[data-model-id="${selectedModelId}"]`); if (modelOption) { configManager.selectModel(modelOption); } // Hide quick switcher document.getElementById('quickSwitcher').classList.remove('active'); showToast('Settings applied', 'success'); } // NEW: Premium features function toggleCreatePersona() { const form = document.getElementById('createPersonaForm'); form.classList.toggle('active'); } async function saveCustomPersona() { const name = document.getElementById('customPersonaName').value.trim(); const emoji = document.getElementById('customPersonaEmoji').value.trim(); const description = document.getElementById('customPersonaDesc').value.trim(); const prompt = document.getElementById('customPersonaPrompt').value.trim(); if (!name || !emoji || !description || !prompt) { showError('Please fill in all fields'); return; } const customPersona = { id: `custom_${Date.now()}`, name, emoji, description, systemPrompt: prompt, isPremium: false, requiredTier: 'Premium', gradientColor: 'linear-gradient(135deg, #5D5CDE 0%, #7C7CE8 100%)', isCustom: true }; // Save to state and localStorage state.config.customPersonas.push(customPersona); localStorage.setItem('bha_custom_personas', JSON.stringify(state.config.customPersonas)); // Re-render personas configManager.renderPersonas(); // Clear form document.getElementById('customPersonaName').value = ''; document.getElementById('customPersonaEmoji').value = ''; document.getElementById('customPersonaDesc').value = ''; document.getElementById('customPersonaPrompt').value = ''; toggleCreatePersona(); showToast(`${name} persona created successfully!`, 'success'); } async function getAISuggestions() { const suggestionsPanel = document.getElementById('suggestionsPanel'); const suggestionsList = document.getElementById('suggestionsList'); suggestionsPanel.classList.add('active'); suggestionsList.innerHTML = '<div style="text-align: center; padding: 20px;">🧠 Analyzing your patterns...</div>'; try { const response = await fetch(CONFIG.suggestionsWebhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: state.user.userId, conversation_history: state.chat.messages.slice(-10) // Last 10 messages }) }); if (!response.ok) throw new Error('Failed to get suggestions'); const data = await response.json(); const suggestions = data.suggestions || []; suggestionsList.innerHTML = suggestions.map(suggestion => ` <div class="suggestion-item" onclick="applySuggestion('${suggestion.prompt}')"> <div class="suggestion-text">${suggestion.prompt}</div> <div class="suggestion-meta">Confidence: ${Math.round(suggestion.confidence * 100)}%</div> </div> `).join(''); } catch (error) { console.error('Error getting suggestions:', error); suggestionsList.innerHTML = '<div style="text-align: center; padding: 20px; color: var(--accent);">Failed to get suggestions. Please try again.</div>'; } } function applySuggestion(prompt) { document.getElementById('chatInput').value = prompt; document.getElementById('suggestionsPanel').classList.remove('active'); showToast('Suggestion applied to chat input', 'success'); } // Chat functions with copy buttons function addMessage(content, isUser = false, persona = null) { const messagesContainer = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); messageDiv.className = `message ${isUser ? 'user' : 'bot'}`; const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); const sender = isUser ? 'You' : (persona || 'AI'); const avatar = isUser ? '👤' : (getPersonaEmoji(state.chat.currentPersona) || '🤖'); // Process markdown for bot messages const processedContent = isUser ? escapeHtml(content) : (typeof marked !== 'undefined' ? marked.parse(content) : content); const copyButton = isUser ? '' : `<button class="copy-button" onclick="copyMessage(this)" data-text="${escapeHtml(content)}">Copy</button>`; messageDiv.innerHTML = ` <div class="message-header"> <div class="message-avatar">${avatar}</div> <span>${sender}</span> <span>•</span> <span>${timestamp}</span> </div> <div class="message-bubble"> ${processedContent} ${copyButton} </div> `; messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; // Store in state state.chat.messages.push({ content: content, isUser: isUser, timestamp: Date.now(), persona: persona }); } function addSystemMessage(content) { const messagesContainer = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); messageDiv.className = 'message system'; messageDiv.innerHTML = ` <div class="message-header"> <div class="message-avatar">⚙️</div> <span>System</span> <span>•</span> <span>${new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span> </div> <div class="message-bubble system-message"> ${content} </div> `; messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; } function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function addTypingIndicator() { const messagesContainer = document.getElementById('chatMessages'); const typingDiv = document.createElement('div'); typingDiv.id = 'typingIndicator'; typingDiv.className = 'message bot'; const persona = getPersonaName(state.chat.currentPersona); const avatar = getPersonaEmoji(state.chat.currentPersona); typingDiv.innerHTML = ` <div class="message-header"> <div class="message-avatar">${avatar}</div> <span>${persona}</span> <span>•</span> <span>typing...</span> </div> <div class="message-bubble"> <div class="typing-indicator"> <div class="typing-dot"></div> <div class="typing-dot"></div> <div class="typing-dot"></div> </div> </div> `; messagesContainer.appendChild(typingDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; } function removeTypingIndicator() { const indicator = document.getElementById('typingIndicator'); if (indicator) { indicator.remove(); } } function getPersonaName(personaId) { const allPersonas = [...(state.config.personas || []), ...(state.config.customPersonas || [])]; const persona = allPersonas.find(p => p.id === personaId); return persona ? persona.name : 'AI'; } function getPersonaEmoji(personaId) { const allPersonas = [...(state.config.personas || []), ...(state.config.customPersonas || [])]; const persona = allPersonas.find(p => p.id === personaId); return persona ? persona.emoji : '🤖'; } function validateStateBeforeSend() { // Type checking and correction if (typeof state.chat.currentPersona !== 'string') { console.warn('🔧 Fixing persona type:', typeof state.chat.currentPersona); state.chat.currentPersona = String(state.chat.currentPersona || ''); } if (typeof state.chat.currentModel !== 'string') { console.warn('🔧 Fixing model type:', typeof state.chat.currentModel); state.chat.currentModel = String(state.chat.currentModel || ''); } // Log final verification console.log('🔍 Pre-send validation:'); console.log(' Persona:', typeof state.chat.currentPersona, '=', state.chat.currentPersona); console.log(' Model:', typeof state.chat.currentModel, '=', state.chat.currentModel); return { personaValid: state.chat.currentPersona && state.chat.currentPersona !== '', modelValid: state.chat.currentModel && state.chat.currentModel !== '' }; } // Enhanced send message with proper state validation async function sendMessage() { const input = document.getElementById('chatInput'); const message = input.value.trim(); if (!message || state.chat.isTyping) return; // Validate that we have selections if (!state.chat.currentPersona || !state.chat.currentModel) { showError('Please select a persona and model before sending a message.'); return; } // Check credits if (state.chat.currentModelCost > 0 && state.user.credits < state.chat.currentModelCost) { showError(`Insufficient credits! You need ${state.chat.currentModelCost} credits to use this model.`); showCreditsModal(); return; } // Add user message addMessage(message, true); // Clear input input.value = ''; input.style.height = 'auto'; // Show typing indicator addTypingIndicator(); state.chat.isTyping = true; // Disable send button const sendBtn = document.getElementById('sendButton'); sendBtn.disabled = true; document.getElementById('sendButtonText').textContent = 'Sending'; document.getElementById('sendButtonIcon').innerHTML = '<div class="loading-animation"></div>'; try { // Pre-send validation const validation = validateStateBeforeSend(); if (!validation.personaValid || !validation.modelValid) { showError('Selection validation failed. Please reselect your persona and model.'); return; } // Generate session-based logId const sessionId = state.chat.conversationId || newConversationId(); const logId = `LOG_${sessionId}_${Date.now()}`; // BULLETPROOF payload construction const payload = { user_id: String(state.user.userId), bot_to_call: String(state.chat.currentPersona), full_prompt_for_llm: String(message), raw_user_situation: String(message), selected_model_for_openrouter: String(state.chat.currentModel), conversation_id: sessionId, timestamp: new Date().toISOString(), log_id: logId, // NEW: Include log ID frontend_state_snapshot: JSON.stringify({ persona: state.chat.currentPersona, model: state.chat.currentModel, conversation_id: sessionId, message_count: state.chat.messages.length }) }; // Final payload verification console.log('🚀 Final payload verification:'); Object.keys(payload).forEach(key => { console.log(` ${key}: ${typeof payload[key]} = ${payload[key]}`); }); // Send to webhook const response = await fetch(CONFIG.mainWebhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' // 'X-API-Key': CONFIG.apiKey // Enable when ready }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); console.log('✅ Response received:', data); let aiResponseText = "I'm having trouble formulating a response right now."; // Default // --- CORRECTED EXTRACTION LOGIC --- if (data && typeof data.response === 'string') { // CHECK IF 'data' EXISTS AND 'data.response' IS A STRING aiResponseText = data.response; } else if (data.bot_response && data.bot_response.analysis) { aiResponseText = data.bot_response.analysis; } else if (data.upsell_message) { // Keep your upsell logic if N8N can send this structure too aiResponseText = `${data.upsell_message} ${data.analysis || ''}`; } // --- CRITICAL DEBUG LOG (keep this for a moment) --- console.log(">>>> FINAL aiResponseText to be displayed:", aiResponseText); console.log(">>>> Type of aiResponseText:", typeof aiResponseText); // --- END CRITICAL DEBUG LOG --- removeTypingIndicator(); addMessage(aiResponseText, false); // Persona name is handled by addMessage // Update credits if provided if (data.credits_remaining !== undefined) { state.user.credits = data.credits_remaining; } else if (state.chat.currentModelCost > 0) { state.user.credits = Math.max(0, state.user.credits - state.chat.currentModelCost); } updateCreditsDisplay(); // Update user tier if provided if (data.user_tier) { state.user.tier = data.user_tier; configManager.updatePremiumSection(); } // Show tool indicator if tool was used if (data.tool_used) { console.log('🔧 Tool was used in this response'); showToast('Enhanced response generated', 'success', 2000); } } catch (error) { console.error('❌ Error sending message:', error); removeTypingIndicator(); showError('Failed to send message. Please check your connection and try again.'); } finally { state.chat.isTyping = false; sendBtn.disabled = false; document.getElementById('sendButtonText').textContent = 'Send'; document.getElementById('sendButtonIcon').textContent = '→'; } } // Chat controls function newConversation() { state.chat.conversationId = newConversationId(); state.chat.messages = []; configManager.updateWelcomeMessage(); showToast('New conversation started', 'success'); } function saveConversation() { if (state.chat.messages.length === 0) { showToast('No messages to save yet!', 'error'); return; } showToast('💾 Conversation saved! (Feature coming soon)', 'success'); } function clearChat() { if (confirm('Clear all messages? This cannot be undone!')) { state.chat.messages = []; if (configManager.isLoaded) { configManager.updateWelcomeMessage(); } showToast('Chat cleared', 'success'); } } function exportChat() { if (state.chat.messages.length === 0) { showToast('No messages to export yet!', 'error'); return; } let content = `BrutallyHonest.ai Chat Export\n`; content += `Exported: ${new Date().toLocaleString()}\n`; content += `Model: ${state.chat.currentModel}\n`; content += `Persona: ${getPersonaName(state.chat.currentPersona)}\n`; content += `Session ID: ${state.chat.conversationId}\n`; content += '='.repeat(50) + '\n\n'; state.chat.messages.forEach(msg => { const timestamp = new Date(msg.timestamp).toLocaleString(); const speaker = msg.isUser ? 'You' : (msg.persona || 'AI'); content += `[${timestamp}] ${speaker}: ${msg.content}\n\n`; }); const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `BHA_Chat_${new Date().toISOString().split('T')[0]}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showToast('Chat exported successfully', 'success'); } // Initialize configuration manager const configManager = new BHAConfigManager(); // Enhanced initialization async function init() { console.log("🔥 BrutallyHonest.ai Initializing..."); // Load custom personas from localStorage const savedCustomPersonas = localStorage.getItem('bha_custom_personas'); if (savedCustomPersonas) { state.config.customPersonas = JSON.parse(savedCustomPersonas); } // Restore saved preferences const savedTheme = localStorage.getItem('bha_selectedTheme') || 'light'; const savedFullscreen = localStorage.getItem('bha_fullscreenMode') === 'true'; // Apply theme state.ui.currentTheme = savedTheme; document.body.dataset.theme = savedTheme; const icon = document.getElementById('themeIcon'); const text = document.getElementById('themeText'); if (savedTheme === 'dark') { icon.textContent = '🌙'; text.textContent = 'Dark'; } // Apply fullscreen if previously set if (savedFullscreen) { state.ui.isFullscreen = true; document.getElementById('app').classList.add('fullscreen'); document.getElementById('fullscreenIcon').textContent = '⛶'; document.getElementById('fullscreenText').textContent = 'Exit'; } // Set up enhanced event listeners document.getElementById('themeToggle').addEventListener('click', toggleTheme); document.getElementById('creditsDisplay').addEventListener('click', showCreditsModal); document.getElementById('closeCreditsModal').addEventListener('click', hideCreditsModal); document.getElementById('layoutToggle').addEventListener('click', toggleLayout); // Fullscreen toggles document.getElementById('fullscreenToggle').addEventListener('click', toggleFullscreen); document.getElementById('chatFullscreenBtn').addEventListener('click', toggleFullscreen); // NEW: Floating controls document.getElementById('exitFullscreen').addEventListener('click', toggleFullscreen); document.getElementById('switcherToggle').addEventListener('click', () => { document.getElementById('quickSwitcher').classList.toggle('active'); }); // Chat controls document.getElementById('newChatBtn').addEventListener('click', newConversation); document.getElementById('saveBtn').addEventListener('click', saveConversation); document.getElementById('clearBtn').addEventListener('click', clearChat); document.getElementById('exportBtn').addEventListener('click', exportChat); // Send message document.getElementById('sendButton').addEventListener('click', sendMessage); document.getElementById('chatInput').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); // Auto-resize textarea document.getElementById('chatInput').addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 100) + 'px'; }); // Close modal when clicking outside window.addEventListener('click', (e) => { const modal = document.getElementById('creditsModal'); if (e.target === modal) { hideCreditsModal(); } // Also close quick switcher const quickSwitcher = document.getElementById('quickSwitcher'); if (!quickSwitcher.contains(e.target) && !document.getElementById('switcherToggle').contains(e.target)) { quickSwitcher.classList.remove('active'); } }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { // Escape to exit fullscreen if (e.key === 'Escape') { if (state.ui.isFullscreen) { toggleFullscreen(); } document.getElementById('quickSwitcher').classList.remove('active'); } // F11 for fullscreen toggle if (e.key === 'F11') { e.preventDefault(); toggleFullscreen(); } // Ctrl/Cmd + / for theme toggle if ((e.ctrlKey || e.metaKey) && e.key === '/') { e.preventDefault(); toggleTheme(); } }); // Initialize displays updateCreditsDisplay(); // Load configuration and render dynamic content await configManager.loadConfiguration(); console.log('✅ BrutallyHonest.ai initialization complete!'); } // Global functions for HTML onclick events window.purchaseSubscription = purchaseSubscription; window.purchaseCredits = purchaseCredits; window.copyMessage = copyMessage; window.toggleCreatePersona = toggleCreatePersona; window.saveCustomPersona = saveCustomPersona; window.getAISuggestions = getAISuggestions; window.applySuggestion = applySuggestion; window.applyQuickSwitch = applyQuickSwitch; // Initialize when ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } </script> </body> </html>
super-embed:
<!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" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <title> BrutallyHonest.ai - Transform Your Bullsh*t Into Breakthroughs </title>
<script src="<https://cdn.tailwindcss.com>"></script>
<script src="<https://cdn.jsdelivr.net/npm/marked/marked.min.js>"></script>
<style>
:root {
/* Light Theme (Default) */
--black: #FFFFFF;
--black-soft: #F8F9FA;
--black-card: #F1F3F4;
--border: #E8EAED;
--border-light: #DADCE0;
--white: #000000;
--gray: #5F6368;
--gray-light: #3C4043;
--gray-dark: #80868B;
--brand: #5D5CDE;
--brand-light: #7C7CE8;
--brand-dark: #4B4BC7;
--accent: #EF4444;
--success: #22C55E;
--warning: #F59E0B;
--gold: #FCD34D;
--gradient-brand: linear-gradient(135deg, #5D5CDE 0%, #7C7CE8 100%);
--gradient-fire: linear-gradient(135deg, #EF4444 0%, #F59E0B 100%);
--gradient-success: linear-gradient(135deg, #22C55E 0%, #4ADE80 100%);
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.08);
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.12), 0 1px 2px -1px rgb(0 0 0 / 0.12);
--shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06);
--shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05);
--shadow-glow: 0 0 0 1px rgba(93, 92, 222, 0.3), 0 0 20px rgba(93, 92, 222, 0.2);
}
.app { /* ... other styles ... / padding: 0 16px 16px 16px; / Top padding is 0, others remain 16px / / Or more explicitly: / / padding-top: 0; / / padding-left: 16px; / / padding-right: 16px; / / padding-bottom: 16px; / / ... other styles ... / } / Dark Theme Override */ [data-theme="dark"] { --black: #0F0F0F; --black-soft: #1A1A1A; --black-card: #262626; --border: #404040; --border-light: #525252; --white: #FFFFFF; --gray: #A3A3A3; --gray-light: #D4D4D4; --gray-dark: #737373;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
html {
position: static;
height: auto;
overflow: auto;
}
body {
background: var(--black);
color: var(--white);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background-color 0.5s ease, color 0.5s ease;
min-height: 100vh;
overflow-x: hidden;
position: relative;
}
.app {
max-width: 1200px;
margin: 0 auto;
padding: 16px;
min-height: 100vh;
position: relative;
width: 100%;
transition: all 0.3s ease;
}
/* Toast Container */
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
pointer-events: none;
}
.toast {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 8px;
box-shadow: var(--shadow-lg);
backdrop-filter: blur(8px);
animation: toastSlideIn 0.3s ease-out;
pointer-events: all;
font-size: 14px;
max-width: 300px;
}
.toast.success {
border-color: var(--success);
background: rgba(34, 197, 94, 0.1);
}
.toast.error {
border-color: var(--accent);
background: rgba(239, 68, 68, 0.1);
}
@keyframes toastSlideIn {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* FULLSCREEN MODE */
.app.fullscreen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
max-width: none;
padding: 0;
z-index: 10000;
background: var(--black);
}
.app.fullscreen .header,
.app.fullscreen .explainer,
.app.fullscreen .model-selection-container,
.app.fullscreen .persona-selection-container {
display: none;
}
.app.fullscreen .chat-container {
height: 100vh;
border-radius: 0;
border: none;
}
.fullscreen-toggle {
background: var(--black-card);
border: 1px solid var(--border);
padding: 6px 8px;
border-radius: 6px;
color: var(--gray);
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
}
.fullscreen-toggle:hover {
border-color: var(--brand);
color: var(--brand);
}
.app.fullscreen .fullscreen-toggle {
position: absolute;
top: 16px;
right: 16px;
z-index: 10001;
background: var(--black-soft);
border: 1px solid var(--border);
}
/* COPY FUNCTIONALITY */
.message.bot .message-bubble {
position: relative;
}
.message.bot:hover .copy-button {
opacity: 1;
}
.copy-button {
position: absolute;
top: 8px;
right: 8px;
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 4px;
padding: 4px 6px;
font-size: 10px;
cursor: pointer;
opacity: 0;
transition: all 0.2s ease;
color: var(--gray);
font-weight: 500;
}
.copy-button:hover {
background: var(--brand);
color: var(--white);
border-color: var(--brand);
}
.copy-button.copied {
background: var(--success);
color: var(--white);
border-color: var(--success);
}
/* Credit System */
.credits-container {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 12px;
padding: 12px 16px;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.credits-container:hover {
border-color: var(--brand);
transform: translateY(-1px);
}
.credits-progress {
flex: 1;
min-width: 80px;
}
.credits-bar {
width: 100%;
height: 4px;
background: var(--border);
border-radius: 2px;
overflow: hidden;
margin-top: 4px;
}
.credits-fill {
height: 100%;
background: var(--gradient-brand);
border-radius: 2px;
transition: width 0.5s ease;
}
.credits-text {
font-size: 12px;
color: var(--gray);
}
.credits-number {
font-weight: 700;
color: var(--brand);
}
/* Dynamic Content Loading */
.loading-placeholder {
background: var(--black-card);
border-radius: 8px;
padding: 16px;
text-align: center;
color: var(--gray);
font-size: 14px;
}
.loading-animation {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid var(--border);
border-radius: 50%;
border-top-color: var(--brand);
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Premium Indicators */
.premium-badge {
position: absolute;
top: -4px;
right: -4px;
background: var(--gradient-brand);
color: var(--white);
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 700;
}
/* Model Organization */
.model-section {
margin-bottom: 16px;
}
.model-section-title {
font-size: 10px;
font-weight: 600;
color: var(--gray);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 8px;
padding-left: 8px;
}
.model-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 8px;
}
.model-option {
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px 12px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
text-align: center;
position: relative;
}
.model-option:hover {
border-color: var(--brand);
transform: translateY(-1px);
}
.model-option.active {
background: rgba(93, 92, 222, 0.1);
border-color: var(--brand);
box-shadow: var(--shadow-glow);
}
.model-name {
font-size: 16px;
font-weight: 600;
margin-bottom: 2px;
}
.model-cost {
font-size: 14px;
text-transform: uppercase;
opacity: 0.8;
}
.model-cost.free {
color: var(--success);
}
.model-cost.paid {
color: var(--warning);
}
/* Persona Cards */
.persona-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 12px;
}
.persona-card {
background: var(--black-card);
border: 2px solid var(--border);
border-radius: 12px;
padding: 16px 12px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
position: relative;
}
.persona-card:hover {
border-color: var(--brand);
transform: translateY(-2px);
}
.persona-card.active {
border-color: var(--brand);
background: rgba(93, 92, 222, 0.05);
box-shadow: var(--shadow-glow);
}
.persona-card.premium {
border-color: var(--gold);
}
.persona-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
margin: 0 auto 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: var(--white);
position: relative;
}
.persona-name {
font-weight: 600;
font-size: 18px;
margin-bottom: 4px;
}
.persona-desc {
font-size: 13px;
color: var(--gray);
line-height: 1.3;
}
/* Chat Interface */
.chat-container {
background: var(--black-soft);
border: 3px solid var(--border);
border-radius: 20px;
height: 500px;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.chat-header {
padding: 16px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
background: var(--black-card);
}
.chat-title {
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
flex: 1;
}
.chat-controls {
display: flex;
gap: 6px;
}
.chat-control-btn {
background: transparent;
border: 1px solid var(--border);
padding: 6px 8px;
border-radius: 6px;
color: var(--gray);
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
}
.chat-control-btn:hover {
border-color: var(--brand);
color: var(--brand);
}
.chat-container.flexbox {
height: 70vh;
max-height: 800px;
min-height: 400px;
}
.layout-toggle {
background: var(--black-card);
border: 1px solid var(--border);
padding: 6px 8px;
border-radius: 6px;
color: var(--gray);
cursor: pointer;
transition: all 0.2s ease;
font-size: 12px;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
scroll-behavior: smooth;
}
.message {
margin-bottom: 16px;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.message-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
font-size: 12px;
color: var(--gray);
}
.message-avatar {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 600;
}
.message.user .message-avatar {
background: var(--gradient-brand);
color: var(--white);
}
[data-theme="light"] .message.user .message-avatar {
color: var(--black);
}
.message.bot .message-avatar {
background: var(--gradient-success);
color: var(--white);
}
[data-theme="light"] .message.bot .message-avatar {
color: var(--black);
}
.message-bubble {
padding: 12px 16px;
border-radius: 18px;
font-size: 14px;
line-height: 1.5;
max-width: 85%;
word-wrap: break-word;
}
.message.user .message-bubble {
margin-left: auto;
background: var(--gradient-brand);
color: var(--white);
}
[data-theme="light"] .message.user .message-bubble {
color: var(--black);
}
.message.bot .message-bubble {
background: linear-gradient(135deg, var(--black-card) 0%, rgba(93, 92, 222, 0.05) 100%);
color: var(--gray-light);
border: 1px solid var(--border);
padding-right: 40px; /* Space for copy button */
}
.chat-input-area {
padding: 16px;
border-top: 1px solid var(--border);
background: var(--black-card);
flex-shrink: 0;
}
.chat-input-container {
display: flex;
gap: 8px;
align-items: flex-end;
}
.chat-input {
flex: 1;
background: var(--black);
border: 1px solid var(--border);
border-radius: 12px;
padding: 12px;
font-size: 16px;
color: var(--white);
resize: none;
min-height: 40px;
max-height: 100px;
transition: border-color 0.2s ease;
font-family: inherit;
}
.chat-input:focus {
outline: none;
border-color: var(--brand);
}
.chat-input::placeholder {
color: var(--gray-dark);
}
.send-button {
background: var(--gradient-brand);
border: none;
padding: 12px 16px;
border-radius: 12px;
color: var(--white);
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 6px;
min-height: 40px;
}
[data-theme="light"] .send-button {
color: var(--black);
}
.send-button:hover:not(:disabled) {
transform: translateY(-1px);
}
.send-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
flex-wrap: wrap;
gap: 16px;
}
.logo {
font-size: 20px;
font-weight: 700;
background: var(--gradient-brand);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
flex-shrink: 0;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.theme-toggle {
background: var(--black-card);
border: 1px solid var(--border);
border-radius: 50px;
padding: 8px 12px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
flex-shrink: 0;
}
.theme-toggle:hover {
border-color: var(--brand);
}
/* Explainer Section */
.explainer {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
text-align: center;
}
.explainer h1 {
font-size: 20px;
font-weight: 600;
color: var(--brand);
margin-bottom: 8px;
}
.explainer p {
font-size: 14px;
color: var(--gray-light);
line-height: 1.5;
}
/* Credits Modal */
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 9000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
padding: 16px;
}
.modal.active {
opacity: 1;
visibility: visible;
}
.modal-content {
background: var(--black-soft);
border: 1px solid var(--border);
border-radius: 20px;
padding: 24px;
max-width: 500px;
width: 100%;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-title {
font-size: 20px;
font-weight: 700;
margin: 0;
}
.modal-close {
background: none;
border: none;
color: var(--gray);
font-size: 20px;
cursor: pointer;
padding: 4px;
}
.modal-close:hover {
color: var(--white);
}
.pricing-option {
background: var(--black-card);
border: 2px solid var(--border);
border-radius: 16px;
padding: 20px;
margin-bottom: 12px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
}
.pricing-option:hover {
border-color: var(--brand);
transform: translateY(-2px);
}
.pricing-option.recommended {
border-color: var(--success);
position: relative;
}
.pricing-option.recommended::before {
content: "RECOMMENDED";
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
background: var(--success);
color: var(--white);
padding: 4px 8px;
border-radius: 12px;
font-size: 9px;
font-weight: 700;
}
.pricing-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 6px;
}
.pricing-price {
font-size: 24px;
font-weight: 700;
color: var(--success);
margin-bottom: 8px;
}
.pricing-features {
list-style: none;
text-align: left;
color: var(--gray-light);
margin-bottom: 12px;
}
.pricing-features li {
padding: 3px 0;
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
}
.pricing-features li::before {
content: "✓";
color: var(--success);
font-weight: bold;
}
.action-button {
background: var(--gradient-brand);
border: none;
padding: 12px 12px;
border-radius: 16px;
color: var(--white);
font-weight: 600;
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
width: 70%;
}
[data-theme="light"] .action-button {
color: var(--black);
}
.action-button:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-glow);
}
/* Typing indicator */
.typing-indicator {
display: inline-flex;
align-items: center;
gap: 2px;
}
.typing-dot {
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--brand);
animation: typing 1.4s infinite;
}
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-10px); opacity: 1; }
}
/* Error States */
.error-message {
background: rgba(239, 68, 68, 0.1);
border: 1px solid var(--accent);
border-radius: 8px;
padding: 12px;
margin: 8px 0;
color: var(--accent);
font-size: 14px;
display: none;
}
.error-message.show {
display: block;
animation: errorSlide 0.3s ease-out;
}
@keyframes errorSlide {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Mobile Optimizations */
@media (max-width: 768px) {
.app {
padding: 12px;
}
.header {
flex-direction: column;
align-items: stretch;
text-align: center;
margin-bottom: 20px;
}
.header-actions {
justify-content: center;
gap: 8px;
}
.model-grid {
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 6px;
}
.persona-grid {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 8px;
}
.chat-container {
height: 400px;
}
.explainer h3 {
font-size: 16px;
}
.explainer p {
font-size: 13px;
}
.app.fullscreen .chat-container {
height: 100vh;
}
}
</style>
</head>
<body data-theme="light"> <!-- Toast Container --> <div class="toast-container" id="toastContainer"></div>
<div class="app" id="app">
<!-- Fullscreen Toggle (Visible in fullscreen mode) -->
<button class="fullscreen-toggle" id="fullscreenToggle">
<span id="fullscreenIcon">⛶</span>
<span id="fullscreenText">Fullscreen</span>
</button>
<!-- Header -->
<header class="header">
<div class="logo">BrutallyHonest.ai</div>
<div class="header-actions">
<!-- Credits Display -->
<div class="credits-container" id="creditsDisplay">
<div class="credits-progress">
<div class="credits-text">
<span class="credits-number" id="creditsAmount">10</span> /
<span id="creditsMax">1000</span> credits
</div>
<div class="credits-bar">
<div
class="credits-fill"
id="creditsFill"
style="width: 1%"
></div>
</div>
</div>
<div
style="font-size: 12px; color: var(--brand); font-weight: 600;"
>
Upgrade
</div>
</div>
<!-- Theme Toggle -->
<div class="theme-toggle" id="themeToggle">
<span id="themeIcon">☀️</span>
<span id="themeText">Light</span>
</div>
</div>
</header>
<!-- Explainer -->
<section class="explainer">
<h3>🔥 The AI That Has Transformed Thousands</h3>
<p>
Choose your AI personality below, pick a model that powers the
conversation, and start chatting.
<strong>Free models are unlimited.</strong> Premium models cost
credits but deliver breakthrough insights.
</p>
</section>
<!-- Error Display -->
<div class="error-message" id="errorMessage">
<!-- Error content will be populated by JavaScript -->
</div>
<!-- Main Chat Interface -->
<div class="chat-container">
<div class="chat-header">
<div class="chat-title">
<span id="currentPersonaEmoji">🔥</span>
<span id="currentPersonaName">Loading...</span>
</div>
<div class="chat-controls">
<button
class="fullscreen-toggle chat-control-btn"
id="chatFullscreenBtn"
>
⛶
</button>
<button class="layout-toggle chat-control-btn" id="layoutToggle">📐</button>
<button class="chat-control-btn" id="saveBtn">💾</button>
<button class="chat-control-btn" id="clearBtn">🗑️</button>
<button class="chat-control-btn" id="exportBtn">📤</button>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<div class="message bot">
<div class="message-header">
<div class="message-avatar">🔥</div>
<span>Loading...</span>
<span>•</span>
<span>Just now</span>
</div>
<div class="message-bubble">
Welcome! Configuring your experience...
<button
class="copy-button"
onclick="copyMessage(this)"
data-text="Welcome! Configuring your experience..."
>
Copy
</button>
</div>
</div>
</div>
<div class="chat-input-area">
<div class="chat-input-container">
<textarea
class="chat-input"
id="chatInput"
placeholder="Type your message here..."
rows="1"
></textarea>
<button class="send-button" id="sendButton">
<span id="sendButtonText">Send</span>
<span id="sendButtonIcon">→</span>
</button>
</div>
</div>
</div>
<!-- Model Selection -->
<div
style="margin: 20px 0;"
class="model-selection-container"
id="modelSelectionContainer"
>
<h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);">
Select AI Model
</h4>
<div class="loading-placeholder" id="modelsLoadingPlaceholder">
<div class="loading-animation"></div>
Loading models...
</div>
<div id="modelsContainer"></div>
</div>
<!-- Persona Selection -->
<div
style="margin: 20px 0;"
class="persona-selection-container"
id="personaSelectionContainer"
>
<h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);">
Choose Your AI Personality
</h4>
<div class="loading-placeholder" id="personasLoadingPlaceholder">
<div class="loading-animation"></div>
Loading personalities...
</div>
<div class="persona-grid" id="personaGrid"></div>
</div>
</div>
<!-- Credits Modal -->
<div class="modal" id="creditsModal">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">💎 Get More Credits</h2>
<button class="modal-close" id="closeCreditsModal">×</button>
</div>
<div class="pricing-option recommended">
<div class="pricing-title">Monthly Subscription</div>
<div class="pricing-price">
$9<span style="font-size: 12px; color: var(--gray);">/month</span>
</div>
<ul class="pricing-features">
<li>500 credits per month</li>
<li>Unlimited free models</li>
<li>Access to all premium models</li>
<li>Credits roll over (up to 1000)</li>
<li>Priority support</li>
</ul>
<button class="action-button" onclick="purchaseMonthly()">
Subscribe Now ✨
</button>
</div>
<div class="pricing-option">
<div class="pricing-title">Pay As You Go</div>
<div class="pricing-price">From $2.99</div>
<ul class="pricing-features">
<li>100 credits for $2.99</li>
<li>250 credits for $5.99</li>
<li>500 credits for $9.99</li>
<li>Credits never expire</li>
</ul>
<button class="action-button" onclick="purchaseCredits()">
Buy Credits 💰
</button>
</div>
</div>
</div>
<script>
// Configuration - UPDATE THESE URLs
const CONFIG = {
mainWebhookUrl: '<https://photobar.app.n8n.cloud/webhook/bha-mvp>',
configWebhookUrl: '<https://photobar.app.n8n.cloud/webhook/config>',
toolRouterUrl: '<https://photobar.app.n8n.cloud/webhook/tool-router>'
}
// Global state with enhanced persistence
const state = {
user: {
credits: 10,
maxCredits: 1000,
userId: generateUserId()
},
chat: {
currentModel: null,
currentModelCost: 0,
currentPersona: null,
messages: [],
isTyping: false
},
ui: {
currentTheme: 'light',
isFullscreen: false
},
config: {
personas: [],
models: [],
modelsByTier: { free: [], standard: [], premium: [] },
isLoaded: false
}
};
// Toast System
function showToast(message, type = 'success', duration = 3000) {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
container.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(100%)';
setTimeout(() => {
if (toast.parentNode) {
container.removeChild(toast);
}
}, 300);
}, duration);
}
// Enhanced Configuration Manager with Fixed Persistence
class BHAConfigManager {
constructor() {
this.config = {
personas: [],
models: [],
modelsByTier: { free: [], standard: [], premium: [] },
features: {}
};
this.isLoaded = false;
}
async loadConfiguration() {
try {
console.log('🔄 Loading configuration from:', CONFIG.configWebhookUrl);
const response = await fetch(CONFIG.configWebhookUrl);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.config = await response.json();
this.isLoaded = true;
state.config = this.config; // Fixed: Add this line that was missing
console.log('✅ Configuration loaded:', this.config);
this.renderDynamicContent();
this.initializeDefaults();
} catch (error) {
console.error('❌ Config load failed:', error);
this.loadFallbackConfig();
}
}
loadFallbackConfig() {
console.log('🔄 Loading fallback configuration...');
this.config = {
personas: [
{
id: 'QuickResonanceSpark_Persona_Haiku',
name: 'Brutally Honest',
emoji: '🔥',
description: 'No BS, just raw truth',
isPremium: false,
gradientColor: 'linear-gradient(135deg, #EF4444, #F59E0B)'
},
{
id: 'ResonanceSession_Orchestrator_Opus',
name: 'Deep Guide',
emoji: '🧠',
description: 'Profound insights & breakthrough clarity',
isPremium: false,
gradientColor: 'linear-gradient(135deg, #10B981, #06D6A0)'
}
],
models: [
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' },
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' }
],
modelsByTier: {
free: [
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free', provider: 'OpenAI' },
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: 0, tier: 'Free', provider: 'Anthropic' }
],
standard: [],
premium: []
},
features: { darkModeEnabled: true, creditsSystemEnabled: true }
};
this.isLoaded = true;
state.config = this.config;
this.renderDynamicContent();
this.initializeDefaults();
}
renderDynamicContent() {
this.renderPersonas();
this.renderModels();
}
renderPersonas() {
const container = document.getElementById('personaGrid');
const loadingPlaceholder = document.getElementById('personasLoadingPlaceholder');
if (loadingPlaceholder) {
loadingPlaceholder.style.display = 'none';
}
container.innerHTML = this.config.personas.map(persona => `
<div class="persona-card ${persona.isPremium ? 'premium' : ''}"
data-persona-id="${persona.id}"
data-persona-name="${persona.name}"
data-persona-emoji="${persona.emoji}">
<div class="persona-avatar" style="background: ${persona.gradientColor};">
${persona.emoji}
${persona.isPremium ? '<div class="premium-badge">💎</div>' : ''}
</div>
<div class="persona-name">${persona.name}</div>
<div class="persona-desc">${persona.description}</div>
</div>
`).join('');
// Add event listeners with proper data binding
document.querySelectorAll('.persona-card').forEach(card => {
card.addEventListener('click', () => this.selectPersona(card));
});
console.log('✅ Personas rendered:', this.config.personas.length);
}
renderModels() {
const container = document.getElementById('modelsContainer');
const loadingPlaceholder = document.getElementById('modelsLoadingPlaceholder');
if (loadingPlaceholder) {
loadingPlaceholder.style.display = 'none';
}
const tiers = ['free', 'standard', 'premium'];
const tierLabels = {
'free': 'Free Models',
'standard': 'Standard Models (5-15 credits)',
'premium': 'Premium Models (15+ credits)'
};
container.innerHTML = tiers.map(tier => {
const models = this.config.modelsByTier[tier] || [];
if (models.length === 0) return '';
return `
<div class="model-section" data-tier="${tier}">
<div class="model-section-title">${tierLabels[tier]}</div>
<div class="model-grid">
${models.map(model => `
<div class="model-option"
data-model-id="${model.id}"
data-model-name="${model.name}"
data-model-cost="${model.cost}">
<div class="model-name">${model.name}</div>
<div class="model-cost ${model.cost === 0 ? 'free' : 'paid'}">
${model.cost === 0 ? 'FREE' : `${model.cost} credits`}
</div>
</div>
`).join('')}
</div>
</div>
`;
}).join('');
// Add event listeners with proper data binding
document.querySelectorAll('.model-option').forEach(option => {
option.addEventListener('click', () => this.selectModel(option));
});
console.log('✅ Models rendered:', this.config.models.length);
}
selectPersona(personaElement) {
// Remove active from all
document.querySelectorAll('.persona-card').forEach(el => el.classList.remove('active'));
personaElement.classList.add('active');
// BULLETPROOF string extraction - FIX THE BUG HERE
const personaId = String(personaElement.dataset.personaId || '');
const personaName = String(personaElement.dataset.personaName || '');
const personaEmoji = String(personaElement.dataset.personaEmoji || '');
// CRITICAL: Store as primitive string, not object reference
state.chat.currentPersona = personaId; // <- THIS IS THE FIX
// Update UI
document.getElementById('currentPersonaEmoji').textContent = personaEmoji;
document.getElementById('currentPersonaName').textContent = personaName;
// Verification
console.log('✅ Persona FIXED:', typeof state.chat.currentPersona, '=', state.chat.currentPersona);
// Save to localStorage
localStorage.setItem('bha_selectedPersona', personaId);
}
selectModel(modelElement) {
// Remove active from all
document.querySelectorAll('.model-option').forEach(el => el.classList.remove('active'));
modelElement.classList.add('active');
// BULLETPROOF string extraction - FIX THE BUG HERE
const modelId = String(modelElement.dataset.modelId || '');
const modelName = String(modelElement.dataset.modelName || '');
const modelCost = parseInt(modelElement.dataset.modelCost || '0');
// CRITICAL: Store as primitive string, not object reference
state.chat.currentModel = modelId; // <- THIS IS THE FIX
state.chat.currentModelCost = modelCost;
// Verification
console.log('✅ Model FIXED:', typeof state.chat.currentModel, '=', state.chat.currentModel);
// Save to localStorage
localStorage.setItem('bha_selectedModel', modelId);
}
initializeDefaults() {
// Restore from localStorage or select first available
const savedPersonaId = localStorage.getItem('bha_selectedPersona');
const savedModelId = localStorage.getItem('bha_selectedModel');
// Restore layout preference
const savedLayout = localStorage.getItem('bha_flexboxMode') === 'true';
if (savedLayout) {
document.querySelector('.chat-container').classList.add('flexbox');
}
// Initialize persona
if (this.config.personas.length > 0) {
let targetPersona = null;
if (savedPersonaId) {
targetPersona = document.querySelector(`[data-persona-id="${savedPersonaId}"]`);
}
if (!targetPersona) {
targetPersona = document.querySelector('.persona-card');
}
if (targetPersona) {
this.selectPersona(targetPersona);
}
}
// Initialize model
if (this.config.models.length > 0) {
let targetModel = null;
if (savedModelId) {
targetModel = document.querySelector(`[data-model-id="${savedModelId}"]`);
}
if (!targetModel) {
targetModel = document.querySelector('.model-option');
}
if (targetModel) {
this.selectModel(targetModel);
}
}
// Update welcome message
setTimeout(() => {
this.updateWelcomeMessage();
}, 100);
// Add this at the end of initializeDefaults()
setTimeout(() => { // Emergency fallback if nothing is selected if (!state.chat.currentPersona) { const firstPersona = document.querySelector('.persona-card'); if (firstPersona) { console.log('🆘 Emergency persona selection'); this.selectPersona(firstPersona); } }
if (!state.chat.currentModel) {
const firstModel = document.querySelector('.model-option');
if (firstModel) {
console.log('🆘 Emergency model selection');
this.selectModel(firstModel);
}
}
}, 500); }
updateWelcomeMessage() {
const messagesContainer = document.getElementById('chatMessages');
const currentPersona = this.config.personas.find(p => p.id === state.chat.currentPersona);
if (currentPersona) {
messagesContainer.innerHTML = `
<div class="message bot">
<div class="message-header">
<div class="message-avatar">${currentPersona.emoji}</div>
<span>${currentPersona.name}</span>
<span>•</span>
<span>Just now</span>
</div>
<div class="message-bubble">
I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today?
<button class="copy-button" onclick="copyMessage(this)" data-text="I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today?">Copy</button>
</div>
</div>
`;
}
}
}
// Utility functions
function generateUserId() {
const stored = localStorage.getItem('bha_user_id');
if (stored) return stored;
const id = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('bha_user_id', id);
return id;
}
function showError(message) {
const errorEl = document.getElementById('errorMessage');
errorEl.textContent = message;
errorEl.classList.add('show');
setTimeout(() => {
errorEl.classList.remove('show');
}, 5000);
}
// Fullscreen functionality
function toggleFullscreen() {
const app = document.getElementById('app');
const fullscreenIcon = document.getElementById('fullscreenIcon');
const fullscreenText = document.getElementById('fullscreenText');
state.ui.isFullscreen = !state.ui.isFullscreen;
app.classList.toggle('fullscreen', state.ui.isFullscreen);
if (state.ui.isFullscreen) {
fullscreenIcon.textContent = '⛶';
fullscreenText.textContent = 'Exit';
} else {
fullscreenIcon.textContent = '⛶';
fullscreenText.textContent = 'Fullscreen';
}
// Save preference
localStorage.setItem('bha_fullscreenMode', state.ui.isFullscreen.toString());
}
// Layout toggle functionality
function toggleLayout() {
const chatContainer = document.querySelector('.chat-container');
const isFlexbox = chatContainer.classList.toggle('flexbox');
localStorage.setItem('bha_flexboxMode', isFlexbox.toString());
showToast(`Layout: ${isFlexbox ? 'Flexible' : 'Fixed'}`, 'success', 2000);
console.log('Layout toggled to:', isFlexbox ? 'flexbox' : 'fixed');
}
// Copy functionality
function copyMessage(button) {
const text = button.dataset.text;
navigator.clipboard.writeText(text).then(() => {
const originalText = button.textContent;
button.textContent = 'Copied!';
button.classList.add('copied');
setTimeout(() => {
button.textContent = originalText;
button.classList.remove('copied');
}, 2000);
showToast('Message copied to clipboard', 'success', 2000);
}).catch(err => {
console.error('Copy failed:', err);
showToast('Failed to copy message', 'error', 3000);
});
}
// Theme system
function toggleTheme() {
const newTheme = state.ui.currentTheme === 'light' ? 'dark' : 'light';
state.ui.currentTheme = newTheme;
document.body.dataset.theme = newTheme;
const icon = document.getElementById('themeIcon');
const text = document.getElementById('themeText');
if (newTheme === 'light') {
icon.textContent = '☀️';
text.textContent = 'Light';
} else {
icon.textContent = '🌙';
text.textContent = 'Dark';
}
localStorage.setItem('bha_selectedTheme', newTheme);
}
// Credits system
function updateCreditsDisplay() {
const creditsAmount = document.getElementById('creditsAmount');
const creditsMax = document.getElementById('creditsMax');
const creditsFill = document.getElementById('creditsFill');
creditsAmount.textContent = state.user.credits;
creditsMax.textContent = state.user.maxCredits;
const percentage = (state.user.credits / state.user.maxCredits) * 100;
creditsFill.style.width = percentage + '%';
}
function showCreditsModal() {
document.getElementById('creditsModal').classList.add('active');
}
function hideCreditsModal() {
document.getElementById('creditsModal').classList.remove('active');
}
function purchaseMonthly() {
window.open(`https://buy.stripe.com/YOUR_MONTHLY_LINK?client_reference_id=${state.user.userId}`, '_blank');
}
function purchaseCredits() {
window.open(`https://buy.stripe.com/YOUR_CREDIT_PACK_LINK?client_reference_id=${state.user.userId}`, '_blank');
}
// Chat functions with copy buttons
function addMessage(content, isUser = false, persona = null) {
const messagesContainer = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user' : 'bot'}`;
const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
const sender = isUser ? 'You' : (persona || 'AI');
const avatar = isUser ? '👤' : (getPersonaEmoji(state.chat.currentPersona) || '🤖');
// Process markdown for bot messages
const processedContent = isUser ?
escapeHtml(content) :
(typeof marked !== 'undefined' ? marked.parse(content) : content);
const copyButton = isUser ? '' :
`<button class="copy-button" onclick="copyMessage(this)" data-text="${escapeHtml(content)}">Copy</button>`;
messageDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">${avatar}</div>
<span>${sender}</span>
<span>•</span>
<span>${timestamp}</span>
</div>
<div class="message-bubble">
${processedContent}
${copyButton}
</div>
`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Store in state
state.chat.messages.push({
content: content,
isUser: isUser,
timestamp: Date.now(),
persona: persona
});
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function addTypingIndicator() {
const messagesContainer = document.getElementById('chatMessages');
const typingDiv = document.createElement('div');
typingDiv.id = 'typingIndicator';
typingDiv.className = 'message bot';
const persona = getPersonaName(state.chat.currentPersona);
const avatar = getPersonaEmoji(state.chat.currentPersona);
typingDiv.innerHTML = `
<div class="message-header">
<div class="message-avatar">${avatar}</div>
<span>${persona}</span>
<span>•</span>
<span>typing...</span>
</div>
<div class="message-bubble">
<div class="typing-indicator">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
`;
messagesContainer.appendChild(typingDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function removeTypingIndicator() {
const indicator = document.getElementById('typingIndicator');
if (indicator) {
indicator.remove();
}
}
function getPersonaName(personaId) {
if (!state.config.personas) return 'AI';
const persona = state.config.personas.find(p => p.id === personaId);
return persona ? persona.name : 'AI';
}
function getPersonaEmoji(personaId) {
if (!state.config.personas) return '🤖';
const persona = state.config.personas.find(p => p.id === personaId);
return persona ? persona.emoji : '🤖';
}
function validateStateBeforeSend() {
// Type checking and correction
if (typeof state.chat.currentPersona !== 'string') {
console.warn('🔧 Fixing persona type:', typeof state.chat.currentPersona);
state.chat.currentPersona = String(state.chat.currentPersona || '');
}
if (typeof state.chat.currentModel !== 'string') {
console.warn('🔧 Fixing model type:', typeof state.chat.currentModel);
state.chat.currentModel = String(state.chat.currentModel || '');
}
// Log final verification
console.log('🔍 Pre-send validation:');
console.log(' Persona:', typeof state.chat.currentPersona, '=', state.chat.currentPersona);
console.log(' Model:', typeof state.chat.currentModel, '=', state.chat.currentModel);
return {
personaValid: state.chat.currentPersona && state.chat.currentPersona !== '',
modelValid: state.chat.currentModel && state.chat.currentModel !== ''
};
}
// Enhanced send message with proper state validation
async function sendMessage() {
const input = document.getElementById('chatInput');
const message = input.value.trim();
if (!message || state.chat.isTyping) return;
// Validate that we have selections
if (!state.chat.currentPersona || !state.chat.currentModel) {
showError('Please select a persona and model before sending a message.');
return;
}
// Check credits
if (state.chat.currentModelCost > 0 && state.user.credits < state.chat.currentModelCost) {
showError(`Insufficient credits! You need ${state.chat.currentModelCost} credits to use this model.`);
showCreditsModal();
return;
}
// Add user message
addMessage(message, true);
// Clear input
input.value = '';
input.style.height = 'auto';
// Show typing indicator
addTypingIndicator();
state.chat.isTyping = true;
// Disable send button
const sendBtn = document.getElementById('sendButton');
sendBtn.disabled = true;
document.getElementById('sendButtonText').textContent = 'Sending';
document.getElementById('sendButtonIcon').innerHTML = '<div class="loading-animation"></div>';
try {
// Pre-send validation
const validation = validateStateBeforeSend();
if (!validation.personaValid || !validation.modelValid) {
showError('Selection validation failed. Please reselect your persona and model.');
return;
}
// BULLETPROOF payload construction
const payload = {
user_id: String(state.user.userId),
bot_to_call: String(state.chat.currentPersona),
full_prompt_for_llm: String(message),
raw_user_situation: String(message),
selected_model_for_openrouter: String(state.chat.currentModel),
conversation_id: 'conv_' + Date.now(),
timestamp: new Date().toISOString()
};
// Final payload verification
console.log('🚀 Final payload verification:');
Object.keys(payload).forEach(key => {
console.log(` ${key}: ${typeof payload[key]} = ${payload[key]}`);
});
// Send to webhook
const response = await fetch(CONFIG.mainWebhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log('✅ Response received:', data);
let aiResponseText = "I'm having trouble formulating a response right now."; // Default
let opoeratorInsightHTML = ""; // Assuming opoerator insight might come in a different structure or not at all with this simple response
// --- CORRECTED EXTRACTION LOGIC ---
if (data && typeof data.response === 'string') { // CHECK IF 'data' EXISTS AND 'data.response' IS A STRING
aiResponseText = data.response;
// If opoerator_insight_html is also sent at the root level by N8N, access it:
// opoeratorInsightHTML = data.opoerator_insight_html || "";
} else if (data.upsell_message) { // Keep your upsell logic if N8N can send this structure too
aiResponseText = `${data.upsell_message} ${data.analysis || ''}`;
}
// REMOVE or COMMENT OUT the old 'else if' conditions that look for 'bot_response' or 'final_response_payload'
// if N8N is no longer sending those structures for this particular response.
// else if (data.bot_response && data.bot_response.analysis) { ... }
// else if (data.final_response_payload && data.final_response_payload.bot_response && data.final_response_payload.bot_response.analysis) { ... }
// --- CRITICAL DEBUG LOG (keep this for a moment) ---
console.log(">>>> FINAL aiResponseText to be displayed:", aiResponseText);
console.log(">>>> Type of aiResponseText:", typeof aiResponseText);
// --- END CRITICAL DEBUG LOG ---
removeTypingIndicator();
addMessage(aiResponseText, false); // Persona name is handled by addMessage
if (opoeratorInsightHTML) { // If you plan to send this
addSystemMessage("✨ oPOErator's Insight: " + opoeratorInsightHTML);
}
// Update credits if provided
if (data.credits_remaining !== undefined) {
state.user.credits = data.credits_remaining;
} else if (state.chat.currentModelCost > 0) {
state.user.credits = Math.max(0, state.user.credits - state.chat.currentModelCost);
}
updateCreditsDisplay();
// Show tool indicator if tool was used
if (data.tool_used) {
console.log('🔧 Tool was used in this response');
showToast('Enhanced response generated', 'success', 2000);
}
} catch (error) {
console.error('❌ Error sending message:', error);
removeTypingIndicator();
showError('Failed to send message. Please check your connection and try again.');
} finally {
state.chat.isTyping = false;
sendBtn.disabled = false;
document.getElementById('sendButtonText').textContent = 'Send';
document.getElementById('sendButtonIcon').textContent = '→';
}
}
// Chat controls
function saveConversation() {
if (state.chat.messages.length === 0) {
showToast('No messages to save yet!', 'error');
return;
}
showToast('💾 Conversation saved! (Feature coming soon)', 'success');
}
function clearChat() {
if (confirm('Clear all messages? This cannot be undone!')) {
state.chat.messages = [];
if (configManager.isLoaded) {
configManager.updateWelcomeMessage();
}
showToast('Chat cleared', 'success');
}
}
function exportChat() {
if (state.chat.messages.length === 0) {
showToast('No messages to export yet!', 'error');
return;
}
let content = `BrutallyHonest.ai Chat Export\\n`;
content += `Exported: ${new Date().toLocaleString()}\\n`;
content += `Model: ${state.chat.currentModel}\\n`;
content += `Persona: ${getPersonaName(state.chat.currentPersona)}\\n`;
content += '='.repeat(50) + '\\n\\n';
state.chat.messages.forEach(msg => {
const timestamp = new Date(msg.timestamp).toLocaleString();
const speaker = msg.isUser ? 'You' : (msg.persona || 'AI');
content += `[${timestamp}] ${speaker}: ${msg.content}\\n\\n`;
});
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `BHA_Chat_${new Date().toISOString().split('T')[0]}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('Chat exported successfully', 'success');
}
// Initialize configuration manager
const configManager = new BHAConfigManager();
// Enhanced initialization
async function init() {
console.log("🔥 BrutallyHonest.ai Initializing...");
// Restore saved preferences
const savedTheme = localStorage.getItem('bha_selectedTheme') || 'light';
const savedFullscreen = localStorage.getItem('bha_fullscreenMode') === 'true';
// Apply theme
state.ui.currentTheme = savedTheme;
document.body.dataset.theme = savedTheme;
const icon = document.getElementById('themeIcon');
const text = document.getElementById('themeText');
if (savedTheme === 'dark') {
icon.textContent = '🌙';
text.textContent = 'Dark';
}
// Apply fullscreen if previously set
if (savedFullscreen) {
state.ui.isFullscreen = true;
document.getElementById('app').classList.add('fullscreen');
document.getElementById('fullscreenIcon').textContent = '⛶';
document.getElementById('fullscreenText').textContent = 'Exit';
}
// Set up enhanced event listeners
document.getElementById('themeToggle').addEventListener('click', toggleTheme);
document.getElementById('creditsDisplay').addEventListener('click', showCreditsModal);
document.getElementById('closeCreditsModal').addEventListener('click', hideCreditsModal);
document.getElementById('layoutToggle').addEventListener('click', toggleLayout);
// Fullscreen toggles
document.getElementById('fullscreenToggle').addEventListener('click', toggleFullscreen);
document.getElementById('chatFullscreenBtn').addEventListener('click', toggleFullscreen);
// Chat controls
document.getElementById('saveBtn').addEventListener('click', saveConversation);
document.getElementById('clearBtn').addEventListener('click', clearChat);
document.getElementById('exportBtn').addEventListener('click', exportChat);
// Send message
document.getElementById('sendButton').addEventListener('click', sendMessage);
document.getElementById('chatInput').addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Auto-resize textarea
document.getElementById('chatInput').addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 100) + 'px';
});
// Close modal when clicking outside
window.addEventListener('click', (e) => {
const modal = document.getElementById('creditsModal');
if (e.target === modal) {
hideCreditsModal();
}
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Escape to exit fullscreen
if (e.key === 'Escape' && state.ui.isFullscreen) {
toggleFullscreen();
}
// F11 for fullscreen toggle
if (e.key === 'F11') {
e.preventDefault();
toggleFullscreen();
}
// Ctrl/Cmd + / for theme toggle
if ((e.ctrlKey || e.metaKey) && e.key === '/') {
e.preventDefault();
toggleTheme();
}
});
// Initialize displays
updateCreditsDisplay();
// Load configuration and render dynamic content
await configManager.loadConfiguration();
console.log('✅ BrutallyHonest.ai initialization complete!');
}
// Global functions for HTML onclick events
window.purchaseMonthly = purchaseMonthly;
window.purchaseCredits = purchaseCredits;
window.copyMessage = copyMessage;
// Initialize when ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
</script>
</body> </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" /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <title> BrutallyHonest.ai - Transform Your Bullsh*t Into Breakthroughs </title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <style> :root { /* Light Theme (Default) */ --black: #FFFFFF; --black-soft: #F8F9FA; --black-card: #F1F3F4; --border: #E8EAED; --border-light: #DADCE0; --white: #000000; --gray: #5F6368; --gray-light: #3C4043; --gray-dark: #80868B; --brand: #5D5CDE; --brand-light: #7C7CE8; --brand-dark: #4B4BC7; --accent: #EF4444; --success: #22C55E; --warning: #F59E0B; --gold: #FCD34D; --gradient-brand: linear-gradient(135deg, #5D5CDE 0%, #7C7CE8 100%); --gradient-fire: linear-gradient(135deg, #EF4444 0%, #F59E0B 100%); --gradient-success: linear-gradient(135deg, #22C55E 0%, #4ADE80 100%); --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.08); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.12), 0 1px 2px -1px rgb(0 0 0 / 0.12); --shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06); --shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05); --shadow-glow: 0 0 0 1px rgba(93, 92, 222, 0.3), 0 0 20px rgba(93, 92, 222, 0.2); } /* Dark Theme Override */ [data-theme="dark"] { --black: #0F0F0F; --black-soft: #1A1A1A; --black-card: #262626; --border: #404040; --border-light: #525252; --white: #FFFFFF; --gray: #A3A3A3; --gray-light: #D4D4D4; --gray-dark: #737373; --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); } * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; } html { position: static; height: auto; overflow: auto; } body { background: var(--black); color: var(--white); line-height: 1.6; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transition: background-color 0.5s ease, color 0.5s ease; min-height: 100vh; overflow-x: hidden; position: relative; } .app { max-width: 1200px; margin: 0 auto; padding: 16px; min-height: 100vh; position: relative; width: 100%; } /* Credit System */ .credits-container { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; display: flex; align-items: center; gap: 12px; cursor: pointer; transition: all 0.2s ease; } .credits-container:hover { border-color: var(--brand); transform: translateY(-1px); } .credits-progress { flex: 1; min-width: 80px; } .credits-bar { width: 100%; height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; margin-top: 4px; } .credits-fill { height: 100%; background: var(--gradient-brand); border-radius: 2px; transition: width 0.5s ease; } .credits-text { font-size: 12px; color: var(--gray); } .credits-number { font-weight: 700; color: var(--brand); } /* Dynamic Content Loading */ .loading-placeholder { background: var(--black-card); border-radius: 8px; padding: 16px; text-align: center; color: var(--gray); font-size: 14px; } .loading-animation { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--border); border-radius: 50%; border-top-color: var(--brand); animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Premium Indicators */ .premium-badge { position: absolute; top: -4px; right: -4px; background: var(--gradient-brand); color: var(--white); border-radius: 50%; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; } .premium-indicator { color: var(--gold); font-weight: 700; font-size: 12px; } /* Model Organization */ .model-section { margin-bottom: 16px; } .model-section-title { font-size: 10px; font-weight: 600; color: var(--gray); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; padding-left: 8px; } .model-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; } .model-option { background: var(--black-card); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 12px; text-align: center; position: relative; } .model-option:hover { border-color: var(--brand); transform: translateY(-1px); } .model-option.active { background: rgba(93, 92, 222, 0.1); border-color: var(--brand); } .model-name { font-size: 16px; font-weight: 600; margin-bottom: 2px; } .model-cost { font-size: 14px; text-transform: uppercase; opacity: 0.8; } .model-cost.free { color: var(--success); } .model-cost.paid { color: var(--warning); } /* Persona Cards */ .persona-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; } .persona-card { background: var(--black-card); border: 2px solid var(--border); border-radius: 12px; padding: 16px 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; position: relative; } .persona-card:hover { border-color: var(--brand); transform: translateY(-2px); } .persona-card.active { border-color: var(--brand); background: rgba(93, 92, 222, 0.05); } .persona-card.premium { border-color: var(--gold); } .persona-avatar { width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; color: var(--white); position: relative; } .persona-name { font-weight: 600; font-size: 18px; margin-bottom: 4px; } .persona-desc { font-size: 13px; color: var(--gray); line-height: 1.3; } /* Chat Interface */ .chat-container { background: var(--black-soft); border: 3px solid var(--border); border-radius: 20px; height: 500px; display: flex; flex-direction: column; overflow: hidden; } .chat-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; background: var(--black-card); } .chat-title { font-weight: 600; display: flex; align-items: center; gap: 8px; font-size: 14px; flex: 1; } .chat-controls { display: flex; gap: 6px; } .chat-control-btn { background: transparent; border: 1px solid var(--border); padding: 6px 8px; border-radius: 6px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 12px; } .chat-control-btn:hover { border-color: var(--brand); color: var(--brand); } .chat-messages { flex: 1; overflow-y: auto; padding: 16px; scroll-behavior: smooth; } .message { margin-bottom: 16px; animation: slideIn 0.3s ease-out; } @keyframes slideIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } .message-header { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; font-size: 12px; color: var(--gray); } .message-avatar { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 600; } .message.user .message-avatar { background: var(--gradient-brand); color: var(--white); } [data-theme="light"] .message.user .message-avatar { color: var(--black); } .message.bot .message-avatar { background: var(--gradient-success); color: var(--white); } [data-theme="light"] .message.bot .message-avatar { color: var(--black); } .message-bubble { padding: 12px 16px; border-radius: 18px; font-size: 14px; line-height: 1.5; max-width: 85%; word-wrap: break-word; } .message.user .message-bubble { margin-left: auto; background: var(--gradient-brand); color: var(--white); } [data-theme="light"] .message.user .message-bubble { color: var(--black); } .message.bot .message-bubble { background: linear-gradient(135deg, var(--black-card) 0%, rgba(93, 92, 222, 0.05) 100%); color: var(--gray-light); border: 1px solid var(--border); } .chat-input-area { padding: 16px; border-top: 1px solid var(--border); background: var(--black-card); flex-shrink: 0; } .chat-input-container { display: flex; gap: 8px; align-items: flex-end; } .chat-input { flex: 1; background: var(--black); border: 1px solid var(--border); border-radius: 12px; padding: 12px; font-size: 16px; color: var(--white); resize: none; min-height: 40px; max-height: 100px; transition: border-color 0.2s ease; font-family: inherit; } .chat-input:focus { outline: none; border-color: var(--brand); } .chat-input::placeholder { color: var(--gray-dark); } .send-button { background: var(--gradient-brand); border: none; padding: 12px 16px; border-radius: 12px; color: var(--white); font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; gap: 6px; min-height: 40px; } [data-theme="light"] .send-button { color: var(--black); } .send-button:hover:not(:disabled) { transform: translateY(-1px); } .send-button:disabled { opacity: 0.5; cursor: not-allowed; } /* Header */ .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px; flex-wrap: wrap; gap: 16px; } .logo { font-size: 20px; font-weight: 700; background: var(--gradient-brand); -webkit-background-clip: text; -webkit-text-fill-color: transparent; flex-shrink: 0; } .header-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } .theme-toggle { background: var(--black-card); border: 1px solid var(--border); border-radius: 50px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 14px; display: flex; align-items: center; gap: 8px; white-space: nowrap; flex-shrink: 0; } .theme-toggle:hover { border-color: var(--brand); } /* Explainer Section */ .explainer { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 24px; text-align: center; } .explainer h1 { font-size: 20px; font-weight: 600; color: var(--brand); margin-bottom: 8px; } .explainer p { font-size: 14px; color: var(--gray-light); line-height: 1.5; } /* Credits Modal */ .modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 9000; opacity: 0; visibility: hidden; transition: all 0.3s ease; padding: 16px; } .modal.active { opacity: 1; visibility: visible; } .modal-content { background: var(--black-soft); border: 1px solid var(--border); border-radius: 20px; padding: 24px; max-width: 500px; width: 100%; max-height: 80vh; overflow-y: auto; } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .modal-title { font-size: 20px; font-weight: 700; margin: 0; } .modal-close { background: none; border: none; color: var(--gray); font-size: 20px; cursor: pointer; padding: 4px; } .modal-close:hover { color: var(--white); } .pricing-option { background: var(--black-card); border: 2px solid var(--border); border-radius: 16px; padding: 20px; margin-bottom: 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; } .pricing-option:hover { border-color: var(--brand); transform: translateY(-2px); } .pricing-option.recommended { border-color: var(--success); position: relative; } .pricing-option.recommended::before { content: "RECOMMENDED"; position: absolute; top: -8px; left: 50%; transform: translateX(-50%); background: var(--success); color: var(--white); padding: 4px 8px; border-radius: 12px; font-size: 9px; font-weight: 700; } .pricing-title { font-size: 16px; font-weight: 600; margin-bottom: 6px; } .pricing-price { font-size: 24px; font-weight: 700; color: var(--success); margin-bottom: 8px; } .pricing-features { list-style: none; text-align: left; color: var(--gray-light); margin-bottom: 12px; } .pricing-features li { padding: 3px 0; display: flex; align-items: center; gap: 6px; font-size: 12px; } .pricing-features li::before { content: "✓"; color: var(--success); font-weight: bold; } .action-button { background: var(--gradient-brand); border: none; padding: 12px 12px; border-radius: 16px; color: var(--white); font-weight: 600; font-size: 16px; cursor: pointer; transition: all 0.2s ease; width: 70%; } [data-theme="light"] .action-button { color: var(--black); } .action-button:hover { transform: translateY(-2px); box-shadow: var(--shadow-glow); } /* Typing indicator */ .typing-indicator { display: inline-flex; align-items: center; gap: 2px; } .typing-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--brand); animation: typing 1.4s infinite; } .typing-dot:nth-child(2) { animation-delay: 0.2s; } .typing-dot:nth-child(3) { animation-delay: 0.4s; } @keyframes typing { 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; } 30% { transform: translateY(-10px); opacity: 1; } } /* Error States */ .error-message { background: rgba(239, 68, 68, 0.1); border: 1px solid var(--accent); border-radius: 8px; padding: 12px; margin: 8px 0; color: var(--accent); font-size: 14px; display: none; } .error-message.show { display: block; animation: errorSlide 0.3s ease-out; } @keyframes errorSlide { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } /* Mobile Optimizations */ @media (max-width: 768px) { .app { padding: 12px; } .header { flex-direction: column; align-items: stretch; text-align: center; margin-bottom: 20px; } .header-actions { justify-content: center; gap: 8px; } .model-grid { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 6px; } .persona-grid { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; } .chat-container { height: 400px; } .explainer h3 { font-size: 16px; } .explainer p { font-size: 13px; } } </style> </head> <body data-theme="light"> <div class="app"> <!-- Header --> <header class="header"> <div class="logo">BrutallyHonest.ai</div> <div class="header-actions"> <!-- Credits Display --> <div class="credits-container" id="creditsDisplay"> <div class="credits-progress"> <div class="credits-text"> <span class="credits-number" id="creditsAmount">483</span> / <span id="creditsMax">500</span> credits </div> <div class="credits-bar"> <div class="credits-fill" id="creditsFill" style="width: 96.6%" ></div> </div> </div> <div style="font-size: 12px; color: var(--brand); font-weight: 600;" > Upgrade </div> </div> <!-- Theme Toggle --> <div class="theme-toggle" id="themeToggle"> <span id="themeIcon">☀️</span> <span id="themeText">Light</span> </div> </div> </header> <!-- Explainer --> <section class="explainer"> <h3>🔥 The AI That Has Transformed Thousands</h3> <p> Choose your AI personality below, pick a model that powers the conversation, and start chatting. <strong>Free models are unlimited.</strong> Premium models cost credits but deliver breakthrough insights. </p> </section> <!-- Error Display --> <div class="error-message" id="errorMessage"> <!-- Error content will be populated by JavaScript --> </div> <!-- Main Chat Interface --> <div class="chat-container"> <div class="chat-header"> <div class="chat-title"> <span id="currentPersonaEmoji">🔥</span> <span id="currentPersonaName">Loading...</span> </div> <div class="chat-controls"> <button class="chat-control-btn" id="saveBtn">💾</button> <button class="chat-control-btn" id="clearBtn">🗑️</button> <button class="chat-control-btn" id="exportBtn">📤</button> </div> </div> <div class="chat-messages" id="chatMessages"> <div class="message bot"> <div class="message-header"> <div class="message-avatar">🔥</div> <span>Loading...</span> <span>•</span> <span>Just now</span> </div> <div class="message-bubble"> Welcome! Configuring your experience... </div> </div> </div> <div class="chat-input-area"> <div class="chat-input-container"> <textarea class="chat-input" id="chatInput" placeholder="Type your message here..." rows="1" ></textarea> <button class="send-button" id="sendButton"> <span id="sendButtonText">Send</span> <span id="sendButtonIcon">→</span> </button> </div> </div> </div> <!-- Model Selection --> <div style="margin: 20px 0;" id="modelSelectionContainer"> <h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);"> Select AI Model </h4> <div class="loading-placeholder" id="modelsLoadingPlaceholder"> <div class="loading-animation"></div> Loading models... </div> <div id="modelsContainer"></div> </div> <!-- Persona Selection --> <div style="margin: 20px 0;" id="personaSelectionContainer"> <h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);"> Choose Your AI Personality </h4> <div class="loading-placeholder" id="personasLoadingPlaceholder"> <div class="loading-animation"></div> Loading personalities... </div> <div class="persona-grid" id="personaGrid"></div> </div> </div> <!-- Credits Modal --> <div class="modal" id="creditsModal"> <div class="modal-content"> <div class="modal-header"> <h2 class="modal-title">💎 Get More Credits</h2> <button class="modal-close" id="closeCreditsModal">×</button> </div> <div class="pricing-option recommended"> <div class="pricing-title">Monthly Subscription</div> <div class="pricing-price"> $9<span style="font-size: 12px; color: var(--gray);">/month</span> </div> <ul class="pricing-features"> <li>500 credits per month</li> <li>Unlimited free models</li> <li>Access to all premium models</li> <li>Credits roll over (up to 1000)</li> <li>Priority support</li> </ul> <button class="action-button" onclick="purchaseMonthly()"> Subscribe Now ✨ </button> </div> <div class="pricing-option"> <div class="pricing-title">Pay As You Go</div> <div class="pricing-price">From $2.99</div> <ul class="pricing-features"> <li>100 credits for $2.99</li> <li>250 credits for $5.99</li> <li>500 credits for $9.99</li> <li>Credits never expire</li> </ul> <button class="action-button" onclick="purchaseCredits()"> Buy Credits 💰 </button> </div> </div> </div> <script> // Global state const state = { user: { credits: 10, maxCredits: 1000, userId: generateUserId() }, chat: { currentModel: null, currentModelCost: 0, currentPersona: null, messages: [], isTyping: false }, ui: { currentTheme: 'light' } }; // Configuration const CONFIG = { mainWebhookUrl: 'https://photobar.app.n8n.cloud/webhook-test/bha-mvp', configWebhookUrl: 'https://photobar.app.n8n.cloud/webhook/config', toolRouterUrl: 'https://photobar.app.n8n.cloud/webhook/tool-router' }; // Enhanced Configuration Manager class BHAConfigManager { constructor() { this.config = { personas: [], models: [], modelsByTier: { free: [], standard: [], premium: [] }, features: {} }; this.isLoaded = false; } async loadConfiguration() { try { console.log('Loading configuration from:', CONFIG.configWebhookUrl); const response = await fetch(CONFIG.configWebhookUrl); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } this.config = await response.json(); this.isLoaded = true; console.log('Configuration loaded:', this.config); this.renderDynamicContent(); this.initializeDefaults(); } catch (error) { console.error('Config load failed:', error); this.loadFallbackConfig(); } } loadFallbackConfig() { console.log('Loading fallback configuration...'); this.config = { personas: [ { id: 'QuickResonanceSpark_Persona_Haiku', name: 'Brutally Honest', emoji: '🔥', description: 'No BS, just raw truth', isPremium: false, gradientColor: 'linear-gradient(135deg, #EF4444, #F59E0B)' } ], models: [ { id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free' } ], modelsByTier: { free: [{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: 0, tier: 'Free' }], standard: [], premium: [] }, features: { darkModeEnabled: true, creditsSystemEnabled: true } }; this.isLoaded = true; this.renderDynamicContent(); this.initializeDefaults(); } renderDynamicContent() { this.renderPersonas(); this.renderModels(); } renderPersonas() { const container = document.getElementById('personaGrid'); const loadingPlaceholder = document.getElementById('personasLoadingPlaceholder'); if (loadingPlaceholder) { loadingPlaceholder.style.display = 'none'; } container.innerHTML = this.config.personas.map(persona => ` <div class="persona-card ${persona.isPremium ? 'premium' : ''}" data-persona="${persona.id}" data-name="${persona.name}" data-emoji="${persona.emoji}"> <div class="persona-avatar" style="background: ${persona.gradientColor};"> ${persona.emoji} ${persona.isPremium ? '<div class="premium-badge">💎</div>' : ''} </div> <div class="persona-name">${persona.name}</div> <div class="persona-desc">${persona.description}</div> </div> `).join(''); // Add event listeners document.querySelectorAll('.persona-card').forEach(card => { card.addEventListener('click', () => this.selectPersona(card)); }); } renderModels() { const container = document.getElementById('modelsContainer'); const loadingPlaceholder = document.getElementById('modelsLoadingPlaceholder'); if (loadingPlaceholder) { loadingPlaceholder.style.display = 'none'; } const tiers = ['free', 'standard', 'premium']; const tierLabels = { 'free': 'Free Models', 'standard': 'Standard Models (5-15 credits)', 'premium': 'Premium Models (15+ credits)' }; container.innerHTML = tiers.map(tier => { const models = this.config.modelsByTier[tier] || []; if (models.length === 0) return ''; return ` <div class="model-section" data-tier="${tier}"> <div class="model-section-title">${tierLabels[tier]}</div> <div class="model-grid"> ${models.map(model => ` <div class="model-option" data-model="${model.id}" data-cost="${model.cost}"> <div class="model-name">${model.name}</div> <div class="model-cost ${model.cost === 0 ? 'free' : 'paid'}"> ${model.cost === 0 ? 'FREE' : `${model.cost} credits`} </div> </div> `).join('')} </div> </div> `; }).join(''); // Add event listeners document.querySelectorAll('.model-option').forEach(option => { option.addEventListener('click', () => this.selectModel(option)); }); } selectPersona(personaElement) { // Remove active from all document.querySelectorAll('.persona-card').forEach(el => el.classList.remove('active')); personaElement.classList.add('active'); // Update state state.chat.currentPersona = personaElement.dataset.persona; // Update chat header document.getElementById('currentPersonaEmoji').textContent = personaElement.dataset.emoji; document.getElementById('currentPersonaName').textContent = personaElement.dataset.name; // Save to localStorage localStorage.setItem('selectedPersona', state.chat.currentPersona); console.log('Persona selected:', state.chat.currentPersona); } selectModel(modelElement) { // Remove active from all document.querySelectorAll('.model-option').forEach(el => el.classList.remove('active')); modelElement.classList.add('active'); // Update state state.chat.currentModel = modelElement.dataset.model; state.chat.currentModelCost = parseInt(modelElement.dataset.cost); // Save to localStorage localStorage.setItem('selectedModel', state.chat.currentModel); console.log('Model selected:', state.chat.currentModel, 'Cost:', state.chat.currentModelCost); } initializeDefaults() { // Select first persona by default or restore from localStorage const savedPersona = localStorage.getItem('selectedPersona'); const savedModel = localStorage.getItem('selectedModel'); // Initialize persona if (this.config.personas.length > 0) { const targetPersona = savedPersona && document.querySelector(`[data-persona="${savedPersona}"]`) || document.querySelector('.persona-card'); if (targetPersona) { this.selectPersona(targetPersona); } } // Initialize model if (this.config.models.length > 0) { const targetModel = savedModel && document.querySelector(`[data-model="${savedModel}"]`) || document.querySelector('.model-option'); if (targetModel) { this.selectModel(targetModel); } } // Update welcome message this.updateWelcomeMessage(); } updateWelcomeMessage() { const messagesContainer = document.getElementById('chatMessages'); const currentPersona = this.config.personas.find(p => p.id === state.chat.currentPersona); if (currentPersona) { messagesContainer.innerHTML = ` <div class="message bot"> <div class="message-header"> <div class="message-avatar">${currentPersona.emoji}</div> <span>${currentPersona.name}</span> <span>•</span> <span>Just now</span> </div> <div class="message-bubble"> I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today? </div> </div> `; } } } // Utility functions function generateUserId() { const stored = localStorage.getItem('bha_user_id'); if (stored) return stored; const id = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); localStorage.setItem('bha_user_id', id); return id; } function showError(message) { const errorEl = document.getElementById('errorMessage'); errorEl.textContent = message; errorEl.classList.add('show'); setTimeout(() => { errorEl.classList.remove('show'); }, 5000); } // Theme system function toggleTheme() { const newTheme = state.ui.currentTheme === 'light' ? 'dark' : 'light'; state.ui.currentTheme = newTheme; document.body.dataset.theme = newTheme; const icon = document.getElementById('themeIcon'); const text = document.getElementById('themeText'); if (newTheme === 'light') { icon.textContent = '☀️'; text.textContent = 'Light'; } else { icon.textContent = '🌙'; text.textContent = 'Dark'; } localStorage.setItem('selectedTheme', newTheme); } // Credits system function updateCreditsDisplay() { const creditsAmount = document.getElementById('creditsAmount'); const creditsMax = document.getElementById('creditsMax'); const creditsFill = document.getElementById('creditsFill'); creditsAmount.textContent = state.user.credits; creditsMax.textContent = state.user.maxCredits; const percentage = (state.user.credits / state.user.maxCredits) * 100; creditsFill.style.width = percentage + '%'; } function showCreditsModal() { document.getElementById('creditsModal').classList.add('active'); } function hideCreditsModal() { document.getElementById('creditsModal').classList.remove('active'); } function purchaseMonthly() { window.open(`https://buy.stripe.com/YOUR_MONTHLY_LINK?client_reference_id=${state.user.userId}`, '_blank'); } function purchaseCredits() { window.open(`https://buy.stripe.com/YOUR_CREDIT_PACK_LINK?client_reference_id=${state.user.userId}`, '_blank'); } // Chat functions function addMessage(content, isUser = false, persona = null) { const messagesContainer = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); messageDiv.className = `message ${isUser ? 'user' : 'bot'}`; const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); const sender = isUser ? 'You' : (persona || 'AI'); const avatar = isUser ? '👤' : (getPersonaEmoji(state.chat.currentPersona) || '🤖'); // Process markdown for bot messages const processedContent = isUser ? escapeHtml(content) : (typeof marked !== 'undefined' ? marked.parse(content) : content); messageDiv.innerHTML = ` <div class="message-header"> <div class="message-avatar">${avatar}</div> <span>${sender}</span> <span>•</span> <span>${timestamp}</span> </div> <div class="message-bubble">${processedContent}</div> `; messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; // Store in state state.chat.messages.push({ content: content, isUser: isUser, timestamp: Date.now(), persona: persona }); } function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function addTypingIndicator() { const messagesContainer = document.getElementById('chatMessages'); const typingDiv = document.createElement('div'); typingDiv.id = 'typingIndicator'; typingDiv.className = 'message bot'; const persona = getPersonaName(state.chat.currentPersona); const avatar = getPersonaEmoji(state.chat.currentPersona); typingDiv.innerHTML = ` <div class="message-header"> <div class="message-avatar">${avatar}</div> <span>${persona}</span> <span>•</span> <span>typing...</span> </div> <div class="message-bubble"> <div class="typing-indicator"> <div class="typing-dot"></div> <div class="typing-dot"></div> <div class="typing-dot"></div> </div> </div> `; messagesContainer.appendChild(typingDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; } function removeTypingIndicator() { const indicator = document.getElementById('typingIndicator'); if (indicator) { indicator.remove(); } } function getPersonaName(personaId) { const persona = configManager.config.personas.find(p => p.id === personaId); return persona ? persona.name : 'AI'; } function getPersonaEmoji(personaId) { const persona = configManager.config.personas.find(p => p.id === personaId); return persona ? persona.emoji : '🤖'; } // Send message async function sendMessage() { const input = document.getElementById('chatInput'); const message = input.value.trim(); if (!message || state.chat.isTyping) return; // Check credits if (state.chat.currentModelCost > 0 && state.user.credits < state.chat.currentModelCost) { showError(`Insufficient credits! You need ${state.chat.currentModelCost} credits to use this model.`); showCreditsModal(); return; } // Add user message addMessage(message, true); // Clear input input.value = ''; input.style.height = 'auto'; // Show typing indicator addTypingIndicator(); state.chat.isTyping = true; // Disable send button const sendBtn = document.getElementById('sendButton'); sendBtn.disabled = true; document.getElementById('sendButtonText').textContent = 'Sending'; document.getElementById('sendButtonIcon').innerHTML = '<div class="loading-animation"></div>'; try { // Prepare webhook payload const payload = { user_id: state.user.userId, bot_to_call: state.chat.currentPersona, full_prompt_for_llm: message, raw_user_situation: message, selected_model_for_openrouter: state.chat.currentModel, conversation_id: 'conv_' + Date.now(), timestamp: new Date().toISOString() }; console.log('Sending payload:', payload); // Send to webhook const response = await fetch(CONFIG.mainWebhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); console.log('Response received:', data); // Extract AI response const aiResponse = data.bot_response?.analysis || data.final_response_payload?.bot_response?.analysis || "I'm having trouble responding right now. Please try again."; // Remove typing indicator removeTypingIndicator(); // Add AI response addMessage(aiResponse, false, getPersonaName(state.chat.currentPersona)); // Update credits if cost was involved if (data.credits_remaining !== undefined) { state.user.credits = data.credits_remaining; } else if (state.chat.currentModelCost > 0) { state.user.credits = Math.max(0, state.user.credits - state.chat.currentModelCost); } updateCreditsDisplay(); // Show tool indicator if tool was used if (data.tool_used) { console.log('Tool was used in this response'); } } catch (error) { console.error('Error sending message:', error); removeTypingIndicator(); showError('Failed to send message. Please check your connection and try again.'); } finally { state.chat.isTyping = false; sendBtn.disabled = false; document.getElementById('sendButtonText').textContent = 'Send'; document.getElementById('sendButtonIcon').textContent = '→'; } } // Chat controls function saveConversation() { if (state.chat.messages.length === 0) { alert('No messages to save yet!'); return; } // In production, this would save to user's account alert('💾 Conversation saved! (Feature coming soon)'); } function clearChat() { if (confirm('Clear all messages? This cannot be undone!')) { state.chat.messages = []; configManager.updateWelcomeMessage(); } } function exportChat() { if (state.chat.messages.length === 0) { alert('No messages to export yet!'); return; } let content = `BrutallyHonest.ai Chat Export\n`; content += `Exported: ${new Date().toLocaleString()}\n`; content += `Model: ${state.chat.currentModel}\n`; content += `Persona: ${getPersonaName(state.chat.currentPersona)}\n`; content += '='.repeat(50) + '\n\n'; state.chat.messages.forEach(msg => { const timestamp = new Date(msg.timestamp).toLocaleString(); const speaker = msg.isUser ? 'You' : (msg.persona || 'AI'); content += `[${timestamp}] ${speaker}: ${msg.content}\n\n`; }); const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `BHA_Chat_${new Date().toISOString().split('T')[0]}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } // Initialize configuration manager const configManager = new BHAConfigManager(); // Initialize async function init() { console.log("🔥 BrutallyHonest.ai Initializing..."); // Check for saved theme const savedTheme = localStorage.getItem('selectedTheme') || 'light'; state.ui.currentTheme = savedTheme; document.body.dataset.theme = savedTheme; const icon = document.getElementById('themeIcon'); const text = document.getElementById('themeText'); if (savedTheme === 'dark') { icon.textContent = '🌙'; text.textContent = 'Dark'; } // Set up event listeners document.getElementById('themeToggle').addEventListener('click', toggleTheme); document.getElementById('creditsDisplay').addEventListener('click', showCreditsModal); document.getElementById('closeCreditsModal').addEventListener('click', hideCreditsModal); // Chat controls document.getElementById('saveBtn').addEventListener('click', saveConversation); document.getElementById('clearBtn').addEventListener('click', clearChat); document.getElementById('exportBtn').addEventListener('click', exportChat); // Send message document.getElementById('sendButton').addEventListener('click', sendMessage); document.getElementById('chatInput').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); // Auto-resize textarea document.getElementById('chatInput').addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 100) + 'px'; }); // Close modal when clicking outside window.addEventListener('click', (e) => { const modal = document.getElementById('creditsModal'); if (e.target === modal) { hideCreditsModal(); } }); // Initialize displays updateCreditsDisplay(); // Load configuration and render dynamic content await configManager.loadConfiguration(); console.log('✅ Initialization complete!'); } // Global functions for HTML onclick events window.purchaseMonthly = purchaseMonthly; window.purchaseCredits = purchaseCredits; // Initialize when ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } </script> </body> </html>
super-embed:
<!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"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <title>Brutally Honest AI - Transform Your Bullsh*t Into Breakthroughs</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style> :root { /* Light Theme (Default) */ --black: #FFFFFF; --black-soft: #F8F9FA; --black-card: #F1F3F4; --border: #E8EAED; --border-light: #DADCE0; --white: #000000; --gray: #5F6368; --gray-light: #3C4043; --gray-dark: #80868B;
- -brand: #5D5CDE; --brand-light: #7C7CE8; --brand-dark: #4B4BC7; --accent: #EF4444; --success: #22C55E; --warning: #F59E0B; --gold: #FCD34D;
- -gradient-brand: linear-gradient(135deg, #5D5CDE 0%, #7C7CE8 100%); --gradient-fire: linear-gradient(135deg, #EF4444 0%, #F59E0B 100%); --gradient-success: linear-gradient(135deg, #22C55E 0%, #4ADE80 100%);
- -shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.08); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.12), 0 1px 2px -1px rgb(0 0 0 / 0.12); --shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06); --shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05); --shadow-glow: 0 0 0 1px rgba(93, 92, 222, 0.3), 0 0 20px rgba(93, 92, 222, 0.2); }
/* Dark Theme Override */ [data-theme="dark"] { --black: #0F0F0F; --black-soft: #1A1A1A; --black-card: #262626; --border: #404040; --border-light: #525252; --white: #FFFFFF; --gray: #A3A3A3; --gray-light: #D4D4D4; --gray-dark: #737373;
- -shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); }
- { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html { position: static; height: auto; overflow: auto; }
body { background: var(--black); color: var(--white); line-height: 1.6; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transition: background-color 0.5s ease, color 0.5s ease; min-height: 100vh; overflow-x: hidden; position: relative; }
.app { max-width: 1200px; margin: 0 auto; padding: 16px; min-height: 100vh; position: relative; width: 100%; }
/* Credit System */ .credits-container { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; display: flex; align-items: center; gap: 12px; cursor: pointer; transition: all 0.2s ease; }
.credits-container:hover { border-color: var(--brand); transform: translateY(-1px); }
.credits-progress { flex: 1; min-width: 80px; }
.credits-bar { width: 100%; height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; margin-top: 4px; }
.credits-fill { height: 100%; background: var(--gradient-brand); border-radius: 2px; transition: width 0.5s ease; }
.credits-text { font-size: 12px; color: var(--gray); }
.credits-number { font-weight: 700; color: var(--brand); }
/* Model Organization */ .model-section { margin-bottom: 16px; }
.model-section-title { font-size: 10px; font-weight: 600; color: var(--gray); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; padding-left: 8px; }
.model-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
.model-option { background: var(--black-card); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 12px; text-align: center; position: relative; }
.model-option:hover { border-color: var(--brand); transform: translateY(-1px); }
.model-option.active { background: rgba(93, 92, 222, 0.1); border-color: var(--brand); }
.model-name { font-size: 16px; font-weight: 600; margin-bottom: 2px; }
.model-cost { font-size: 14px; text-transform: uppercase; opacity: 0.8; }
.model-cost.free { color: var(--success); }
.model-cost.paid { color: var(--warning); }
/* Persona Cards */ .persona-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
.persona-card { background: var(--black-card); border: 2px solid var(--border); border-radius: 12px; padding: 16px 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; }
.persona-card:hover { border-color: var(--brand); transform: translateY(-2px); }
.persona-card.active { border-color: var(--brand); background: rgba(93, 92, 222, 0.05); }
.persona-avatar { width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; color: var(--white); }
.persona-name { font-weight: 600; font-size: 18px; margin-bottom: 4px; }
.persona-desc { font-size: 13px; color: var(--gray); line-height: 1.3; }
/* Chat Interface */ .chat-container { background: var(--black-soft); border: 3px solid var(--border); border-radius: 20px; height: 500px; display: flex; flex-direction: column; overflow: hidden; }
.chat-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; background: var(--black-card); }
.chat-title { font-weight: 600; display: flex; align-items: center; gap: 8px; font-size: 14px; flex: 1; }
.chat-controls { display: flex; gap: 6px; }
.chat-control-btn { background: transparent; border: 1px solid var(--border); padding: 6px 8px; border-radius: 6px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 12px; }
.chat-control-btn:hover { border-color: var(--brand); color: var(--brand); }
.chat-messages { flex: 1; overflow-y: auto; padding: 16px; scroll-behavior: smooth; }
.message { margin-bottom: 16px; animation: slideIn 0.3s ease-out; }
@keyframes slideIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.message-header { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; font-size: 12px; color: var(--gray); }
.message-avatar { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 600; }
.message.user .message-avatar { background: var(--gradient-brand); color: var(--white); }
[data-theme="light"] .message.user .message-avatar { color: var(--black); }
.message.bot .message-avatar { background: var(--gradient-success); color: var(--white); }
[data-theme="light"] .message.bot .message-avatar { color: var(--black); }
.message-bubble { padding: 12px 16px; border-radius: 18px; font-size: 14px; line-height: 1.5; max-width: 85%; word-wrap: break-word; }
.message.user .message-bubble { margin-left: auto; background: var(--gradient-brand); color: var(--white); }
[data-theme="light"] .message.user .message-bubble { color: var(--black); }
.message.bot .message-bubble { background: linear-gradient(135deg, var(--black-card) 0%, rgba(93, 92, 222, 0.05) 100%); color: var(--gray-light); border: 1px solid var(--border); }
.chat-input-area { padding: 16px; border-top: 1px solid var(--border); background: var(--black-card); flex-shrink: 0; }
.chat-input-container { display: flex; gap: 8px; align-items: flex-end; }
.chat-input { flex: 1; background: var(--black); border: 1px solid var(--border); border-radius: 12px; padding: 12px; font-size: 16px; color: var(--white); resize: none; min-height: 40px; max-height: 100px; transition: border-color 0.2s ease; font-family: inherit; }
.chat-input:focus { outline: none; border-color: var(--brand); }
.chat-input::placeholder { color: var(--gray-dark); }
.send-button { background: var(--gradient-brand); border: none; padding: 12px 16px; border-radius: 12px; color: var(--white); font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; gap: 6px; min-height: 40px; }
[data-theme="light"] .send-button { color: var(--black); }
.send-button:hover:not(:disabled) { transform: translateY(-1px); }
.send-button:disabled { opacity: 0.5; cursor: not-allowed; }
/* Header */ .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px; flex-wrap: wrap; gap: 16px; }
.logo { font-size: 20px; font-weight: 700; background: var(--gradient-brand); -webkit-background-clip: text; -webkit-text-fill-color: transparent; flex-shrink: 0; }
.header-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.theme-toggle { background: var(--black-card); border: 1px solid var(--border); border-radius: 50px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 14px; display: flex; align-items: center; gap: 8px; white-space: nowrap; flex-shrink: 0; }
.theme-toggle:hover { border-color: var(--brand); }
/* Explainer Section */ .explainer { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 20px; margin-bottom: 24px; text-align: center; }
.explainer h1 { font-size: 20px; font-weight: 600; color: var(--brand); margin-bottom: 8px; }
.explainer p { font-size: 14px; color: var(--gray-light); line-height: 1.5; }
/* Credits Modal */ .modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 9000; opacity: 0; visibility: hidden; transition: all 0.3s ease; padding: 16px; }
.modal.active { opacity: 1; visibility: visible; }
.modal-content { background: var(--black-soft); border: 1px solid var(--border); border-radius: 20px; padding: 24px; max-width: 500px; width: 100%; max-height: 80vh; overflow-y: auto; }
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.modal-title { font-size: 20px; font-weight: 700; margin: 0; }
.modal-close { background: none; border: none; color: var(--gray); font-size: 20px; cursor: pointer; padding: 4px; }
.modal-close:hover { color: var(--white); }
.pricing-option { background: var(--black-card); border: 2px solid var(--border); border-radius: 16px; padding: 20px; margin-bottom: 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; }
.pricing-option:hover { border-color: var(--brand); transform: translateY(-2px); }
.pricing-option.recommended { border-color: var(--success); position: relative; }
.pricing-option.recommended::before { content: "RECOMMENDED"; position: absolute; top: -8px; left: 50%; transform: translateX(-50%); background: var(--success); color: var(--white); padding: 4px 8px; border-radius: 12px; font-size: 9px; font-weight: 700; }
.pricing-title { font-size: 16px; font-weight: 600; margin-bottom: 6px; }
.pricing-price { font-size: 24px; font-weight: 700; color: var(--success); margin-bottom: 8px; }
.pricing-features { list-style: none; text-align: left; color: var(--gray-light); margin-bottom: 12px; }
.pricing-features li { padding: 3px 0; display: flex; align-items: center; gap: 6px; font-size: 12px; }
.pricing-features li::before { content: "✓"; color: var(--success); font-weight: bold; }
.action-button { background: var(--gradient-brand); border: none; padding: 12px 12px; border-radius: 16px; color: var(--white); font-weight: 600; font-size: 16px; cursor: pointer; transition: all 0.2s ease; width: 70%; }
[data-theme="light"] .action-button { color: var(--black); }
.action-button:hover { transform: translateY(-2px); box-shadow: var(--shadow-glow); }
/* Typing indicator */ .typing-indicator { display: inline-flex; align-items: center; gap: 2px; }
.typing-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--brand); animation: typing 1.4s infinite; }
.typing-dot:nth-child(2) { animation-delay: 0.2s; } .typing-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing { 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; } 30% { transform: translateY(-10px); opacity: 1; } }
/* Mobile Optimizations */ @media (max-width: 768px) { .app { padding: 12px; }
.header { flex-direction: column; align-items: stretch; text-align: center; margin-bottom: 20px; }
.header-actions { justify-content: center; gap: 8px; }
.model-grid { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 6px; }
.persona-grid { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
.chat-container { height: 400px; }
.explainer h3 { font-size: 16px; }
.explainer p { font-size: 13px; } } </style> </head>
<body data-theme="light"> <div class="app"> <!-- Header --> <header class="header"> <div class="logo">Brutally Honest AI</div> <div class="header-actions"> <!-- Credits Display --> <div class="credits-container" id="creditsDisplay"> <div class="credits-progress"> <div class="credits-text"> <span class="credits-number" id="creditsAmount">483</span> / 500 credits </div> <div class="credits-bar"> <div class="credits-fill" id="creditsFill" style="width: 96.6%"></div> </div> </div> <div style="font-size: 12px; color: var(--brand); font-weight: 600;"> Upgrade </div> </div>
<!-- Theme Toggle --> <div class="theme-toggle" id="themeToggle"> <span id="themeIcon">☀️</span> <span id="themeText">Light</span> </div> </div> </header>
<!-- Explainer --> <section class="explainer"> <h3> The AI That Has Transformed Thousands</h3> <p> Choose your AI personality below, pick a model that powers the conversation, and start chatting. <strong>Free models are unlimited.</strong> Premium models cost credits but deliver breakthrough insights. </p> </section>
<!-- Main Chat Interface --> <div class="chat-container"> <div class="chat-header"> <div class="chat-title"> <span id="currentPersonaEmoji">🔥</span> <span id="currentPersonaName">Brutally Honest</span> </div> <div class="chat-controls"> <button class="chat-control-btn" id="saveBtn">💾</button> <button class="chat-control-btn" id="clearBtn">🗑️</button> <button class="chat-control-btn" id="exportBtn">📤</button> </div> </div>
<div class="chat-messages" id="chatMessages"> <div class="message bot"> <div class="message-header"> <div class="message-avatar">🔥</div> <span>Brutally Honest</span> <span>•</span> <span>Just now</span> </div> <div class="message-bubble"> I'm here to cut through the bullshit and give you the unfiltered truth you need to hear. No sugar-coating, no coddling. What would you like me to be brutally honest about today? </div> </div> </div>
<div class="chat-input-area"> <div class="chat-input-container"> <textarea class="chat-input" id="chatInput" placeholder="Type your message here..." rows="1"></textarea> <button class="send-button" id="sendButton"> <span id="sendButtonText">Send</span> <span id="sendButtonIcon">→</span> </button> </div> </div> </div>
<!-- Model Selection --> <div style="margin: 20px 0;"> <h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);">Select AI Model</h4>
<div class="model-section"> <div class="model-section-title">Free Models</div> <div class="model-grid"> <div class="model-option active" data-model="gpt-4o-mini" data-cost="0"> <div class="model-name">GPT-4o-mini</div> <div class="model-cost free">Free</div> </div> <div class="model-option" data-model="claude-3-haiku" data-cost="0"> <div class="model-name">Claude 3 Haiku</div> <div class="model-cost free">Free</div> </div> </div> </div>
<div class="model-section"> <div class="model-section-title">Standard Models (5-10 credits)</div> <div class="model-grid"> <div class="model-option" data-model="gpt-4o" data-cost="5"> <div class="model-name">GPT-4o</div> <div class="model-cost paid">5 credits</div> </div> <div class="model-option" data-model="gemini-flash" data-cost="7"> <div class="model-name">Gemini Flash</div> <div class="model-cost paid">7 credits</div> </div> <div class="model-option" data-model="deepseek" data-cost="8"> <div class="model-name">DeepSeek V3</div> <div class="model-cost paid">8 credits</div> </div> </div> </div>
<div class="model-section"> <div class="model-section-title">Premium Models (15+ credits)</div> <div class="model-grid"> <div class="model-option" data-model="claude-3.5-sonnet" data-cost="15"> <div class="model-name">Claude 3.5 Sonnet</div> <div class="model-cost paid">15 credits</div> </div> <div class="model-option" data-model="claude-3-opus" data-cost="25"> <div class="model-name">Claude Opus</div> <div class="model-cost paid">25 credits</div> </div> </div> </div> </div>
<!-- Persona Selection --> <div style="margin: 20px 0;"> <h4 style="font-weight: 600; margin-bottom: 16px; color: var(--brand);">Choose Your AI Personality</h4> <div class="persona-grid"> <div class="persona-card active" data-persona="brutally-honest"> <div class="persona-avatar" style="background: linear-gradient(135deg, #EF4444, #F59E0B);">🔥</div> <div class="persona-name">Brutally Honest</div> <div class="persona-desc">No BS, just raw truth</div> </div>
<div class="persona-card" data-persona="not-therapy"> <div class="persona-avatar" style="background: linear-gradient(135deg, #10B981, #06D6A0);">🧠</div> <div class="persona-name">Not Therapy</div> <div class="persona-desc">Deep insights without the couch</div> </div>
<div class="persona-card" data-persona="real-talk"> <div class="persona-avatar" style="background: linear-gradient(135deg, #F59E0B, #F97316);">💬</div> <div class="persona-name">Real Talk</div> <div class="persona-desc">Straight talk like a friend</div> </div>
<div class="persona-card" data-persona="accept-ai"> <div class="persona-avatar" style="background: linear-gradient(135deg, #3B82F6, #1D4ED8);">🤝</div> <div class="persona-name">Accept.ai</div> <div class="persona-desc">Finding peace in hard truths</div> </div> </div> </div> </div>
<!-- Credits Modal --> <div class="modal" id="creditsModal"> <div class="modal-content"> <div class="modal-header"> <h2 class="modal-title">💎 Get More Credits</h2> <button class="modal-close" id="closeCreditsModal">×</button> </div>
<div class="pricing-option recommended"> <div class="pricing-title">Monthly Subscription</div> <div class="pricing-price">$9<span style="font-size: 12px; color: var(--gray);">/month</span></div> <ul class="pricing-features"> <li>500 credits per month</li> <li>Unlimited free models</li> <li>Access to all premium models</li> <li>Credits roll over (up to 1000)</li> <li>Priority support</li> </ul> <button class="action-button" onclick="purchaseMonthly()">Subscribe Now ✨</button> </div>
<div class="pricing-option"> <div class="pricing-title">Pay As You Go</div> <div class="pricing-price">From $2.99</div> <ul class="pricing-features"> <li>100 credits for $2.99</li> <li>250 credits for $5.99</li> <li>500 credits for $9.99</li> <li>Credits never expire</li> </ul> <button class="action-button" onclick="purchaseCredits()">Buy Credits 💰</button> </div> </div> </div>
<script> // Global state const state = { user: { credits: 483, maxCredits: 500, userId: generateUserId() }, chat: { currentModel: 'gpt-4o', currentModelCost: 0, currentPersona: 'brutally-honest', messages: [], isTyping: false }, ui: { currentTheme: 'light' }, webhookUrl: 'https://photobar.app.n8n.cloud/webhook/bha-mvp' };
// Utility functions function generateUserId() { return 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); }
// Theme system function toggleTheme() { const newTheme = state.ui.currentTheme === 'light' ? 'dark' : 'light'; state.ui.currentTheme = newTheme; document.body.dataset.theme = newTheme;
const icon = document.getElementById('themeIcon'); const text = document.getElementById('themeText');
if (newTheme === 'light') { icon.textContent = '☀️'; text.textContent = 'Light'; } else { icon.textContent = '🌙'; text.textContent = 'Dark'; } }
// Credits system function updateCreditsDisplay() { const creditsAmount = document.getElementById('creditsAmount'); const creditsFill = document.getElementById('creditsFill');
creditsAmount.textContent = state.user.credits; const percentage = (state.user.credits / state.user.maxCredits) * 100; creditsFill.style.width = percentage + '%'; }
function showCreditsModal() { document.getElementById('creditsModal').classList.add('active'); }
function hideCreditsModal() { document.getElementById('creditsModal').classList.remove('active'); }
function purchaseMonthly() { window.open('https://buy.stripe.com/YOUR_MONTHLY_LINK?client_reference_id=' + state.user.userId, '_blank'); }
function purchaseCredits() { window.open('https://buy.stripe.com/YOUR_CREDIT_PACK_LINK?client_reference_id=' + state.user.userId, '_blank'); }
// Model selection function selectModel(modelElement) { // Remove active class from all models document.querySelectorAll('.model-option').forEach(el => el.classList.remove('active')); modelElement.classList.add('active');
// Update state state.chat.currentModel = modelElement.dataset.model; state.chat.currentModelCost = parseInt(modelElement.dataset.cost);
console.log('Model selected:', state.chat.currentModel, 'Cost:', state.chat.currentModelCost); }
// Persona selection function selectPersona(personaElement) { // Remove active class from all personas document.querySelectorAll('.persona-card').forEach(el => el.classList.remove('active')); personaElement.classList.add('active');
// Update state const personaData = { 'brutally-honest': { name: 'Brutally Honest', emoji: '🔥' }, 'not-therapy': { name: 'Not Therapy', emoji: '🧠' }, 'real-talk': { name: 'Real Talk', emoji: '💬' }, 'accept-ai': { name: 'Accept.ai', emoji: '🤝' } };
state.chat.currentPersona = personaElement.dataset.persona; const persona = personaData[state.chat.currentPersona];
// Update chat header document.getElementById('currentPersonaEmoji').textContent = persona.emoji; document.getElementById('currentPersonaName').textContent = persona.name;
// Add system message about persona change
addSystemMessage(Switched to ${persona.name} personality
);
console.log('Persona selected:', state.chat.currentPersona); }
// Chat functions
function addMessage(content, isUser = false, persona = null) {
const messagesContainer = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = message ${isUser ? 'user' : 'bot'}
;
const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); const sender = isUser ? 'You' : (persona || 'AI'); const avatar = isUser ? '👤' : (getPersonaEmoji(state.chat.currentPersona) || '🤖');
messageDiv.innerHTML = <div class="message-header"> <div class="message-avatar">${avatar}</div> <span>${sender}</span> <span>•</span> <span>${timestamp}</span> </div> <div class="message-bubble">${content}</div>
;
messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Store in state state.chat.messages.push({ content: content, isUser: isUser, timestamp: Date.now(), persona: persona }); }
function addSystemMessage(content) { const messagesContainer = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); messageDiv.className = 'message system'; messageDiv.style.textAlign = 'center'; messageDiv.style.fontSize = '12px'; messageDiv.style.color = 'var(--gray)'; messageDiv.style.margin = '8px 0'; messageDiv.innerHTML = content;
messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; }
function addTypingIndicator() { const messagesContainer = document.getElementById('chatMessages'); const typingDiv = document.createElement('div'); typingDiv.id = 'typingIndicator'; typingDiv.className = 'message bot';
const persona = getPersonaName(state.chat.currentPersona); const avatar = getPersonaEmoji(state.chat.currentPersona);
typingDiv.innerHTML = <div class="message-header"> <div class="message-avatar">${avatar}</div> <span>${persona}</span> <span>•</span> <span>typing...</span> </div> <div class="message-bubble"> <div class="typing-indicator"> <div class="typing-dot"></div> <div class="typing-dot"></div> <div class="typing-dot"></div> </div> </div>
;
messagesContainer.appendChild(typingDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; }
function removeTypingIndicator() { const indicator = document.getElementById('typingIndicator'); if (indicator) { indicator.remove(); } }
function getPersonaName(persona) { const personas = { 'brutally-honest': 'Brutally Honest', 'not-therapy': 'Not Therapy', 'real-talk': 'Real Talk', 'accept-ai': 'Accept.ai' }; return personas[persona] || 'AI'; }
function getPersonaEmoji(persona) { const emojis = { 'brutally-honest': '🔥', 'not-therapy': '🧠', 'real-talk': '💬', 'accept-ai': '🤝' }; return emojis[persona] || '🤖'; }
function mapPersonaToBot(persona) { const mapping = { 'brutally-honest': 'BrutallyHonestAI', 'not-therapy': 'NotTherapyBot', 'real-talk': 'TheREALrealtalk', 'accept-ai': 'accept.ai' }; return mapping[persona] || 'BrutallyHonestAI'; }
function mapModelToOpenRouter(model) { const mapping = { 'gpt-4o-mini': 'openai/gpt-4o-mini', 'claude-3-haiku': 'anthropic/claude-3-haiku', 'gpt-4o': 'openai/gpt-4o-mini', 'gemini-flash': 'google/gemini-2.0-flash-001', 'deepseek-v3': 'deepseek/deepseek-chat-v3-0324:free', 'claude-3.5-sonnet': 'anthropic/claude-3.5-sonnet', 'claude-3.7-sonnet': 'anthropic/claude-3.7-sonnet', 'claude-3-opus': 'anthropic/claude-3-opus', 'gemini-pro': 'google/gemini-2.5-pro-preview-05-06', 'claude-sonnet-4': 'anthropic/claude-sonnet-4' }; return mapping[model] || 'anthropic/claude-3-haiku-20240307'; }
// Send message async function sendMessage() { const input = document.getElementById('chatInput'); const message = input.value.trim(); if (!message || state.chat.isTyping) return;
// Check credits if (state.chat.currentModelCost > 0 && state.user.credits < state.chat.currentModelCost) { addSystemMessage('⚠️ Insufficient credits for this model. Please choose a free model or purchase more credits.'); showCreditsModal(); return; }
// Add user message addMessage(message, true);
// Clear input input.value = ''; input.style.height = 'auto';
// Show typing indicator addTypingIndicator(); state.chat.isTyping = true;
try { // Prepare webhook payload const payload = { user_id: state.user.userId, bot_to_call: mapPersonaToBot(state.chat.currentPersona), // ← KEY MAPPING full_prompt_for_llm: message, raw_user_situation: message, selected_tone_key: state.chat.currentPersona.replace('-', ''), selected_model_for_openrouter: mapModelToOpenRouter(state.chat.currentModel), // ← KEY MAPPING timestamp: new Date().toISOString(), conversation_id: 'conv' + Date.now() };
// Send to webhook const response = await fetch(state.webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(payload) });
if (!response.ok) {
throw new Error(HTTP ${response.status}: ${response.statusText}
);
}
const data = await response.json();
// Your n8n returns this structure: const aiResponse = data.bot_response?.analysis || data.final_response_payload?.bot_response?.analysis || "I'm having trouble responding right now.";
// Remove typing indicator removeTypingIndicator();
// Add AI response addMessage(aiResponse, false, getPersonaName(state.chat.currentPersona));
// Deduct credits if needed if (state.chat.currentModelCost > 0) { state.user.credits -= state.chat.currentModelCost; updateCreditsDisplay(); }
} catch (error) { console.error('Error sending message:', error); removeTypingIndicator(); addSystemMessage('❌ Error sending message. Please try again.'); } finally { state.chat.isTyping = false; } }
// Chat controls function saveConversation() { if (state.chat.messages.length === 0) { alert('No messages to save yet!'); return; } alert('💾 Conversation saved! (In production, this would save to your account)'); }
function clearChat() {
if (confirm('Clear all messages? This cannot be undone!')) {
state.chat.messages = [];
const messagesContainer = document.getElementById('chatMessages');
messagesContainer.innerHTML = <div class="message bot"> <div class="message-header"> <div class="message-avatar">${getPersonaEmoji(state.chat.currentPersona)}</div> <span>${getPersonaName(state.chat.currentPersona)}</span> <span>•</span> <span>Just now</span> </div> <div class="message-bubble"> Chat cleared! What's on your mind now? </div> </div>
;
}
}
function exportChat() { if (state.chat.messages.length === 0) { alert('No messages to export yet!'); return; }
let content = Brutally Honest AI Chat Export\\n
;
content += Exported: ${new Date().toLocaleString()}\\n
;
content += Model: ${state.chat.currentModel}\\n
;
content += Persona: ${getPersonaName(state.chat.currentPersona)}\\n
;
content += '='.repeat(50) + '\n\n';
state.chat.messages.forEach(msg => {
const timestamp = new Date(msg.timestamp).toLocaleString();
const speaker = msg.isUser ? 'You' : (msg.persona || 'AI');
content += [${timestamp}] ${speaker}: ${msg.content}\\n\\n
;
});
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = BHA_Chat_${new Date().toISOString().split('T')[0]}.txt
;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Initialize function init() { console.log("🔥 Brutally Honest AI Initializing...");
// Theme detection if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.classList.add('dark'); }
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { if (event.matches) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } });
// Event listeners document.getElementById('themeToggle').addEventListener('click', toggleTheme); document.getElementById('creditsDisplay').addEventListener('click', showCreditsModal); document.getElementById('closeCreditsModal').addEventListener('click', hideCreditsModal);
// Model selection document.querySelectorAll('.model-option').forEach(option => { option.addEventListener('click', () => selectModel(option)); });
// Persona selection document.querySelectorAll('.persona-card').forEach(card => { card.addEventListener('click', () => selectPersona(card)); });
// Chat controls document.getElementById('saveBtn').addEventListener('click', saveConversation); document.getElementById('clearBtn').addEventListener('click', clearChat); document.getElementById('exportBtn').addEventListener('click', exportChat);
// Send message document.getElementById('sendButton').addEventListener('click', sendMessage); document.getElementById('chatInput').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } });
// Auto-resize textarea document.getElementById('chatInput').addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 100) + 'px'; });
// Close modal when clicking outside window.addEventListener('click', (e) => { const modal = document.getElementById('creditsModal'); if (e.target === modal) { hideCreditsModal(); } });
// Initialize displays updateCreditsDisplay();
console.log('✅ Initialization complete!'); }
// Global functions for HTML onclick events window.purchaseMonthly = purchaseMonthly; window.purchaseCredits = purchaseCredits;
// Initialize when ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } </script> </body> </html>
super-embed: <!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"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <title>Brutally Honest AI - Transform Your Bullsh*t Into Breakthroughs</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <style> :root { /* Light Theme (Default) */ --black: #FFFFFF; --black-soft: #F8F9FA; --black-card: #F1F3F4; --border: #E8EAED; --border-light: #DADCE0; --white: #000000; --gray: #5F6368; --gray-light: #3C4043; --gray-dark: #80868B; --brand: #5D5CDE; --brand-light: #7C7CE8; --brand-dark: #4B4BC7; --accent: #EF4444; --success: #22C55E; --warning: #F59E0B; --gold: #FCD34D; --gradient-brand: linear-gradient(135deg, #5D5CDE 0%, #7C7CE8 100%); --gradient-fire: linear-gradient(135deg, #EF4444 0%, #F59E0B 100%); --gradient-success: linear-gradient(135deg, #22C55E 0%, #4ADE80 100%); --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.08); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.12), 0 1px 2px -1px rgb(0 0 0 / 0.12); --shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06); --shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05); --shadow-glow: 0 0 0 1px rgba(93, 92, 222, 0.3), 0 0 20px rgba(93, 92, 222, 0.2); } /* Dark Theme Override */ [data-theme="dark"] { --black: #0F0F0F; --black-soft: #1A1A1A; --black-card: #262626; --border: #404040; --border-light: #525252; --white: #FFFFFF; --gray: #A3A3A3; --gray-light: #D4D4D4; --gray-dark: #737373; --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); } * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; } html { position: static; height: auto; overflow: auto; } body { background: var(--black); color: var(--white); line-height: 1.6; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; transition: background-color 0.5s ease, color 0.5s ease; min-height: 100vh; overflow-x: hidden; position: relative; } .app { max-width: 1200px; margin: 0 auto; padding: 16px; min-height: 100vh; position: relative; width: 100%; } .app * { transition: border-color 0.3s ease, background-color 0.3s ease, color 0.3s ease; } /* Tooltips - FIXED for models */ .tooltip { position: relative; } .tooltip::after { content: attr(data-tooltip); position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%); background: var(--black-card); color: var(--white); padding: 8px 12px; border-radius: 8px; font-size: 11px; white-space: nowrap; border: 1px solid var(--border); box-shadow: var(--shadow-lg); z-index: 10000; opacity: 0; pointer-events: none; transition: opacity 0.2s, transform 0.2s; max-width: 200px; white-space: normal; text-align: center; } .tooltip:hover::after { opacity: 1; transform: translateX(-50%) translateY(-4px); } /* Model tooltips - special positioning */ .model-option.tooltip::after { bottom: auto; top: calc(100% + 8px); left: 0; transform: translateY(0); max-width: 250px; font-size: 10px; line-height: 1.3; } .model-option.tooltip:hover::after { opacity: 1; transform: translateY(4px); } /* Notifications Area */ .notifications-container { position: fixed; top: 20px; right: 20px; z-index: 8888; width: 320px; pointer-events: none; max-height: 60vh; overflow-y: auto; } .notification { background: var(--black-soft); border: 1px solid var(--border); border-radius: 16px; padding: 16px; margin-bottom: 12px; position: relative; box-shadow: var(--shadow-lg); backdrop-filter: blur(8px); animation: slideInRight 0.3s ease-out; pointer-events: all; opacity: 1; transform: translateX(0); transition: all 0.3s ease; } .notification.removing { opacity: 0; transform: translateX(100%); } @keyframes slideInRight { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } } .notification.opoerator { border-color: var(--brand); background: linear-gradient(135deg, rgba(93, 92, 222, 0.1) 0%, rgba(124, 124, 232, 0.05) 100%); } .notification.opoerator::before { content: "🤖 oPOErator Insight"; position: absolute; top: -8px; left: 16px; background: var(--brand); color: var(--white); padding: 4px 8px; border-radius: 8px; font-size: 10px; font-weight: 700; text-transform: uppercase; } .notification-content { font-size: 13px; line-height: 1.5; color: var(--gray-light); margin-top: 8px; } .notification-close { position: absolute; top: 8px; right: 8px; background: none; border: none; color: var(--gray); cursor: pointer; font-size: 16px; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.2s ease; } .notification-close:hover { color: var(--white); background: rgba(255, 255, 255, 0.1); } /* Feedback Button */ .feedback-btn { position: fixed; bottom: 90px; right: 20px; background: var(--gradient-fire); border: none; border-radius: 50%; width: 48px; height: 48px; color: var(--white); font-size: 18px; cursor: pointer; transition: all 0.2s ease; box-shadow: var(--shadow-lg); z-index: 8000; display: flex; align-items: center; justify-content: center; } [data-theme="light"] .feedback-btn { color: var(--black); } .feedback-btn:hover { transform: scale(1.1); box-shadow: var(--shadow-glow); } /* Reset Layout Button */ .reset-layout-btn { position: fixed; bottom: 20px; right: 20px; background: var(--gradient-brand); border: none; border-radius: 50%; width: 56px; height: 56px; color: var(--white); font-size: 20px; cursor: pointer; transition: all 0.2s ease; box-shadow: var(--shadow-lg); z-index: 8000; display: flex; align-items: center; justify-content: center; } [data-theme="light"] .reset-layout-btn { color: var(--black); } .reset-layout-btn:hover { transform: scale(1.1); box-shadow: var(--shadow-glow); } /* Modal Styles - SHARED */ .modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 9000; opacity: 0; visibility: hidden; transition: all 0.3s ease; padding: 16px; } .modal.active { opacity: 1; visibility: visible; } .modal-content { background: var(--black-soft); border: 1px solid var(--border); border-radius: 20px; padding: 24px; max-width: 500px; width: 100%; max-height: 80vh; overflow-y: auto; } .modal-content::-webkit-scrollbar { width: 6px; } .modal-content::-webkit-scrollbar-track { background: var(--black-card); } .modal-content::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .modal-title { font-size: 20px; font-weight: 700; margin: 0; } .modal-close { background: none; border: none; color: var(--gray); font-size: 20px; cursor: pointer; padding: 4px; } .modal-close:hover { color: var(--white); } /* Storage/Archive Modal */ .archive-section { margin-bottom: 24px; } .archive-section h3 { font-size: 16px; font-weight: 600; color: var(--brand); margin-bottom: 12px; display: flex; align-items: center; gap: 8px; } .archive-item { background: var(--black-card); border: 1px solid var(--border); border-radius: 12px; padding: 12px; margin-bottom: 8px; cursor: pointer; transition: all 0.2s ease; } .archive-item:hover { border-color: var(--brand); transform: translateX(4px); } .archive-item-title { font-size: 14px; font-weight: 600; margin-bottom: 4px; } .archive-item-meta { font-size: 11px; color: var(--gray); display: flex; justify-content: space-between; } .empty-state { text-align: center; color: var(--gray); font-style: italic; padding: 20px; } /* Settings Modal */ .settings-section { margin-bottom: 20px; } .settings-section h4 { font-size: 14px; font-weight: 600; color: var(--brand); margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.05em; } .settings-option { display: flex; justify-content: space-between; align-items: center; padding: 12px 0; border-bottom: 1px solid var(--border); } .settings-option:last-child { border-bottom: none; } .settings-option-text { flex: 1; } .settings-option-title { font-size: 14px; font-weight: 600; margin-bottom: 2px; } .settings-option-desc { font-size: 12px; color: var(--gray); } .settings-toggle { background: var(--border); border: none; border-radius: 12px; width: 44px; height: 24px; position: relative; cursor: pointer; transition: all 0.2s ease; } .settings-toggle.active { background: var(--success); } .settings-toggle-dot { position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; background: var(--white); border-radius: 50%; transition: all 0.2s ease; } .settings-toggle.active .settings-toggle-dot { transform: translateX(20px); } /* Credits Modal */ .pricing-option { background: var(--black-card); border: 2px solid var(--border); border-radius: 16px; padding: 20px; margin-bottom: 12px; cursor: pointer; transition: all 0.2s ease; text-align: center; } .pricing-option:hover { border-color: var(--brand); transform: translateY(-2px); } .pricing-option.recommended { border-color: var(--success); position: relative; } .pricing-option.recommended::before { content: "RECOMMENDED"; position: absolute; top: -8px; left: 50%; transform: translateX(-50%); background: var(--success); color: var(--white); padding: 4px 8px; border-radius: 12px; font-size: 9px; font-weight: 700; } .pricing-title { font-size: 16px; font-weight: 600; margin-bottom: 6px; } .pricing-price { font-size: 24px; font-weight: 700; color: var(--success); margin-bottom: 8px; } .pricing-features { list-style: none; text-align: left; color: var(--gray-light); margin-bottom: 12px; } .pricing-features li { padding: 3px 0; display: flex; align-items: center; gap: 6px; font-size: 12px; } .pricing-features li::before { content: "✓"; color: var(--success); font-weight: bold; } .action-button { background: var(--gradient-brand); border: none; padding: 12px 24px; border-radius: 16px; color: var(--white); font-weight: 600; font-size: 16px; cursor: pointer; transition: all 0.2s ease; width: 100%; } [data-theme="light"] .action-button { color: var(--black); } .action-button:hover { transform: translateY(-2px); box-shadow: var(--shadow-glow); } /* Feedback Modal */ .feedback-section { margin-bottom: 20px; } .feedback-section h4 { font-size: 14px; font-weight: 600; color: var(--brand); margin-bottom: 8px; } .feedback-buttons { display: flex; gap: 8px; margin-bottom: 16px; } .feedback-type-btn { flex: 1; padding: 8px 12px; border: 1px solid var(--border); border-radius: 8px; background: transparent; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 12px; } .feedback-type-btn.active { border-color: var(--brand); background: rgba(93, 92, 222, 0.1); color: var(--white); } .feedback-textarea { width: 100%; min-height: 100px; background: var(--black-card); border: 1px solid var(--border); border-radius: 8px; padding: 12px; color: var(--white); font-family: inherit; resize: vertical; } .feedback-textarea:focus { outline: none; border-color: var(--brand); } /* Onboarding System - FIXED */ .onboarding-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.85); backdrop-filter: blur(6px); z-index: 9999; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: all 0.3s ease; pointer-events: none; } .onboarding-overlay.active { opacity: 1; visibility: visible; pointer-events: all; } .onboarding-spotlight { position: absolute; border-radius: 50%; box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.8); transition: all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94); z-index: 10000; pointer-events: none; } .onboarding-tooltip { position: absolute; background: var(--black-card); border: 2px solid var(--brand); border-radius: 16px; padding: 24px; max-width: 350px; box-shadow: var(--shadow-xl); z-index: 10001; } .welcome-modal { background: var(--black-soft); border: 2px solid var(--brand); border-radius: 20px; padding: 32px; text-align: center; max-width: 500px; } .welcome-title { font-size: 28px; font-weight: 700; margin-bottom: 16px; background: var(--gradient-brand); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .welcome-subtitle { font-size: 16px; color: var(--gray-light); margin-bottom: 24px; } .welcome-buttons { display: flex; gap: 12px; justify-content: center; } .welcome-btn { padding: 12px 24px; border-radius: 12px; border: none; cursor: pointer; font-size: 16px; font-weight: 600; transition: all 0.2s ease; } .welcome-btn.tour { background: var(--gradient-brand); color: var(--white); } .welcome-btn.skip { background: transparent; color: var(--gray); border: 1px solid var(--border); } /* Header */ .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px; flex-wrap: wrap; gap: 16px; } .logo { font-size: 20px; font-weight: 700; background: var(--gradient-brand); -webkit-background-clip: text; -webkit-text-fill-color: transparent; flex-shrink: 0; } .header-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } .theme-toggle { background: var(--black-card); border: 1px solid var(--border); border-radius: 50px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 14px; display: flex; align-items: center; gap: 8px; white-space: nowrap; flex-shrink: 0; } .theme-toggle:hover { border-color: var(--brand); } .opoerator-toggle { background: var(--black-card); border: 1px solid var(--border); border-radius: 50px; padding: 8px 12px; cursor: pointer; transition: all 0.2s ease; font-size: 12px; display: flex; align-items: center; gap: 6px; white-space: nowrap; flex-shrink: 0; } .opoerator-toggle:hover { border-color: var(--brand); } .opoerator-toggle.active { border-color: var(--success); background: rgba(34, 197, 94, 0.1); } .toggle-switch { position: relative; width: 32px; height: 16px; background: var(--border); border-radius: 8px; transition: all 0.2s ease; } .toggle-dot { position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; background: var(--gray); border-radius: 50%; transition: all 0.2s ease; } .opoerator-toggle.active .toggle-dot { background: var(--success); transform: translateX(16px); } .credits-display { background: var(--black-card); border: 1px solid var(--border); padding: 8px 16px; border-radius: 50px; font-size: 14px; font-weight: 500; display: flex; align-items: center; gap: 8px; cursor: pointer; transition: all 0.2s ease; flex-shrink: 0; white-space: nowrap; } .credits-display:hover { border-color: var(--gold); transform: translateY(-1px); } .credits-amount { color: var(--gold); font-weight: 700; } .hero { text-align: center; margin-bottom: 40px; } .hero h1 { font-size: clamp(24px, 5vw, 48px); font-weight: 700; line-height: 1.1; margin-bottom: 16px; } .hero h1 .highlight { background: var(--gradient-fire); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .hero p { font-size: clamp(16px, 2.5vw, 18px); color: var(--gray); max-width: 600px; margin: 0 auto; } .status-bar { background: var(--black-soft); border: 1px solid var(--border); border-radius: 12px; padding: 16px; margin-bottom: 24px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px; } .status-item { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--gray); flex: 1; min-width: 120px; justify-content: center; cursor: pointer; transition: all 0.2s ease; } .status-item:hover { color: var(--brand); transform: translateY(-1px); } .status-value { color: var(--brand); font-weight: 600; } .tabs { display: flex; gap: 4px; background: var(--black-soft); padding: 8px; border-radius: 16px; margin-bottom: 24px; overflow-x: auto; scroll-behavior: smooth; } .tab { flex: 1; background: transparent; border: none; color: var(--gray); padding: 12px 16px; border-radius: 12px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; font-size: 14px; display: flex; align-items: center; justify-content: center; gap: 6px; position: relative; white-space: nowrap; flex-shrink: 0; min-width: 100px; } .tab:hover { color: var(--white); background: rgba(255, 255, 255, 0.05); } [data-theme="light"] .tab:hover { background: rgba(0, 0, 0, 0.05); } .tab.active { background: var(--black-card); color: var(--white); box-shadow: var(--shadow); } .tab .badge, .tab .cool { position: absolute; top: -4px; right: 4px; background: var(--success); color: var(--white); font-size: 9px; padding: 2px 5px; border-radius: 20px; font-weight: 700; } .tab .cool { background: var(--gold); } .tab-content { display: none; animation: fadeIn 0.3s ease-out; } .tab-content.active { display: block; } @keyframes fadeIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } } .chat-layout { display: grid; grid-template-columns: 1fr 300px; gap: 20px; margin-bottom: 24px; } .chat-container, .chat-sidebar { resize: both; overflow: auto; min-width: 280px; min-height: 400px; } .chat-container { background: var(--black-soft); border: 1px solid var(--border); border-radius: 20px; height: 500px; display: flex; flex-direction: column; } .chat-container::-webkit-resizer, .chat-sidebar::-webkit-resizer { background: var(--brand); border-radius: 0 0 20px 0; } .chat-header { padding: 16px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } .chat-title { font-weight: 600; display: flex; align-items: center; gap: 8px; font-size: 14px; flex: 1; } .chat-controls { display: flex; gap: 6px; } .chat-control-btn { background: transparent; border: 1px solid var(--border); padding: 6px 8px; border-radius: 6px; color: var(--gray); cursor: pointer; transition: all 0.2s ease; font-size: 12px; } .chat-control-btn:hover { border-color: var(--brand); color: var(--brand); } .chat-messages { flex: 1; overflow-y: auto; padding: 16px; scroll-behavior: smooth; } .message { margin-bottom: 16px; animation: slideIn 0.3s ease-out; } @keyframes slideIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } .message-header { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; font-size: 11px; color: var(--gray); } .message-avatar { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 600; } .message.user .message-avatar { background: var(--gradient-brand); color: var(--white); } [data-theme="light"] .message.user .message-avatar { color: var(--black); } .message.bot .message-avatar { background: var(--gradient-success); color: var(--white); } [data-theme="light"] .message.bot .message-avatar { color: var(--black); } .message-bubble { padding: 12px 16px; border-radius: 18px; font-size: 14px; line-height: 1.5; max-width: 85%; word-wrap: break-word; } .message.user .message-bubble { margin-left: auto; background: var(--gradient-brand); color: var(--white); } [data-theme="light"] .message.user .message-bubble { color: var(--black); } .message.bot .message-bubble { background: linear-gradient(135deg, var(--black-card) 0%, rgba(93, 92, 222, 0.05) 100%); color: var(--gray-light); border: 1px solid var(--border); } .chat-input-area { padding: 16px; border-top: 1px solid var(--border); background: var(--black-card); flex-shrink: 0; } .chat-input-container { display: flex; gap: 8px; align-items: flex-end; } .chat-input { flex: 1; background: var(--black); border: 1px solid var(--border); border-radius: 12px; padding: 12px; font-size: 14px; color: var(--white); resize: none; min-height: 40px; max-height: 100px; transition: border-color 0.2s ease; font-family: inherit; } .chat-input:focus { outline: none; border-color: var(--brand); } .chat-input::placeholder { color: var(--gray-dark); } .send-button { background: var(--gradient-brand); border: none; padding: 12px 16px; border-radius: 12px; color: var(--white); font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; gap: 6px; min-height: 40px; } [data-theme="light"] .send-button { color: var(--black); } .send-button:hover:not(:disabled) { transform: translateY(-1px); } .send-button:disabled { opacity: 0.5; cursor: not-allowed; } .chat-sidebar { display: flex; flex-direction: column; gap: 16px; height: 500px; } .sidebar-card { background: var(--black-soft); border: 1px solid var(--border); border-radius: 16px; padding: 16px; flex: 1; overflow-y: auto; } .sidebar-header { font-size: 12px; font-weight: 600; color: var(--gray); margin-bottom: 12px; display: flex; align-items: center; justify-content: space-between; text-transform: uppercase; } .model-options { display: flex; flex-direction: column; gap: 6px; max-height: 180px; overflow-y: auto; } .model-option { display: flex; align-items: center; justify-content: space-between; padding: 8px; background: var(--black-card); border: 1px solid var(--border); border-radius: 8px; cursor: pointer; transition: all 0.2s ease; font-size: 12px; } .model-option:hover { border-color: var(--brand); } .model-option.active { background: rgba(93, 92, 222, 0.1); border-color: var(--brand); } .persona-options { display: flex; flex-direction: column; gap: 6px; } .persona-option { display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--black-card); border: 1px solid var(--border); border-radius: 12px; cursor: pointer; transition: all 0.2s ease; font-size: 12px; } .persona-option:hover { border-color: var(--brand); } .persona-option.active { background: rgba(93, 92, 222, 0.1); border-color: var(--brand); } /* Bot Info Panel - SCROLLABLE */ .bot-info-panel { background: var(--black-soft); border: 1px solid var(--border); border-radius: 16px; margin-top: 12px; overflow: hidden; max-height: 0; opacity: 0; transition: all 0.4s ease; } .bot-info-panel.open { max-height: 400px; opacity: 1; } .bot-info-content { padding: 20px; max-height: 400px; overflow-y: auto; } .bot-info-content::-webkit-scrollbar { width: 6px; } .bot-info-content::-webkit-scrollbar-track { background: var(--black-card); } .bot-info-content::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } .bot-info-header { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } .bot-info-avatar { background: var(--gradient-brand); width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px; } .bot-info-title { flex: 1; } .bot-info-rating { background: var(--success); color: var(--white); padding: 4px 8px; border-radius: 12px; font-size: 11px; font-weight: 700; } .bot-info-description { font-size: 14px; color: var(--gray-light); margin-bottom: 16px; } .bot-info-section h5 { font-size: 13px; font-weight: 600; color: var(--brand); margin-bottom: 8px; text-transform: uppercase; } .use-cases { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; } .use-case { background: var(--black-card); border: 1px solid var(--border); padding: 4px 8px; border-radius: 8px; font-size: 11px; color: var(--gray-light); } .example-prompts { background: var(--black-card); border: 1px solid var(--border); border-radius: 8px; padding: 12px; } .example-prompt { font-size: 12px; color: var(--gray-light); font-style: italic; margin-bottom: 8px; padding: 8px; background: rgba(93, 92, 222, 0.1); border-left: 3px solid var(--brand); border-radius: 0 8px 8px 0; cursor: pointer; transition: all 0.2s ease; } .example-prompt:hover { background: rgba(93, 92, 222, 0.2); transform: translateX(4px); } .persona-info-btn { background: transparent; border: 1px solid var(--border); border-radius: 50%; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; font-size: 10px; margin-left: 4px; } .persona-info-btn:hover { border-color: var(--brand); color: var(--brand); } /* Transform Tab Content */ .transform-container { background: var(--black-soft); border: 1px solid var(--border); border-radius: 20px; padding: 24px; text-align: center; } .workflow-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; margin-top: 24px; } .workflow-card { background: var(--black-card); border: 2px solid var(--border); border-radius: 16px; padding: 20px; cursor: pointer; transition: all 0.2s ease; text-align: left; } .workflow-card:hover { border-color: var(--brand); transform: translateY(-2px); } .workflow-card h3 { font-size: 18px; font-weight: 700; margin-bottom: 8px; color: var(--brand); } .workflow-card p { font-size: 14px; color: var(--gray-light); line-height: 1.5; margin-bottom: 12px; } .workflow-meta { display: flex; justify-content: space-between; align-items: center; font-size: 12px; } .workflow-cost { color: var(--gold); font-weight: 600; } .workflow-rating { color: var(--success); } .hidden { display: none !important; } /* Mobile Optimizations */ @media (max-width: 768px) { .app { padding: 12px; } .header { flex-direction: column; align-items: stretch; text-align: center; margin-bottom: 20px; } .header-actions { justify-content: center; gap: 8px; } .theme-toggle, .opoerator-toggle, .credits-display { font-size: 12px; padding: 6px 10px; } .chat-layout { grid-template-columns: 1fr; gap: 16px; } .chat-container, .chat-sidebar { resize: none; height: auto; min-height: 300px; } .chat-container { height: 400px; } .chat-sidebar { order: 2; height: auto; max-height: 300px; } .notifications-container { width: calc(100vw - 40px); right: 20px; left: 20px; top: 10px; } .feedback-btn { bottom: 70px; right: 10px; width: 40px; height: 40px; font-size: 16px; } .reset-layout-btn { bottom: 10px; right: 10px; width: 48px; height: 48px; font-size: 16px; } .status-bar { flex-direction: column; gap: 8px; padding: 12px; } .status-item { justify-content: center; min-width: auto; } .hero h1 { font-size: 28px; } .hero p { font-size: 16px; } .workflow-grid { grid-template-columns: 1fr; } } </style> </head> <body data-theme="light"> <!-- Notifications Container --> <div class="notifications-container" id="notificationsContainer"></div> <!-- Archive/Storage Modal --> <div class="modal" id="archiveModal"> <div class="modal-content"> <div class="modal-header"> <h2 class="modal-title">⚡ Your Archive & Storage</h2> <button class="modal-close" id="closeArchiveModal">×</button> </div> <div class="archive-section"> <h3>💬 Saved Conversations</h3> <div id="savedConversations"> <div class="empty-state">No saved conversations yet. Save some chats to see them here!</div> </div> </div> <div class="archive-section"> <h3>🔥 Transformation Results</h3> <div id="savedTransformations"> <div class="empty-state">No transformation results yet. Complete some workflows to see them here!</div> </div> </div> <div class="archive-section"> <h3>⚡ Custom Workflows</h3> <div id="savedWorkflows"> <div class="empty-state">Custom workflows coming soon! 🚀</div> </div> </div> <div class="archive-section"> <h3>📝 Journal Entries</h3> <div id="journalEntries"> <div class="empty-state">Journaling feature coming soon! ✍️</div> </div> </div> <div class="archive-section"> <h3>🔔 Old Notifications</h3> <div id="oldNotifications"> <div class="empty-state">No archived notifications yet.</div> </div> </div> </div> </div> <!-- Credits Modal --> <div class="modal" id="creditsModal"> <div class="modal-content"> <div class="modal-header"> <h2 class="modal-title">💎 Get More Credits</h2> <button class="modal-close" id="closeCreditsModal">×</button> </div> <div class="pricing-option recommended"> <div class="pricing-title">Monthly Premium</div> <div class="pricing-price">$15<span style="font-size: 12px; color: var(--gray);">/month</span></div> <ul class="pricing-features"> <li>100 credits included</li> <li>Unlimited basic chat</li> <li>All transformation workflows</li> <li>Premium AI models</li> <li>Priority support</li> <li>Advanced analytics</li> <li>Custom workflows</li> </ul> <button class="action-button" onclick="purchaseMonthly()">Get Premium ✨</button> </div> <div class="pricing-option"> <div class="pricing-title">Pay As You Go</div> <div class="pricing-price">$10<span style="font-size: 12px; color: var(--gray);">/pack</span></div> <ul class="pricing-features"> <li>50 credits</li> <li>No monthly commitment</li> <li>Access to premium models</li> <li>Credits never expire</li> <li>Perfect for occasional use</li> </ul> <button class="action-button" onclick="purchaseCredits()">Buy Credits 💰</button> </div> </div> </div> <!-- Feedback Modal --> <div class="modal" id="feedbackModal"> <div class="modal-content"> <div class="modal-header"> <h2 class="modal-title">💬 Send Feedback</h2> <button class="modal-close" id="closeFeedbackModal">×</button> </div> <div class="feedback-section"> <h4>What type of feedback is this?</h4> <div class="feedback-buttons"> <button class="feedback-type-btn active" data-type="bug">🐛 Bug Report</button> <button class="feedback-type-btn" data-type="feature">✨ Feature Request</button> <button class="feedback-type-btn" data-type="improvement">💡 Improvement</button> <button class="feedback-type-btn" data-type="general">💬 General</button> </div> </div> <div class="feedback-section"> <h4>Tell us more</h4> <textarea class="feedback-textarea" id="feedbackText" placeholder="Describe your feedback, bug report, or feature request in detail..."></textarea> </div> <div class="feedback-section"> <button class="action-button" id="submitFeedback">Send Feedback 🚀</button> </div> </div> </div> <!-- Feedback Button --> <button class="feedback-btn" id="feedbackBtn" data-tooltip="Send feedback to help improve BHA">💌</button> <!-- Reset Layout Button --> <button class="reset-layout-btn" id="resetLayoutBtn" data-tooltip="Reset window layout">⚡</button> <!-- Onboarding Overlay --> <div class="onboarding-overlay" id="onboardingOverlay"> <div class="welcome-modal" id="welcomeModal"> <h2 class="welcome-title">Welcome to Brutally Honest AI! 🔥</h2> <p class="welcome-subtitle"> Ready to cut through the bullsh*t and find your flow? We can show you around or you can dive right in. </p> <div class="welcome-buttons"> <button class="welcome-btn tour" onclick="startOnboardingTour()">Show me around! ✨</button> <button class="welcome-btn skip" onclick="skipOnboarding()">I'll figure it out 🤠</button> </div> </div> <!-- Tour elements --> <div class="onboarding-spotlight hidden" id="onboardingSpotlight"></div> <div class="onboarding-tooltip hidden" id="onboardingTooltip"> <h3 id="tooltipTitle">🎯 Choose Your Vibe</h3> <p id="tooltipText">Switch between light and dark themes. Most people prefer dark mode for focus sessions.</p> <div style="display: flex; justify-content: space-between; margin-top: 16px;"> <button class="welcome-btn skip" onclick="skipOnboarding()">Skip</button> <button class="welcome-btn tour" onclick="nextOnboardingStep()">Next →</button> </div> </div> </div> <div class="app"> <!-- Header --> <header class="header"> <div class="logo">Brutally Honest AI</div> <div class="header-actions" id="headerActions"> <!-- Theme Toggle --> <div class="theme-toggle tooltip" data-tooltip="Switch between light and dark theme" id="themeToggle"> <span id="themeIcon">☀️</span> <span id="themeText">Light</span> </div> <!-- oPOErator Toggle --> <div class="opoerator-toggle tooltip" data-tooltip="Enable AI insights and behavior analysis" id="opoeratorToggle"> <span>🤖 oPOErator</span> <div class="toggle-switch"> <div class="toggle-dot"></div> </div> </div> <div class="credits-display tooltip" data-tooltip="Click to view your credits and upgrade options" id="creditsDisplay"> 💎 <span class="credits-amount" id="creditsAmount">10.0</span> credits </div> </div> </header> <!-- Hero --> <section class="hero"> <h1>Ditch The <span class="highlight">Bullsh*t</span>, Find Your Flow</h1> <p>Real talk, practical tools & AI integration for authentic transformation</p> </section> <!-- Status Bar --> <div class="status-bar"> <div class="status-item tooltip" data-tooltip="Days you've been actively using the platform" id="streakDays"> 🔥 <span class="status-value">1</span> day streak </div> <div class="status-item tooltip" data-tooltip="Click to view your saved conversations, transformations, and archive" id="breakthroughsBtn"> ⚡ <span class="status-value" id="totalBreakthroughs">0</span> breakthroughs </div> <div class="status-item tooltip" data-tooltip="Your current membership level" id="membershipLevel"> 👤 <span class="status-value">Free</span> member </div> </div> <!-- Tab Navigation --> <nav class="tabs" id="tabNavigation"> <button class="tab active tooltip" data-tab="chat" data-tooltip="Chat with AI using real OpenRouter models"> 💬 Free Chat <span class="badge">FREE</span> </button> <button class="tab tooltip" data-tab="transform" id="transformTab" data-tooltip="Curated transformation workflows designed by experts"> 🔥 Transform <span class="cool">NEW</span> </button> <button class="tab tooltip" data-tab="workflow" data-tooltip="Build custom AI workflows - coming soon!"> ⚡ Workflows <span class="badge">Soon</span> </button> <button class="tab tooltip" data-tab="assistant" data-tooltip="Your personal AI assistant - coming soon!"> 🤖 Assistant <span class="badge">Soon</span> </button> </nav> <!-- Chat Mode --> <main class="tab-content active" id="chat-content"> <div class="chat-layout" id="chatLayout"> <div class="chat-container" id="chatContainer"> <div class="chat-header"> <div class="chat-title"> <span id="currentPersonaEmoji">💊</span> <span id="currentPersonaName">theREALrealtalk</span> <button class="persona-info-btn tooltip" id="personaInfoBtn" data-tooltip="Learn about this AI personality">ℹ️</button> </div> <div class="chat-controls"> <button class="chat-control-btn tooltip" id="saveBtn" data-tooltip="Save conversation">💾</button> <button class="chat-control-btn tooltip" id="clearBtn" data-tooltip="Clear all messages">🗑️</button> <button class="chat-control-btn tooltip" id="exportBtn" data-tooltip="Export chat as text">📤</button> </div> </div> <div class="chat-messages" id="chatMessages"> <div class="message bot"> <div class="message-header"> <div class="message-avatar">💊</div> <span>theREALrealtalk</span> <span>•</span> <span>Just now</span> </div> <div class="message-bubble"> Alright, let's cut through the bullsh*t. What's actually going on with you right now? No fluff. </div> </div> </div> <div class="chat-input-area"> <div class="chat-input-container"> <textarea class="chat-input" id="chatInput" placeholder="Type your message here..." rows="1"></textarea> <button class="send-button tooltip" id="sendButton" data-tooltip="Send message (Ctrl+Enter)"> <span id="sendButtonText">Send</span> <span id="sendButtonIcon">→</span> </button> </div> </div> <!-- Bot Info Panel --> <div class="bot-info-panel" id="botInfoPanel"> <div class="bot-info-content" id="botInfoContent"> <!-- Will be populated by JavaScript --> </div> </div> </div> <aside class="chat-sidebar" id="chatSidebar"> <!-- Model Selector --> <div class="sidebar-card"> <div class="sidebar-header" id="modelSelector"> 🧠 AI Model </div> <div class="model-options" id="modelOptions"> <div style="text-align: center; padding: 16px; font-size: 12px;">Loading models...</div> </div> </div> <!-- Persona Selector --> <div class="sidebar-card"> <div class="sidebar-header" id="personaSelector">🎭 Persona</div> <div class="persona-options"> <div class="persona-option tooltip" data-persona="guide" data-tooltip="Balanced AI companion for thoughtful conversations"> <div>🤖</div> <div> <div style="font-weight: 600;">AI Guide</div> <div style="font-size: 10px; color: var(--gray);">Balanced support</div> </div> </div> <div class="persona-option active tooltip" data-persona="direct" data-tooltip="No-bullshit advisor who delivers uncomfortable truths"> <div>💊</div> <div> <div style="font-weight: 600;">theREALrealtalk</div> <div style="font-size: 10px; color: var(--gray);">No BS approach | 97% Rating</div> </div> </div> <div class="persona-option tooltip" data-persona="coach" data-tooltip="Strategic guidance focused on actionable plans"> <div>🎯</div> <div> <div style="font-weight: 600;">Life Coach</div> <div style="font-size: 10px; color: var(--gray);">Strategic guidance</div> </div> </div> <div class="persona-option tooltip" data-persona="therapist" data-tooltip="Deep pattern exploration and self-awareness"> <div>🧠</div> <div> <div style="font-weight: 600;">NotTherapy</div> <div style="font-size: 10px; color: var(--gray);">Deep exploration</div> </div> </div> <div class="persona-option tooltip" data-persona="tough-love" data-tooltip="Extremely direct feedback - use with caution!"> <div>😢</div> <div> <div style="font-weight: 600;">BrutallyHonest</div> <div style="font-size: 10px; color: var(--gray);">Chat at your own risk...</div> </div> </div> </div> </div> </aside> </div> </main> <!-- Transform Mode --> <main class="tab-content" id="transform-content"> <div class="transform-container"> <h2>🔥 Transformation Workflows</h2> <p style="color: var(--gray); margin-bottom: 24px;"> Expert-designed workflows that combine multiple AI specialists for deep, personalized breakthroughs. </p> <div class="workflow-grid"> <div class="workflow-card"> <h3>💡 Personal Breakthrough</h3> <p>Identify and overcome the mental blocks that are holding you back from your potential.</p> <div class="workflow-meta"> <div class="workflow-cost">1.5 credits</div> <div class="workflow-rating">⭐ 4.9/5</div> </div> </div> <div class="workflow-card"> <h3>🎯 Life Direction</h3> <p>Get crystal clear on what you actually want and create a realistic plan to get there.</p> <div class="workflow-meta"> <div class="workflow-cost">1.2 credits</div> <div class="workflow-rating">⭐ 4.8/5</div> </div> </div> <div class="workflow-card"> <h3>🔥 Confidence Boost</h3> <p>Develop genuine confidence that comes from self-awareness and self-acceptance.</p> <div class="workflow-meta"> <div class="workflow-cost">0.8 credits</div> <div class="workflow-rating">⭐ 4.7/5</div> </div> </div> </div> </div> </main> <!-- Workflow Mode --> <main class="tab-content" id="workflow-content"> <div class="transform-container"> <h2>⚡ Custom Workflow Builder</h2> <p style="color: var(--gray); margin-bottom: 24px;"> Build complex AI workflows with multiple models, bots, and custom logic. </p> <div style="padding: 40px; color: var(--gray);"> <h3 style="margin-bottom: 16px;">🚀 Coming Soon!</h3> <p>We're building an amazing workflow system where you can:</p> <ul style="text-align: left; margin-top: 16px; line-height: 2;"> <li>• Chain multiple AI models together</li> <li>• Create automated journaling workflows</li> <li>• Set up recurring check-ins and reviews</li> <li>• Build custom transformation sequences</li> <li>• Connect to external APIs and services</li> </ul> </div> </div> </main> <!-- Assistant Mode --> <main class="tab-content" id="assistant-content"> <div class="transform-container"> <h2>🤖 Your Personal AI Assistant</h2> <p style="color: var(--gray); margin-bottom: 24px;"> An AI assistant that knows you, accesses your data, and helps with any task. </p> <div style="padding: 40px; color: var(--gray);"> <h3 style="margin-bottom: 16px;">🔥 Coming Soon!</h3> <p>Your personal assistant will help with:</p> <ul style="text-align: left; margin-top: 16px; line-height: 2;"> <li>• Managing your projects and goals</li> <li>• Tracking your habits and progress</li> <li>• Scheduling and reminders</li> <li>• Analyzing your journal entries for insights</li> <li>• Connecting with your calendar, notes, and apps</li> </ul> </div> </div> </main> </div> <script> // Global state const state = { user: { credits: 10.0, isPremium: false, streak: 1, totalBreakthroughs: 0, userId: generateUserId(), email: null }, chat: { currentModel: 'anthropic/claude-3.5-haiku', currentPersona: 'direct', messages: [], isTyping: false, botInfoPanelOpen: false }, ui: { activeTab: 'chat', currentTheme: 'light' }, models: { available: [], loading: false }, opoerator: { enabled: false, insightCount: 0 }, notifications: { enabled: false, opoeratorInsights: false }, archive: { conversations: [], transformations: [], workflows: [], journalEntries: [], notifications: [] }, feedback: { type: 'bug' }, onboarding: { currentStep: 0, steps: [ { target: '#themeToggle', title: '🎯 Choose Your Vibe', text: 'Switch between light and dark themes.' }, { target: '#opoeratorToggle', title: '🤖 Enable oPOErator', text: 'AI insights about your behavior patterns.' }, { target: '#creditsDisplay', title: '💎 Your Credits', text: 'Track your credits and upgrade options.' }, { target: '#personaInfoBtn', title: 'ℹ️ Bot Info', text: 'Click the info button to learn about each AI personality.' }, { target: '#transformTab', title: '🔥 Transformations', text: 'Expert-designed workflows for breakthroughs.' } ] } }; // Utility functions function generateUserId() { return 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } function escapeHtml(unsafe) { return String(unsafe).replace(/[&<>"']/g, s => ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[s]); } // Notification system function showNotification(content, type = 'default', duration = 3000) { if (!state.notifications.enabled) return; const container = document.getElementById('notificationsContainer'); const notification = document.createElement('div'); notification.className = `notification ${type}`; const notificationId = Date.now(); notification.dataset.id = notificationId; notification.innerHTML = ` <button class="notification-close" onclick="removeNotification(${notificationId})">×</button> <div class="notification-content">${escapeHtml(content)}</div> `; container.appendChild(notification); // Add to archive state.archive.notifications.unshift({ id: notificationId, content: content, type: type, timestamp: new Date().toISOString() }); setTimeout(() => { removeNotification(notificationId); }, duration); return notificationId; } function removeNotification(notificationId) { const notification = document.querySelector(`.notification[data-id="${notificationId}"]`); if (notification) { notification.classList.add('removing'); setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); } } // Archive/Storage Modal function showArchiveModal() { const modal = document.getElementById('archiveModal'); modal.classList.add('active'); updateArchiveDisplay(); } function hideArchiveModal() { document.getElementById('archiveModal').classList.remove('active'); } function updateArchiveDisplay() { // Update conversations const conversationsContainer = document.getElementById('savedConversations'); if (state.archive.conversations.length > 0) { conversationsContainer.innerHTML = state.archive.conversations.map(conv => ` <div class="archive-item"> <div class="archive-item-title">${escapeHtml(conv.title)}</div> <div class="archive-item-meta"> <span>${conv.messages.length} messages</span> <span>${new Date(conv.timestamp).toLocaleDateString()}</span> </div> </div> `).join(''); } // Update old notifications const notificationsContainer = document.getElementById('oldNotifications'); if (state.archive.notifications.length > 0) { notificationsContainer.innerHTML = state.archive.notifications.slice(0, 10).map(notif => ` <div class="archive-item"> <div class="archive-item-title">${escapeHtml(notif.content.substring(0, 50))}${notif.content.length > 50 ? '...' : ''}</div> <div class="archive-item-meta"> <span>${notif.type}</span> <span>${new Date(notif.timestamp).toLocaleDateString()}</span> </div> </div> `).join(''); } } // Credits Modal - NOW WORKING function showCreditsModal() { const modal = document.getElementById('creditsModal'); modal.classList.add('active'); } function hideCreditsModal() { document.getElementById('creditsModal').classList.remove('active'); } function purchaseMonthly() { alert('🚀 Redirecting to Stripe checkout for $15/month premium subscription...\n\n(In production, this would open the real payment flow)'); hideCreditsModal(); } function purchaseCredits() { alert('💰 Redirecting to Stripe checkout for $10 credit pack...\n\n(In production, this would open the real payment flow)'); hideCreditsModal(); } // Feedback Modal function showFeedbackModal() { const modal = document.getElementById('feedbackModal'); modal.classList.add('active'); } function hideFeedbackModal() { document.getElementById('feedbackModal').classList.remove('active'); } function selectFeedbackType(type) { state.feedback.type = type; document.querySelectorAll('.feedback-type-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.type === type); }); } function submitFeedback() { const text = document.getElementById('feedbackText').value.trim(); if (!text) { alert('Please enter your feedback!'); return; } // In production, this would send to your Notion database const feedbackData = { type: state.feedback.type, text: text, userId: state.user.userId, email: state.user.email, timestamp: new Date().toISOString(), userAgent: navigator.userAgent, url: window.location.href }; console.log('Feedback submitted:', feedbackData); // Simulate API call alert(`✅ Feedback submitted successfully! Thank you for helping improve BHA.\n\nType: ${state.feedback.type}\nContent: ${text.substring(0, 100)}...`); // Clear form and close document.getElementById('feedbackText').value = ''; hideFeedbackModal(); } // Layout reset function resetLayout() { const chatContainer = document.getElementById('chatContainer'); const chatSidebar = document.getElementById('chatSidebar'); if (chatContainer) { chatContainer.style.width = ''; chatContainer.style.height = '500px'; } if (chatSidebar) { chatSidebar.style.width = ''; chatSidebar.style.height = '500px'; } // Close panels const botInfoPanel = document.getElementById('botInfoPanel'); if (botInfoPanel && state.chat.botInfoPanelOpen) { botInfoPanel.classList.remove('open'); state.chat.botInfoPanelOpen = false; } // Visual feedback const resetBtn = document.getElementById('resetLayoutBtn'); if (resetBtn) { resetBtn.style.transform = 'scale(1.2)'; setTimeout(() => { resetBtn.style.transform = ''; }, 200); } } // Onboarding system - FIXED window.startOnboardingTour = function() { console.log('Starting onboarding tour!'); document.getElementById('welcomeModal').classList.add('hidden'); document.getElementById('onboardingSpotlight').classList.remove('hidden'); document.getElementById('onboardingTooltip').classList.remove('hidden'); state.onboarding.currentStep = 0; showOnboardingStep(0); }; window.skipOnboarding = function() { console.log('Skipping onboarding!'); document.getElementById('onboardingOverlay').classList.remove('active'); localStorage.setItem('bha_onboarding_completed', 'true'); }; window.nextOnboardingStep = function() { state.onboarding.currentStep++; if (state.onboarding.currentStep >= state.onboarding.steps.length) { skipOnboarding(); setTimeout(() => { showNotification('Welcome to BHA! Click the ⚡ breakthroughs button to see your archive! 🔥', 'default', 4000); }, 500); } else { showOnboardingStep(state.onboarding.currentStep); } }; function showOnboardingStep(stepIndex) { const step = state.onboarding.steps[stepIndex]; const target = document.querySelector(step.target); if (!target) { nextOnboardingStep(); return; } // Update tooltip document.getElementById('tooltipTitle').textContent = step.title; document.getElementById('tooltipText').textContent = step.text; // Position spotlight const rect = target.getBoundingClientRect(); const spotlight = document.getElementById('onboardingSpotlight'); const tooltip = document.getElementById('onboardingTooltip'); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const size = Math.max(rect.width, rect.height) + 40; spotlight.style.left = `${centerX - size / 2}px`; spotlight.style.top = `${centerY - size / 2}px`; spotlight.style.width = `${size}px`; spotlight.style.height = `${size}px`; // Position tooltip tooltip.style.left = `${Math.min(centerX - 150, window.innerWidth - 320)}px`; tooltip.style.top = `${rect.bottom + 20}px`; } // Bot info system const botInfoData = { direct: { name: 'theREALrealtalk', emoji: '💊', rating: '4.9', description: 'No-bullshit advisor who cuts through excuses and delivers uncomfortable truths.', useCases: ['Breaking bad habits', 'Accountability', 'Tough love advice', 'Reality checks'], examplePrompts: [ "I keep procrastinating on my goals and making excuses.", "Why do I always sabotage myself when things get good?", "I need someone to call me out on my BS patterns." ] }, guide: { name: 'AI Guide', emoji: '🤖', rating: '4.6', description: 'Your balanced AI companion for thoughtful conversations and gentle guidance.', useCases: ['General questions', 'Daily reflection', 'Goal planning', 'Balanced advice'], examplePrompts: [ "I'm feeling overwhelmed with work. Can you help me prioritize?", "What's a good way to start building better habits?", "I'm not sure what direction to take my career in." ] }, coach: { name: 'Life Coach', emoji: '🎯', rating: '4.8', description: 'Strategic guidance focused on actionable plans and measurable progress.', useCases: ['Goal setting', 'Action plans', 'Strategy', 'Performance'], examplePrompts: [ "Help me create a 90-day plan to launch my business.", "I need a system for staying consistent with my workouts.", "How do I break down big goals into manageable steps?" ] }, therapist: { name: 'NotTherapy', emoji: '🧠', rating: '4.7', description: 'Deep pattern exploration and self-awareness development (not actual therapy).', useCases: ['Self-awareness', 'Pattern recognition', 'Emotional processing', 'Deep reflection'], examplePrompts: [ "Why do I always choose partners who aren't good for me?", "I have trouble setting boundaries. Where does this come from?", "Help me understand my relationship patterns." ] }, 'tough-love': { name: 'BrutallyHonest', emoji: '😢', rating: '4.5', description: 'Extremely direct feedback that cuts to the bone. Use only when ready for brutal honesty.', useCases: ['Harsh reality checks', 'Breaking delusions', 'Tough motivation', 'Hard truths'], examplePrompts: [ "Tell me the hard truth about why I'm not successful yet.", "What am I lying to myself about?", "Give it to me straight - what's really holding me back?" ] } }; function toggleBotInfoPanel() { const panel = document.getElementById('botInfoPanel'); const isOpen = state.chat.botInfoPanelOpen; if (isOpen) { panel.classList.remove('open'); state.chat.botInfoPanelOpen = false; } else { updateBotInfoPanel(); panel.classList.add('open'); state.chat.botInfoPanelOpen = true; } } function updateBotInfoPanel() { const botData = botInfoData[state.chat.currentPersona]; if (!botData) return; const content = document.getElementById('botInfoContent'); content.innerHTML = ` <div class="bot-info-header"> <div class="bot-info-avatar">${botData.emoji}</div> <div class="bot-info-title"> <h4>${botData.name}</h4> <p>AI Personality</p> </div> <div class="bot-info-rating">⭐ ${botData.rating}</div> </div> <div class="bot-info-description">${botData.description}</div> <div class="bot-info-section"> <h5>Best Used For</h5> <div class="use-cases"> ${botData.useCases.map(useCase => `<div class="use-case">${useCase}</div>`).join('')} </div> </div> <div class="bot-info-section"> <h5>Example Prompts</h5> <div class="example-prompts"> ${botData.examplePrompts.map(prompt => `<div class="example-prompt" onclick="useExamplePrompt('${prompt.replace(/'/g, "\\'")}')">"${prompt}"</div>` ).join('')} </div> </div> `; } function useExamplePrompt(prompt) { const chatInput = document.getElementById('chatInput'); chatInput.value = prompt; chatInput.focus(); toggleBotInfoPanel(); } // oPOErator system function toggleOpoerator() { state.opoerator.enabled = !state.opoerator.enabled; const toggle = document.getElementById('opoeratorToggle'); toggle.classList.toggle('active', state.opoerator.enabled); if (state.opoerator.enabled && state.notifications.enabled) { showNotification('oPOErator activated! 🤖', 'opoerator', 2000); setTimeout(() => { addRandomOpoeratorInsight(); }, 10000); } } function addRandomOpoeratorInsight() { if (!state.opoerator.enabled || !state.notifications.enabled) return; const insights = [ "Your interaction patterns suggest you're testing boundaries - this shows healthy skepticism.", "I notice periods of reflection between actions. This thoughtful approach is a strength.", "Your communication style indicates someone who values authenticity over diplomacy.", "The way you navigate the interface shows comfort with exploration and experimentation." ]; const randomInsight = insights[Math.floor(Math.random() * insights.length)]; showNotification(randomInsight, 'opoerator', 6000); state.opoerator.insightCount++; if (state.opoerator.enabled && state.opoerator.insightCount < 3) { setTimeout(addRandomOpoeratorInsight, Math.random() * 180000 + 120000); // 2-5 minutes } } // Theme system function toggleTheme() { const newTheme = state.ui.currentTheme === 'light' ? 'dark' : 'light'; state.ui.currentTheme = newTheme; document.body.dataset.theme = newTheme; const icon = document.getElementById('themeIcon'); const text = document.getElementById('themeText'); if (newTheme === 'light') { icon.textContent = '☀️'; text.textContent = 'Light'; } else { icon.textContent = '🌙'; text.textContent = 'Dark'; } } // Tab switching function switchTab(tabName) { state.ui.activeTab = tabName; document.querySelectorAll('.tab').forEach(tab => { tab.classList.toggle('active', tab.dataset.tab === tabName); }); document.querySelectorAll('.tab-content').forEach(content => { content.classList.toggle('active', content.id === `${tabName}-content`); }); } // Persona switching function selectPersona(personaName) { state.chat.currentPersona = personaName; const personas = { guide: { name: 'AI Guide', emoji: '🤖' }, direct: { name: 'theREALrealtalk', emoji: '💊' }, coach: { name: 'Life Coach', emoji: '🎯' }, therapist: { name: 'NotTherapy', emoji: '🧠' }, 'tough-love': { name: 'BrutallyHonest', emoji: '😢' } }; const persona = personas[personaName]; if (!persona) return; // Update display document.getElementById('currentPersonaEmoji').textContent = persona.emoji; document.getElementById('currentPersonaName').textContent = persona.name; document.querySelectorAll('.persona-option').forEach(option => { option.classList.toggle('active', option.dataset.persona === personaName); }); if (state.chat.botInfoPanelOpen) { updateBotInfoPanel(); } } // Model system function renderModelOptions() { const container = document.getElementById('modelOptions'); const models = [ { id: 'anthropic/claude-3.5-haiku', name: '3.5 Haiku', cost: 0, description: 'Fast and capable model perfect for everyday conversations. Great balance of speed and intelligence.' }, { id: 'anthropic/claude-3.5-sonnet', name: 'Claude 3.5 Sonnet', cost: 0.5, description: 'Advanced reasoning and analysis. Better for complex problems and detailed responses.' }, { id: 'openai/gpt-4o', name: 'GPT-4o', cost: 0.8, description: 'OpenAI\'s flagship model. Excellent for creative tasks and nuanced conversations.' } ]; state.models.available = models; container.innerHTML = ''; models.forEach(model => { const option = document.createElement('div'); option.className = `model-option tooltip ${model.id === state.chat.currentModel ? 'active' : ''}`; option.dataset.model = model.id; option.dataset.tooltip = model.description; option.addEventListener('click', () => selectModel(model.id)); option.innerHTML = ` <span>${model.name}</span> <span style="color: ${model.cost === 0 ? 'var(--success)' : 'var(--gold)'}; font-size: 10px;"> ${model.cost === 0 ? 'FREE' : model.cost + ' credits'} </span> `; container.appendChild(option); }); } function selectModel(modelId) { state.chat.currentModel = modelId; renderModelOptions(); } // Chat controls - NOW WORKING function saveConversation() { if (state.chat.messages.length === 0) { alert('No messages to save yet!'); return; } const title = prompt('Enter a name for this conversation:', 'My Chat ' + new Date().toLocaleDateString()); if (title && title.trim()) { const conversation = { title: title.trim(), messages: [...state.chat.messages], timestamp: new Date().toISOString(), persona: state.chat.currentPersona, model: state.chat.currentModel }; state.archive.conversations.unshift(conversation); alert('✅ Conversation saved! View it in your archive (click the ⚡ breakthroughs button).'); } } function clearChat() { if (confirm('Clear all messages? This cannot be undone!')) { state.chat.messages = []; const messagesContainer = document.getElementById('chatMessages'); const currentPersona = botInfoData[state.chat.currentPersona]; if (messagesContainer && currentPersona) { messagesContainer.innerHTML = ` <div class="message bot"> <div class="message-header"> <div class="message-avatar">${currentPersona.emoji}</div> <span>${currentPersona.name}</span> <span>•</span> <span>Just now</span> </div> <div class="message-bubble"> Chat cleared! What's on your mind now? </div> </div> `; } } } function exportChat() { if (state.chat.messages.length === 0) { alert('No messages to export yet!'); return; } let content = `Brutally Honest AI Chat Export\n`; content += `Exported: ${new Date().toLocaleString()}\n`; content += `Persona: ${botInfoData[state.chat.currentPersona]?.name || 'Unknown'}\n`; content += `Model: ${state.chat.currentModel}\n`; content += '='.repeat(50) + '\n\n'; state.chat.messages.forEach(msg => { const timestamp = new Date(msg.timestamp).toLocaleString(); const speaker = msg.sender === 'user' ? 'You' : (msg.persona || 'AI'); content += `[${timestamp}] ${speaker}: ${msg.content}\n\n`; }); const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `BHA_Chat_${new Date().toISOString().split('T')[0]}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function sendMessage() { const input = document.getElementById('chatInput'); const message = input.value.trim(); if (!message) return; // Add message to state state.chat.messages.push({ content: message, sender: 'user', timestamp: Date.now(), persona: state.chat.currentPersona }); input.value = ''; console.log('Message sent:', message); } // Initialize function init() { console.log("🔥 BHA.ai Initializing..."); // Set up event listeners document.getElementById('themeToggle')?.addEventListener('click', toggleTheme); document.getElementById('opoeratorToggle')?.addEventListener('click', toggleOpoerator); document.getElementById('personaInfoBtn')?.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); toggleBotInfoPanel(); }); document.getElementById('resetLayoutBtn')?.addEventListener('click', resetLayout); document.getElementById('creditsDisplay')?.addEventListener('click', showCreditsModal); document.getElementById('breakthroughsBtn')?.addEventListener('click', showArchiveModal); document.getElementById('feedbackBtn')?.addEventListener('click', showFeedbackModal); // Modal close buttons document.getElementById('closeArchiveModal')?.addEventListener('click', hideArchiveModal); document.getElementById('closeCreditsModal')?.addEventListener('click', hideCreditsModal); document.getElementById('closeFeedbackModal')?.addEventListener('click', hideFeedbackModal); // Feedback form document.querySelectorAll('.feedback-type-btn').forEach(btn => { btn.addEventListener('click', () => selectFeedbackType(btn.dataset.type)); }); document.getElementById('submitFeedback')?.addEventListener('click', submitFeedback); // Tab switching document.querySelectorAll('.tab').forEach(tab => { tab.addEventListener('click', (e) => { const tabName = e.currentTarget.dataset.tab; if (tabName) switchTab(tabName); }); }); // Persona selection document.querySelectorAll('.persona-option').forEach(option => { option.addEventListener('click', function() { selectPersona(this.dataset.persona); }); }); // Chat controls document.getElementById('saveBtn')?.addEventListener('click', saveConversation); document.getElementById('clearBtn')?.addEventListener('click', clearChat); document.getElementById('exportBtn')?.addEventListener('click', exportChat); document.getElementById('sendButton')?.addEventListener('click', sendMessage); document.getElementById('chatInput')?.addEventListener('keydown', (e) => { if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); sendMessage(); } }); // Initialize models renderModelOptions(); // Start onboarding check const completed = localStorage.getItem('bha_onboarding_completed'); if (!completed) { setTimeout(() => { document.getElementById('onboardingOverlay')?.classList.add('active'); }, 1000); } else { // Show brief welcome for returning users setTimeout(() => { state.notifications.enabled = true; showNotification('Welcome back to BHA! 🔥', 'default', 2000); }, 1000); } console.log('✅ Initialization complete!'); } // Global functions for HTML onclick events window.removeNotification = removeNotification; window.useExamplePrompt = useExamplePrompt; window.purchaseMonthly = purchaseMonthly; window.purchaseCredits = purchaseCredits; // Initialize when ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } </script> </body> </html>
Real Talk, Practical Tools & AI Integration for Authentic Transformation
The proof is in the people 👇
“It’s brutally honest and its brutality is undoubtedly admirable”
“Exceeded my expectations with a reality check and makes me laugh.”
“It actually gave me a game plan and helped me set goals. Now I have a job and faced my fear of being on the phone.”
“This thing has probably saved lives. The real talk one is phenomenal. Literally talks to you like a therapist to an intelligent friend. Not mean. Could save someone from suicidal thoughts.”
“I needed to hear or read what I knew in my heart but don't want to admit.”
“I love this bot don’t change it”
“It was spot on, like a mind reader.”
“Absolutely mind blowing!!! So unreal!!!”
“He has helped me when I was completely stumped, kept me from letting emotions take over. I appreciate this bot!”
“It made me smile and I can actually use the advice—I ended up with clear action points.”
57,000+ Lifetime Users
830,000+ Messages Sent
97% Satisfaction 10,800+ Votes