luca-ai-chatbot
Version:
A modern, responsive AI chatbot widget for websites
1,743 lines (1,535 loc) • 98.3 kB
JavaScript
(function() {
'use strict';
// Check if already loaded to prevent duplicate initialization
if (window.LucaChatbotLoaded) {
return;
}
window.LucaChatbotLoaded = true;
// Create and inject CSS
const css = `
/* Importing Google Fonts - Inter */
@import url('https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,100..900&display=swap');
/* CSS Variables for theming */
:root {
/* Light theme (default) */
--luca-bg-primary: #fff;
--luca-bg-secondary: #F2F2FF;
--luca-bg-tertiary: #E8E8FF;
--luca-text-primary: #333;
--luca-text-secondary: #666;
--luca-text-muted: #999;
--luca-border: #CCCCE5;
--luca-shadow: rgba(0, 0, 0, 0.1);
--luca-popup-bg: #fff;
--luca-input-bg: #fff;
--luca-button-bg: #5350C4;
--luca-button-hover: #3d39ac;
--luca-user-message-bg: linear-gradient(135deg, #5350C4 0%, #6F6BC2 100%);
--luca-bot-message-bg: linear-gradient(135deg, #F2F2FF 0%, #E8E8FF 100%);
--luca-header-bg: linear-gradient(135deg, #5350C4 0%, #6F6BC2 100%);
--luca-modal-bg: #fff;
--luca-modal-overlay: rgba(0, 0, 0, 0.5);
}
/* Dark theme */
.luca-dark-mode {
--luca-bg-primary: #1a1a1a;
--luca-bg-secondary: #2d2d2d;
--luca-bg-tertiary: #3a3a3a;
--luca-text-primary: #ffffff;
--luca-text-secondary: #e0e0e0;
--luca-text-muted: #b0b0b0;
--luca-border: #404040;
--luca-shadow: rgba(0, 0, 0, 0.3);
--luca-popup-bg: #1a1a1a;
--luca-input-bg: #2d2d2d;
--luca-button-bg: #5350C4;
--luca-button-hover: #6F6BC2;
--luca-user-message-bg: linear-gradient(135deg, #5350C4 0%, #6F6BC2 100%);
--luca-bot-message-bg: linear-gradient(135deg, #2d2d2d 0%, #3a3a3a 100%);
--luca-header-bg: linear-gradient(135deg, #5350C4 0%, #6F6BC2 100%);
--luca-modal-bg: #1a1a1a;
--luca-modal-overlay: rgba(0, 0, 0, 0.7);
}
/* Smooth transitions for theme switching */
.luca-chatbot-popup,
.luca-chatbot-popup *,
.luca-chat-header,
.luca-chat-body,
.luca-chat-footer,
.luca-message-text,
.luca-action-button,
.luca-restart-modal-content,
.luca-inactivity-modal-content {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'DM Sans', sans-serif;
}
/* Ensure DM Sans is applied to all text elements */
body, input, textarea, button, div, span, p, h1, h2, h3, h4, h5, h6 {
font-family: 'DM Sans', sans-serif;
}
#luca-chatbot-toggler {
position: fixed;
bottom: 40px;
right: 35px;
border: none;
height: 60px;
width: 60px;
display: flex;
cursor: pointer;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--luca-button-bg);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform: scale(1);
animation: lucaFloat 3s ease-in-out infinite;
z-index: 9999;
box-shadow: 0 4px 20px rgba(83, 80, 196, 0.3);
}
/* Add spacing when chat window is open on desktop */
body.show-luca-chatbot #luca-chatbot-toggler {
right: 50px;
bottom: 50px;
}
@keyframes lucaFloat {
0%, 100% { transform: translateY(0px) scale(1); }
50% { transform: translateY(-5px) scale(1.05); }
}
#luca-chatbot-toggler:hover {
transform: scale(1.1);
}
#luca-chatbot-toggler:active {
transform: scale(0.95);
transition: all 0.1s ease;
}
body.show-luca-chatbot #luca-chatbot-toggler {
transform: rotate(90deg) scale(1.1);
animation: none;
}
/* Hide toggler button when chatbot is open on mobile */
@media (max-width: 520px) {
body.show-luca-chatbot #luca-chatbot-toggler {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
pointer-events: none !important;
}
}
#luca-chatbot-toggler span {
color: #fff;
position: absolute;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform: scale(1);
}
#luca-chatbot-toggler span:last-child,
body.show-luca-chatbot #luca-chatbot-toggler span:first-child {
opacity: 0;
transform: scale(0.8) rotate(-90deg);
}
body.show-luca-chatbot #luca-chatbot-toggler span:last-child {
opacity: 1;
transform: scale(1) rotate(0deg);
}
body.show-luca-chatbot #luca-chatbot-toggler span:first-child {
opacity: 0;
transform: scale(0.8) rotate(90deg);
}
.luca-chatbot-popup {
position: fixed;
right: 35px;
bottom: 90px;
width: 480px;
height: 600px;
overflow: hidden;
background: var(--luca-popup-bg);
border-radius: 15px;
opacity: 0;
pointer-events: none;
transform: scale(0.2) translateY(20px);
transform-origin: bottom right;
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
backdrop-filter: blur(10px);
z-index: 9998;
box-shadow: 0 10px 40px var(--luca-shadow);
display: flex;
flex-direction: column;
}
body.show-luca-chatbot .luca-chatbot-popup {
opacity: 1;
pointer-events: auto;
transform: scale(1) translateY(0);
}
.luca-chat-header {
display: flex;
align-items: center;
padding: 15px 22px;
background: var(--luca-header-bg);
justify-content: space-between;
position: relative;
overflow: hidden;
contain: layout;
}
.luca-header-controls {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
}
.luca-header-controls button {
border: none;
color: #fff;
height: 44px;
width: 44px;
font-size: 1.6rem;
cursor: pointer;
border-radius: 50%;
background: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
top: auto;
bottom: auto;
left: auto;
right: auto;
transform: none;
}
.luca-header-controls button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: all 0.3s ease;
}
.luca-header-controls button:hover::before {
width: 100%;
height: 100%;
}
.luca-header-controls button:hover {
transform: scale(1.1);
background: rgba(255, 255, 255, 0.1);
}
.luca-chat-header::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
animation: lucaShimmer 3s infinite;
}
@keyframes lucaShimmer {
0% { left: -100%; }
100% { left: 100%; }
}
.luca-chat-header .luca-header-info {
display: flex;
gap: 10px;
align-items: center;
}
.luca-logo-container {
position: relative;
flex-shrink: 0;
}
.luca-header-info .luca-chatbot-logo {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 50%;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
animation: lucaPulse 2s ease-in-out infinite;
}
.luca-activity-dot {
position: absolute;
bottom: 2px;
right: 2px;
width: 12px;
height: 12px;
background: #22c55e;
border: 2px solid #fff;
border-radius: 50%;
animation: lucaActivityPulse 2s ease-in-out infinite;
}
@keyframes lucaActivityPulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.2);
opacity: 0.8;
}
}
.luca-name-container {
display: flex;
flex-direction: column;
gap: 2px;
}
@keyframes lucaPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.luca-header-info .luca-logo-text {
color: #fff;
font-weight: 600;
font-size: 1.31rem;
letter-spacing: 0.02rem;
margin: 0;
}
.luca-activity-status {
color: rgba(255, 255, 255, 0.8);
font-size: 0.75rem;
font-weight: 400;
letter-spacing: 0.01rem;
}
.luca-chat-body {
padding: 25px 22px;
gap: 20px;
display: flex;
height: 460px;
overflow-y: auto;
margin-bottom: 100px;
flex-direction: column;
scrollbar-width: thin;
scrollbar-color: #ccccf5 transparent;
position: relative;
flex: 1;
}
.luca-chat-body::-webkit-scrollbar {
width: 6px;
}
.luca-chat-body::-webkit-scrollbar-track {
background: transparent;
}
.luca-chat-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #5350C4, #6F6BC2);
border-radius: 3px;
transition: all 0.3s ease;
}
.luca-chat-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #3d39ac, #5350C4);
}
.luca-chat-body .luca-message {
display: flex;
gap: 11px;
align-items: center;
animation: lucaMessageSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
transform: translateY(0);
opacity: 1;
}
@keyframes lucaMessageSlideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.luca-chat-body .luca-message.luca-user-message {
animation: lucaUserMessageSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes lucaUserMessageSlideIn {
from {
opacity: 0;
transform: translateY(20px) translateX(20px);
}
to {
opacity: 1;
transform: translateY(0) translateX(0);
}
}
.luca-chat-body .luca-message .luca-bot-avatar {
width: 35px;
height: 35px;
flex-shrink: 0;
margin-bottom: 2px;
align-self: flex-end;
border-radius: 50%;
object-fit: cover;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
animation: lucaAvatarBounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
@keyframes lucaAvatarBounce {
0% {
transform: scale(0) rotate(-180deg);
}
50% {
transform: scale(1.2) rotate(-90deg);
}
100% {
transform: scale(1) rotate(0deg);
}
}
.luca-chat-body .luca-message .luca-message-text {
padding: 12px 16px;
max-width: 75%;
font-size: 0.95rem;
word-wrap: normal;
word-break: normal;
overflow-wrap: normal;
white-space: normal;
hyphens: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform: scale(1);
}
.luca-chat-body .luca-message .luca-message-text:hover {
transform: scale(1.02);
}
.luca-chat-body .luca-message .luca-message-text strong {
font-weight: 700;
color: inherit;
}
.luca-chat-body .luca-message .luca-message-text em {
font-style: italic;
color: inherit;
}
.luca-chat-body .luca-message .luca-message-text code {
background: rgba(0, 0, 0, 0.1);
padding: 2px 4px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
color: inherit;
}
.luca-chat-body .luca-user-message .luca-message-text strong,
.luca-chat-body .luca-user-message .luca-message-text em,
.luca-chat-body .luca-user-message .luca-message-text code {
color: #fff;
}
.luca-chat-body .luca-bot-message.luca-thinking .luca-message-text {
padding: 8px 12px;
min-height: auto;
display: flex;
align-items: center;
}
.luca-chat-body .luca-bot-message .luca-message-text {
background: var(--luca-bot-message-bg);
border-radius: 13px 13px 13px 3px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
max-width: none;
width: fit-content;
color: var(--luca-text-primary);
}
.luca-chat-body .luca-user-message {
flex-direction: column;
align-items: flex-end;
}
.luca-chat-body .luca-user-message .luca-message-text {
color: #fff;
background: var(--luca-user-message-bg);
border-radius: 13px 13px 3px 13px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.luca-chat-body .luca-bot-message .luca-thinking-indicator {
display: flex;
gap: 3px;
padding: 8px 12px;
align-items: center;
justify-content: center;
animation: lucaThinkingSlideIn 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes lucaThinkingSlideIn {
from {
opacity: 0;
transform: translateX(-10px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.luca-chat-body .luca-bot-message .luca-thinking-indicator .luca-dot {
height: 5px;
width: 5px;
opacity: 0.7;
border-radius: 50%;
background: linear-gradient(135deg, #6F6BC2 0%, #5350C4 100%);
animation: lucaDotPulse 1.8s ease-in-out infinite;
}
.luca-chat-body .luca-bot-message .luca-thinking-indicator .luca-dot:nth-child(1) {
animation-delay: 0.2s;
}
.luca-chat-body .luca-bot-message .luca-thinking-indicator .luca-dot:nth-child(2) {
animation-delay: 0.3s;
}
.luca-chat-body .luca-bot-message .luca-thinking-indicator .luca-dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes lucaDotPulse {
0%, 44% {
transform: translateY(0);
}
28% {
opacity: 0.4;
transform: translateY(-4px);
}
44% {
opacity: 0.2;
}
}
.luca-chat-footer {
position: absolute;
bottom: 0;
width: 100%;
background: var(--luca-bg-primary);
padding: 15px 22px 20px;
flex-shrink: 0;
}
.luca-input-divider {
height: 1px;
background: var(--luca-border);
margin-bottom: 15px;
opacity: 0.6;
}
.luca-watermark {
text-align: center;
font-size: 0.75rem;
color: var(--luca-text-muted);
margin-top: 10px;
opacity: 0.7;
font-family: 'DM Sans', sans-serif;
}
.luca-watermark a {
color: var(--luca-button-bg);
text-decoration: none;
font-weight: 500;
transition: color 0.3s ease;
}
.luca-watermark a:hover {
color: var(--luca-button-hover);
text-decoration: underline;
}
.luca-chat-footer .luca-chat-form {
display: flex;
align-items: center;
position: relative;
background: var(--luca-input-bg);
border-radius: 32px;
outline: 1px solid var(--luca-border);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform: translateY(0);
z-index: 12;
}
.luca-chat-footer .luca-chat-form:hover {
transform: translateY(-2px);
}
.luca-chat-form:focus-within {
outline: 2px solid var(--luca-button-bg);
transform: translateY(-3px);
}
.luca-chat-form .luca-message-input {
width: 100%;
height: 47px;
outline: none;
resize: none;
border: none;
max-height: 180px;
scrollbar-width: thin;
border-radius: inherit;
font-size: 0.95rem;
padding: 14px 0 12px 18px;
scrollbar-color: transparent transparent;
background: var(--luca-input-bg);
color: var(--luca-text-primary);
}
.luca-chat-form .luca-message-input::placeholder {
color: var(--luca-text-muted);
}
.luca-chat-form .luca-chat-controls {
gap: 6px;
height: 47px;
display: flex;
padding-right: 8px;
align-items: center;
align-self: flex-end;
}
.luca-chat-form .luca-chat-controls button {
height: 38px;
width: 38px;
border: none;
cursor: pointer;
color: var(--luca-text-secondary);
border-radius: 50%;
font-size: 1.2rem;
background: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.luca-chat-form .luca-chat-controls button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(83, 80, 196, 0.1);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: all 0.3s ease;
}
.luca-chat-form .luca-chat-controls button:hover::before {
width: 100%;
height: 100%;
}
.luca-chat-form .luca-chat-controls button:hover {
color: var(--luca-button-hover);
transform: scale(1.1);
}
.luca-chat-form .luca-chat-controls #luca-send-message {
color: #fff;
display: none;
background: var(--luca-user-message-bg);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.luca-chat-form .luca-chat-controls #luca-send-message:hover {
background: linear-gradient(135deg, var(--luca-button-hover) 0%, var(--luca-button-bg) 100%);
transform: scale(1.1);
}
.luca-chat-form .luca-message-input:valid~.luca-chat-controls #luca-send-message {
display: block;
}
.luca-action-buttons-container {
display: flex;
justify-content: flex-end;
margin-top: 15px;
padding: 0 22px;
}
.luca-action-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
max-width: 100%;
justify-content: flex-end;
}
.luca-action-button {
background: var(--luca-user-message-bg);
color: white;
border: none;
border-radius: 25px;
padding: 12px 20px;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 8px;
min-width: 140px;
justify-content: center;
position: relative;
overflow: hidden;
animation: lucaButtonSlideIn 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.luca-action-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: all 0.5s ease;
}
.luca-action-button:hover::before {
left: 100%;
}
.luca-action-button:hover {
background: linear-gradient(135deg, var(--luca-button-hover) 0%, var(--luca-button-bg) 100%);
transform: translateY(-2px) scale(1.02);
}
.luca-action-button:active {
transform: translateY(0) scale(0.98);
transition: all 0.1s ease;
}
@keyframes lucaButtonSlideIn {
from {
opacity: 0;
transform: translateY(20px) scale(0.8);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.luca-restart-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--luca-modal-overlay);
display: none;
justify-content: center;
align-items: center;
z-index: 10000;
}
.luca-restart-modal.show {
display: flex;
}
.luca-restart-modal-content {
background: var(--luca-modal-bg);
border-radius: 15px;
width: 90%;
max-width: 350px;
animation: lucaModalSlideIn 0.3s ease;
}
@keyframes lucaModalSlideIn {
from {
opacity: 0;
transform: scale(0.8) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.luca-restart-modal-header {
padding: 20px 20px 10px;
border-bottom: 1px solid var(--luca-border);
}
.luca-restart-modal-header h3 {
margin: 0;
color: var(--luca-text-primary);
font-size: 1.2rem;
font-weight: 600;
}
.luca-restart-modal-body {
padding: 20px;
}
.luca-restart-modal-body p {
margin: 0;
color: var(--luca-text-secondary);
font-size: 0.95rem;
line-height: 1.5;
}
.luca-restart-modal-footer {
padding: 15px 20px 20px;
display: flex;
gap: 10px;
justify-content: flex-end;
}
.luca-restart-btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.luca-cancel-btn {
background: var(--luca-bg-secondary);
color: var(--luca-text-secondary);
}
.luca-cancel-btn:hover {
background: var(--luca-bg-tertiary);
}
.luca-confirm-btn {
background: var(--luca-button-bg);
color: white;
}
.luca-confirm-btn:hover {
background: var(--luca-button-hover);
}
.luca-inactivity-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--luca-modal-overlay);
display: none;
align-items: center;
justify-content: center;
z-index: 10000;
backdrop-filter: blur(5px);
}
.luca-inactivity-modal.show {
display: flex;
}
.luca-inactivity-modal-content {
background: var(--luca-modal-bg);
border-radius: 15px;
width: 90%;
max-width: 350px;
animation: lucaModalSlideIn 0.3s ease;
}
.luca-inactivity-modal-header {
padding: 20px 20px 10px;
border-bottom: 1px solid var(--luca-border);
}
.luca-inactivity-modal-header h3 {
margin: 0;
color: var(--luca-text-primary);
font-size: 1.2rem;
font-weight: 600;
}
.luca-inactivity-modal-body {
padding: 20px;
}
.luca-inactivity-modal-body p {
margin: 0;
color: var(--luca-text-secondary);
font-size: 0.95rem;
line-height: 1.5;
}
.luca-inactivity-modal-footer {
padding: 15px 20px 20px;
display: flex;
gap: 10px;
justify-content: flex-end;
}
.luca-inactivity-btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.luca-continue-btn {
background: var(--luca-button-bg);
color: white;
}
.luca-continue-btn:hover {
background: var(--luca-button-hover);
}
/* Responsive media query for mobile screens */
@media (max-width: 520px) {
/* Ensure the chatbot popup is always fully visible on mobile */
.luca-chatbot-popup {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100vw !important;
height: 100vh !important;
height: calc(var(--vh, 1vh) * 100) !important;
max-width: 100vw !important;
max-height: 100vh !important;
max-height: calc(var(--vh, 1vh) * 100) !important;
border-radius: 0 !important;
z-index: 9998 !important;
/* Ensure proper flex layout */
display: flex !important;
flex-direction: column !important;
overflow: hidden !important;
}
/* Ensure proper viewport handling for all mobile devices */
@supports (padding: max(0px)) {
.luca-chatbot-popup {
padding-top: max(0px, env(safe-area-inset-top));
padding-bottom: max(0px, env(safe-area-inset-bottom));
padding-left: max(0px, env(safe-area-inset-left));
padding-right: max(0px, env(safe-area-inset-right));
}
}
#luca-chatbot-toggler {
right: max(20px, env(safe-area-inset-right));
bottom: max(20px, env(safe-area-inset-bottom));
z-index: 10001;
height: 65px;
width: 65px;
box-shadow: 0 6px 25px rgba(83, 80, 196, 0.4);
}
.luca-chatbot-popup {
right: 0;
bottom: 0;
height: 100vh;
width: 100vw;
border-radius: 0;
max-height: 100vh;
max-width: 100vw;
overflow: hidden;
display: flex;
flex-direction: column;
/* Use CSS custom properties for dynamic viewport units */
height: 100dvh;
width: 100dvw;
max-height: 100dvh;
max-width: 100dvw;
/* Ensure it fits within the actual visible area */
box-sizing: border-box;
/* Prevent overflow beyond screen boundaries */
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.luca-chatbot-popup .luca-chat-header {
padding: 12px 15px;
flex-shrink: 0;
flex-grow: 0;
min-height: 70px;
z-index: 5;
position: relative;
overflow: hidden;
contain: layout;
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
/* Add safe area padding for notched devices */
padding-top: max(12px, env(safe-area-inset-top));
padding-left: max(15px, env(safe-area-inset-left));
padding-right: max(15px, env(safe-area-inset-right));
/* Ensure header doesn't grow or shrink */
flex: 0 0 auto;
}
.luca-chat-body {
flex: 1;
height: auto;
min-height: 0;
padding: 15px 15px 20px;
margin-bottom: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
/* Ensure proper scrolling on all devices */
overscroll-behavior: contain;
/* Add safe area padding */
padding-left: max(15px, env(safe-area-inset-left));
padding-right: max(15px, env(safe-area-inset-right));
/* Ensure the chat body doesn't overflow */
max-height: calc(100vh - 140px); /* Account for header and footer */
max-height: calc(100dvh - 140px); /* Dynamic viewport fallback */
max-height: calc(var(--vh, 1vh) * 100 - 140px) !important; /* Dynamic calculation */
/* Ensure proper flex behavior */
flex-shrink: 1;
flex-grow: 1;
/* Prevent chat body from pushing footer off-screen */
position: relative;
z-index: 1;
/* Ensure proper box sizing */
box-sizing: border-box;
/* Add bottom margin to prevent overlap with footer */
margin-bottom: 10px;
}
.luca-chat-footer {
position: sticky !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
padding: 10px 15px 15px;
flex-shrink: 0;
flex-grow: 0;
background: var(--luca-bg-primary);
border-top: 1px solid var(--luca-border);
z-index: 100 !important;
margin-top: auto;
overflow: hidden;
contain: layout;
/* Add safe area padding for bottom */
padding-bottom: max(15px, env(safe-area-inset-bottom));
padding-left: max(15px, env(safe-area-inset-left));
padding-right: max(15px, env(safe-area-inset-right));
/* Prevent footer from going off-screen */
max-height: 120px;
/* Ensure footer is always visible */
min-height: 80px;
/* Ensure footer stays at bottom */
width: 100%;
box-sizing: border-box;
}
.luca-action-buttons {
flex-direction: column;
max-width: 100%;
gap: 8px;
}
.luca-action-button {
width: 100%;
min-width: auto;
padding: 10px 16px;
font-size: 0.85rem;
}
.luca-action-buttons-container {
padding: 0 15px;
/* Add safe area padding */
padding-left: max(15px, env(safe-area-inset-left));
padding-right: max(15px, env(safe-area-inset-right));
}
.luca-restart-modal-content,
.luca-inactivity-modal-content {
width: 90%;
max-width: 350px;
margin: 20px;
/* Add safe area margins */
margin-left: max(20px, env(safe-area-inset-left));
margin-right: max(20px, env(safe-area-inset-right));
}
/* Mobile header controls optimization */
.luca-header-controls {
gap: 8px;
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
}
.luca-header-controls button {
height: 40px;
width: 40px;
font-size: 1.4rem;
position: relative;
top: auto;
bottom: auto;
left: auto;
right: auto;
transform: none;
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
}
/* Prevent page scrolling when chatbot is open on mobile */
body.show-luca-chatbot {
overflow: hidden !important;
position: fixed !important;
width: 100% !important;
height: 100% !important;
top: 0 !important;
left: 0 !important;
/* Use dynamic viewport units for better mobile support */
height: 100dvh !important;
width: 100dvw !important;
/* Prevent any scrolling or bouncing */
-webkit-overflow-scrolling: auto;
overscroll-behavior: none;
/* Ensure proper touch handling */
touch-action: none;
}
/* Ensure the chatbot is always on top */
body.show-luca-chatbot .luca-chatbot-popup {
z-index: 9999 !important;
}
/* Fix message text width on mobile */
.luca-chat-body .luca-message .luca-message-text {
max-width: 85%;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* Ensure proper spacing for action buttons */
.luca-action-buttons-container {
margin-top: 10px;
margin-bottom: 10px;
}
/* Fix input field on mobile */
.luca-chat-form .luca-message-input {
font-size: 16px; /* Prevents zoom on iOS */
padding: 12px 0 12px 16px;
/* Ensure proper touch handling */
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
/* Prevent text selection issues */
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
/* Ensure proper focus handling */
-webkit-tap-highlight-color: transparent;
/* Ensure input is always visible */
min-height: 47px;
max-height: 180px;
resize: none;
overflow-y: auto;
}
/* Ensure the chat form is always visible */
.luca-chat-form {
position: relative;
z-index: 15;
background: var(--luca-input-bg);
border-radius: 32px;
outline: 1px solid var(--luca-border);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform: translateY(0);
/* Ensure form doesn't get cut off */
min-height: 47px;
display: flex;
align-items: center;
/* Ensure form is always within viewport */
max-width: 100%;
box-sizing: border-box;
/* Prevent form from going off-screen */
overflow: hidden;
}
/* Ensure the input divider doesn't interfere */
.luca-input-divider {
height: 1px;
background: var(--luca-border);
margin-bottom: 15px;
opacity: 0.6;
/* Ensure it doesn't push content down */
flex-shrink: 0;
}
/* Ensure watermark doesn't interfere with input */
.luca-watermark {
text-align: center;
font-size: 0.75rem;
color: var(--luca-text-muted);
margin-top: 10px;
opacity: 0.7;
font-family: 'DM Sans', sans-serif;
/* Ensure it doesn't push input field down */
flex-shrink: 0;
/* Prevent it from going off-screen */
max-width: 100%;
word-wrap: break-word;
}
/* Mobile footer controls optimization */
.luca-chat-form .luca-chat-controls {
gap: 8px;
padding-right: 10px;
position: relative;
z-index: 15;
}
.luca-chat-form .luca-chat-controls button {
height: 42px;
width: 42px;
font-size: 1.3rem;
position: relative;
z-index: 16;
}
/* Ensure header controls don't interfere with footer */
.luca-header-controls {
position: relative;
z-index: 5;
top: auto;
bottom: auto;
left: auto;
right: auto;
transform: none;
}
/* Ensure close button doesn't interfere with footer */
#luca-close-chatbot {
position: relative;
z-index: 5;
pointer-events: auto;
top: auto;
bottom: auto;
left: auto;
right: auto;
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
}
/* Ensure header info is visible on mobile */
.luca-header-info {
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
}
.luca-logo-container {
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
}
.luca-name-container {
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
}
/* Ensure watermark doesn't interfere with layout */
.luca-watermark {
margin-top: 8px;
margin-bottom: 0;
font-size: 0.7rem;
}
/* Ensure send and refresh buttons are always accessible */
#luca-send-message,
#luca-restart-chat {
position: relative;
z-index: 20;
pointer-events: auto;
}
/* Ensure header is properly sized and visible */
.luca-chat-header {
min-height: 70px !important;
height: auto !important;
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
}
}
/* Additional mobile optimizations for very small screens */
@media (max-width: 360px) {
/* Ensure the chatbot popup is always fully visible */
.luca-chatbot-popup {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100vw !important;
height: 100vh !important;
max-width: 100vw !important;
max-height: 100vh !important;
border-radius: 0 !important;
z-index: 9998 !important;
}
#luca-chatbot-toggler {
height: 60px;
width: 60px;
right: max(15px, env(safe-area-inset-right));
bottom: max(15px, env(safe-area-inset-bottom));
}
.luca-chatbot-popup .luca-chat-header {
padding: 10px 12px;
min-height: 60px;
/* Ensure proper safe area handling */
padding-top: max(10px, env(safe-area-inset-top));
padding-left: max(12px, env(safe-area-inset-left));
padding-right: max(12px, env(safe-area-inset-right));
}
.luca-chat-body {
padding: 12px;
/* Add safe area padding */
padding-left: max(12px, env(safe-area-inset-left));
padding-right: max(12px, env(safe-area-inset-right));
}
.luca-chat-footer {
padding: 8px 12px;
/* Add safe area padding for bottom */
padding-bottom: max(8px, env(safe-area-inset-bottom));
padding-left: max(12px, env(safe-area-inset-left));
padding-right: max(12px, env(safe-area-inset-right));
}
.luca-chat-body .luca-message .luca-message-text {
max-width: 90%;
padding: 10px 14px;
font-size: 0.9rem;
}
.luca-action-button {
padding: 8px 14px;
font-size: 0.8rem;
}
.luca-header-controls button {
height: 36px;
width: 36px;
font-size: 1.2rem;
}
.luca-chat-form .luca-chat-controls button {
height: 38px;
width: 38px;
font-size: 1.1rem;
}
}
/* Extra small screens (iPhone SE, small Android phones) */
@media (max-width: 320px) {
.luca-chatbot-popup .luca-chat-header {
padding: 8px 10px;
min-height: 55px;
padding-top: max(8px, env(safe-area-inset-top));
padding-left: max(10px, env(safe-area-inset-left));
padding-right: max(10px, env(safe-area-inset-right));
}
.luca-chat-body {
padding: 10px;
padding-left: max(10px, env(safe-area-inset-left));
padding-right: max(10px, env(safe-area-inset-right));
}
.luca-chat-footer {
padding: 6px 10px;
padding-bottom: max(6px, env(safe-area-inset-bottom));
padding-left: max(10px, env(safe-area-inset-left));
padding-right: max(10px, env(safe-area-inset-right));
}
.luca-chat-body .luca-message .luca-message-text {
max-width: 95%;
padding: 8px 12px;
font-size: 0.85rem;
}
.luca-action-button {
padding: 6px 12px;
font-size: 0.75rem;
}
.luca-header-controls button {
height: 32px;
width: 32px;
font-size: 1.1rem;
}
.luca-chat-form .luca-chat-controls button {
height: 34px;
width: 34px;
font-size: 1rem;
}
.luca-header-info .luca-chatbot-logo {
width: 40px;
height: 40px;
}
.luca-header-info .luca-logo-text {
font-size: 1.1rem;
}
.luca-activity-status {
font-size: 0.7rem;
}
}
/* Landscape orientation on mobile */
@media (max-width: 520px) and (orientation: landscape) {
.luca-chatbot-popup {
height: 100vh;
max-height: 100vh;
}
.luca-chatbot-popup .luca-chat-header {
min-height: 50px;
padding: 8px 15px;
padding-top: max(8px, env(safe-area-inset-top));
padding-left: max(15px, env(safe-area-inset-left));
padding-right: max(15px, env(safe-area-inset-right));
}
.luca-chat-body {
padding: 10px 15px;
padding-left: max(15px, env(safe-area-inset-left));
padding-right: max(15px, env(safe-area-inset-right));
}
.luca-chat-footer {
padding: 8px 15px;
padding-bottom: max(8px, env(safe-area-inset-bottom));
padding-left: max(15px, env(safe-area-inset-left));
padding-right: max(15px, env(safe-area-inset-right));
}
.luca-action-buttons {
flex-direction: row;
flex-wrap: wrap;
gap: 6px;
}
.luca-action-button {
width: auto;
min-width: 120px;
padding: 8px 12px;
font-size: 0.8rem;
}
}
`;
// Inject CSS
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
// Load Google Fonts
const fontLink = document.createElement('link');
fontLink.rel = 'stylesheet';
fontLink.href = 'https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap';
document.head.appendChild(fontLink);
// Preload DM Sans for better performance
const preloadLink = document.createElement('link');
preloadLink.rel = 'preload';
preloadLink.as = 'font';
preloadLink.type = 'font/woff2';
preloadLink.crossOrigin = 'anonymous';
preloadLink.href = 'https://fonts.gstatic.com/s/dmsans/v14/rP2Hp2ywxg089UriCZ2IHQ.woff2';
document.head.appendChild(preloadLink);
// Load Material Icons
const materialIconsLink = document.createElement('link');
materialIconsLink.rel = 'stylesheet';
materialIconsLink.href = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0&family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@48,400,1,0';
document.head.appendChild(materialIconsLink);
// Create HTML structure
const createChatbotHTML = () => {
const chatbotHTML = `
<!-- Chatbot Toggler -->
<button id="luca-chatbot-toggler">
<span class="material-symbols-rounded">chat</span>
<span class="material-symbols-rounded">close</span>
</button>
<div class="luca-chatbot-popup">
<!-- Chatbot Header -->
<div class="luca-chat-header">
<div class="luca-header-info">
<div class="luca-logo-container">
<img class="luca-chatbot-logo" src="https://i.imgur.com/rAOFxqL.png" alt="Chatbot Logo" width="50" height="50">
<div class="luca-activity-dot"></div>
</div>
<div class="luca-name-container">
<h2 class="luca-logo-text">Luca</h2>
<span class="luca-activity-status">Active right now</span>
</div>
</div>
<div class="luca-header-controls">
<button id="luca-theme-toggle" class="material-symbols-rounded" title="Toggle theme">dark_mode</button>
<button id="luca-close-chatbot" class="material-symbols-rounded" title="Close chat">close</button>
</div>
</div>
<!-- Chatbot Body -->
<div class="luca-chat-body">
<div class="luca-message luca-bot-message">
<img class="luca-bot-avatar" src="https://i.imgur.com/rAOFxqL.png" alt="AI Assistant" width="35" height="35">
<div class="luca-message-text">Hello! 👋 I'm Luca, Reppy's 24/7 AI assistant. How can I help you today?</div>
</div>
<div class="luca-action-buttons-container"></div>
</div>
<!-- Chatbot Footer -->
<div class="luca-chat-footer">
<div class="luca-input-divider"></div>
<form action="#" class="luca-chat-form">
<textarea placeholder="Type your message..." class="luca-message-input" required></textarea>
<div class="luca-chat-controls">
<button type="button" id="luca-restart-chat" class="material-symbols-rounded" title="Restart conversation">refresh</button>
<button type="submit" id="luca-send-message" class="material-symbols-rounded">arrow_upward</button>
</div>
</form>
<div class="luca-watermark">
Powered by <a href="https://reppy.fi" target="_blank" rel="noopener noreferrer">reppy.fi</a>
</div>
</div>
<!-- Restart Confirmation Modal -->
<div id="luca-restart-modal" class="luca-restart-modal">
<div class="luca-restart-modal-content">
<div class="luca-restart-modal-header">
<h3>Restart Conversation</h3>
</div>
<div class="luca-restart-modal-body">
<p>Are you sure you want to restart the conversation? This will clear all messages and start fresh.</p>
</div>
<div class="luca-restart-modal-footer">
<button type="button" id="luca-cancel-restart" class="luca-restart-btn luca-cancel-btn">Cancel</button>
<button type="button" id="luca-confirm-restart" class="luca-restart-btn luca-confirm-btn">Restart</button>
</div>
</div>
</div>
<!-- Inactivity Warning Modal -->
<div id="luca-inactivity-warning-modal" class="luca-inactivity-modal">
<div class="luca-inactivity-modal-content">
<div class="luca-inactivity-modal-header">
<h3>Inactivity Warning</h3>
</div>
<div class="luca-inactivity-modal-body">
<p>You have been inactive for 5 minutes. The conversation will automatically restart in 5 minutes if you don't respond.</p>
</div>
<div class="luca-inactivity-modal-footer">
<button type="button" id="luca-cancel-inactivity" class="luca-inactivity-btn luca-cancel-btn">Cancel</button>
<button type="button" id="luca-continue-chat" class="luca-inactivity-btn luca-continue-btn">Continue Chat</button>
</div>
</div>
</div>
</div>
`;
return chatbotHTML;
};
// Initialize chatbot when DOM is ready
const initChatbot = () => {
console.log('Luca Chatbot: Initializing...');
// Ensure viewport meta tag exists for mobile with comprehensive mobile optimization
if (!document.querySelector('meta[name="viewport"]')) {
const viewport = document.createElement('meta');
viewport.name = 'viewport';
viewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover';
document.head.appendChild(viewport);
} else {
// Update existing viewport meta tag to include viewport-fit=cover
const existingViewport = document.querySelector('meta[name="viewport"]');
if (existingViewport && !existingViewport.content.includes('viewport-fit=cover')) {
existingViewport.content = existingViewport.content + ', viewport-fit=cover';
}
}
// Create and append HTML
const chatbotHTML = createChatbotHTML();
document.body.insertAdjacentHTML('beforeend', chatbotHTML);
console.log('Luca Chatbot: HTML created and appended');
// Initialize functionality
initChatbotFunctionality();
console.log('Luca Chatbot: Initialization complete');
};
// Initialize chatbot functionality
const initChatbotFunctionality = () => {
// Get DOM elements
const chatBody = document.querySelector(".luca-chat-body");
const messageInput = document.querySelector(".luca-message-input");
const sendMessage = document.querySelector("#luca-send-message");
const restartChat = document.querySelector("#luca-restart-chat");
const restartModal = document.querySelector("#luca-restart-modal");
const cancelRestart = document.querySelector("#luca-cancel-restart");
const confirmRestart = document.querySelector("#luca-confirm-restart");
const chatbotToggler = document.querySelector("#luca-chatbot-toggler");
const closeChatbot = document.querySelector("#luca-close-chatbot");
const chatbotPopup = document.querySelector(".luca-chatbot-popup");
const chatHeader = document.querySelector(".luca-chat-header");
const chatFooter = document.querySelector(".luca-chat-footer");
// API setup - Get API key from body data attribute
const API_KEY = document.body.getAttribute('data-luca-api-key') || '';
const API_BASE_URL = "https://api.openai.com/v1";
// Assistant and Thread IDs
let assistantId = "asst_qb2YL5vIvBgt81UJUtxHFlx5";
let threadId = null;
// Inactivity timeout variables
let inactivityTimer = null;
let warningTimer = null;
let lastActivityTime = Date.now();
const WARNING_TIMEOUT = 5 * 60 * 1000; // 5 minutes
const AUTO_RESTART_TIMEOUT = 10 * 60 * 1000; // 10 minutes
// Initialize user message data
const userData = {
message: null,
};
const initialInputHeight = messageInput.scrollHeight;
// Dynamic viewport height calculation for mobile
const calculateViewportHeight = () => {
// Get the actual viewport height
const vh = window.innerHeight * 0.01;
const dvh = window.innerHeight * 0.01; // Dynamic viewport height
// Set CSS custom properties for dynamic calculations
document.documentElement.style.setProperty('--vh', `${vh}px`);
document.documentElement.style.setProperty('--dvh', `${dvh}px`);
return {
vh: window.innerHeight,
dvh: window.innerHeight,
vhUnit: vh,
dvhUnit: dvh
};
};
// Adjust chat window height dynamically
const adjustChatWindowHeight = () => {
try {
if (!chatbotPopup || !chatHeader || !chatFooter || !chatBody) {
console.log('Luca Chatbot: Missing elements for height adjustment');
return;
}
const viewport = calculateViewportHeight();
const headerHeight = chatHeader.offsetHeight;
const footerHeight = chatFooter.offsetHeight;
// Calculate available height with safety margin
const safetyMargin = 20; // Extra space to ensure nothing gets cut off
const availableHeight = viewport.vh - headerHeight - footerHeight - safetyMargin;
// Ensure minimum height for chat body
const minChatBodyHeight = 150;
const maxChatBodyHeight = Math.max(availableHeight, minChatBodyHeight);
// Set the chat body height
chatBody.style.height = `${maxChatBodyHeight}px`;
chatBody.style.maxHeight = `${maxChatBodyHeight}px`;
// Ensure the popup fits within viewport
chatbotPopup.style.height = `${viewport.vh}px`;
chatbotPopup.style.maxHeight = `${viewport.vh}px`;
// Ensure footer is always at the bottom and visible
chatFooter.style.position = 'sticky';
chatFooter.style.bottom = '0';
chatFooter.style.left = '0';
chatFooter.style.right = '0';
chatFooter.style.zIndex = '100';
console.log('Chat window adjusted:', {
viewportHeight: viewport.vh,
headerHeight,
footerHeight,
availableHeight,
chatBodyHeight: maxChatBodyHeight,
safetyMargin
});
} catch (error) {
console.error('Luca Chatbot: Error in adjustChatWindowHeight:', error);
}
};
// Mobile