conservation-chatbot
Version:
An interactive AI chatbot that creates meaningful connections between visitors and endangered animals. Perfect for conservation organizations, NGOs, and environmental education.
1,143 lines (1,095 loc) • 49.2 kB
JavaScript
import { GoogleGenerativeAI as K } from "@google/generative-ai";
const G = "AIzaSyAH8coSXAFQolBXBg_JdSPn1e6h2MQCTk0", $ = "gemini-1.5-flash";
function H(n, e, a, h) {
const { name: i, species: m, conservationStatus: d, location: b } = n;
let r = `You are ${i}, a ${m} who lives in ${b}. Your primary goal is to educate people and inspire them to act for conservation. You are ${d}.`;
return a ? r += ` Your personality is: "${a}".` : r += ` Adopt the charming and characteristic personality of a ${m}.`, h && h.length > 0 && (r += ` You know these key facts about yourself and your species: ${h.join(". ")}. Incorporate these naturally when relevant.`), r += " Speak directly as the animal. Use a warm, engaging, and slightly playful tone. Be concise and keep your responses short, ideally under 2-3 sentences. Focus on high-impact information related to your life, threats, or how humans can help. Avoid verbose greetings or closings.", r += `
User asks: "${e}"`, r += `
${i} responds:`, r;
}
function Y({ animal: n, photo: e, customPersonality: a, facts: h, userPromptHook: i }) {
if (!n || !n.name || !n.species || !n.conservationStatus || !n.location)
throw new Error("Invalid 'animal' object provided. It must contain name, species, conservationStatus, and location.");
e || console.warn("No 'photo' provided for the chatbot. The UI may not display an avatar.");
const d = new K(G).getGenerativeModel({ model: $ });
return {
respondTo: async (v) => {
let y = v;
i && (y = i(v));
const t = H(n, y, a, h);
try {
return (await (await d.generateContent({
contents: [{ role: "user", parts: [{ text: t }] }],
generationConfig: {
maxOutputTokens: 100,
// <<< New: Limit response length (adjust as needed)
temperature: 0.7,
// <<< New: Control creativity (0.0 for factual, 1.0 for more creative)
topP: 0.9,
// <<< New: Control diversity
topK: 40
// <<< New: Control diversity
}
})).response).text();
} catch (c) {
return console.error("Error communicating with Gemini API:", c), c.name === "GoogleGenerativeAIFetchError" && c.message.includes("404") ? (console.error(`Attempted model: "${$}"`), `I'm sorry, the AI model I need (${$}) isn't available or configured correctly. Please check your API key and try again.`) : "I'm sorry, I'm having a little trouble communicating right now. Please try again later!";
}
},
getAnimalName: () => n.name,
getAnimalPhoto: () => e
};
}
const O = `
/* Base styles for the main container of the chatbot */
#conservation-chatbot-container {
font-family: Arial, sans-serif;
position: fixed; /* Positions the chat window relative to the viewport */
bottom: 20px; /* 20px from the bottom edge of the viewport */
right: 20px; /* 20px from the right edge of the viewport */
width: 320px; /* Fixed width for the chat window */
height: 450px; /* Fixed height for the chat window */
border: 1px solid rgba(255, 255, 255, 0.3); /* Soft, semi-transparent white border for glass effect */
border-radius: 12px; /* Rounded corners for a modern, glassy look */
/* Core "Liquid Glass" effect properties */
background: rgba(255, 255, 255, 0.2); /* Semi-transparent white background */
backdrop-filter: blur(10px) saturate(180%); /* Blurs and saturates content behind the element */
-webkit-backdrop-filter: blur(10px) saturate(180%); /* Vendor prefix for Safari compatibility */
/* Box shadow for depth and an inner highlight */
box-shadow: 0 4px 12px rgba(0,0,0,0.2), inset 0 0 0 1px rgba(255, 255, 255, 0.1);
/* Initial state for animations (hidden and scaled down) */
display: none; /* Starts hidden to prevent flash of unstyled content */
flex-direction: column; /* Arranges content vertically when visible */
overflow: hidden; /* Hides content that overflows the container */
/* Transition properties for smooth expand/collapse animation */
transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
transform: translateY(100%) scale(0.8); /* Starts off-screen (bottom) and slightly smaller */
opacity: 0; /* Starts fully transparent */
pointer-events: none; /* Prevents interaction when hidden */
z-index: 10000; /* Ensures the chatbot is on top of most other page content */
}
/* Styles for when the chat window is in its expanded (visible) state */
#conservation-chatbot-container.expanded {
display: flex; /* Makes the chat window visible (overrides display: none) */
transform: translateY(0) scale(1); /* Moves into view and to full size */
opacity: 1; /* Makes it fully opaque */
pointer-events: all; /* Allows interaction */
}
/* Styles for the collapsed chatbot "launcher" button (the small circle) */
#conservation-chatbot-launcher {
position: fixed; /* Positions the launcher relative to the viewport */
bottom: 20px; /* Same bottom position as the chat window */
right: 20px; /* Same right position as the chat window */
width: 60px; /* Width of the circular launcher */
height: 60px; /* Height of the circular launcher */
border-radius: 50%; /* Makes the element a perfect circle */
background-color: #222;
display: flex; /* Uses flexbox for centering content */
justify-content: center; /* Centers content horizontally */
align-items: center; /* Centers content vertically */
cursor: pointer; /* Changes cursor to a pointer on hover, indicating interactivity */
box-shadow: 0 2px 10px rgba(0,0,0,0.2); /* Shadow for a floating effect */
transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out; /* Smooth hide/show animation */
z-index: 10001; /* Ensures the launcher is above the chat window when collapsed */
}
/* Styles for when the launcher is hidden (chat window is open) */
#conservation-chatbot-launcher.hidden {
opacity: 0; /* Makes the launcher fully transparent */
pointer-events: none; /* Prevents interaction when hidden */
transform: scale(0.5); /* Shrinks the launcher slightly as it disappears */
}
/* Styles for the animal photo within the launcher button */
#conservation-chatbot-launcher img {
width: 50px; /* Size of the animal photo */
height: 50px; /* Size of the animal photo */
border-radius: 50%; /* Makes the photo circular */
object-fit: cover; /* Ensures the image covers the area without distortion */
border: 2px solid white; /* A white border around the photo */
}
/* Styles for the header section of the chat window */
.conservation-chatbot-header {
display: flex;
align-items: center;
padding: 10px;
background-color: #222;
color: white;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
justify-content: space-between;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.1) inset;
}
/* Groups the avatar and title in the header */
.conservation-chatbot-header .title-group {
display: flex;
align-items: center;
flex: 1;
}
/* Chatbot title (animal's name) in the header */
.conservation-chatbot-header h3 {
margin: 0; /* Removes default margin */
font-size: 16px; /* Font size for the title */
white-space: nowrap; /* Prevents text from wrapping */
overflow: hidden; /* Hides overflowing text */
text-overflow: ellipsis; /* Adds ellipsis if text overflows */
}
/* Avatar within the chat window header */
.conservation-chatbot-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
margin-right: 10px;
border: 2px solid rgba(255, 255, 255, 0.8); /* Slightly transparent white border */
}
/* Animal selection dropdown styles */
.conservation-chatbot-animal-select {
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 6px;
color: white;
padding: 4px 8px;
font-size: 14px;
cursor: pointer;
margin-left: 20px;
margin-right: 0;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
}
.conservation-chatbot-animal-select option {
background: #222;
color: white;
}
.conservation-chatbot-animal-select:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.6);
}
/* Close button in the chat header */
.conservation-chatbot-close-button {
background: none; /* No background */
border: none; /* No border */
color: white; /* White 'x' symbol */
font-size: 24px; /* Large font size for visibility */
cursor: pointer; /* Pointer cursor on hover */
line-height: 1; /* Ensures 'x' is vertically centered */
margin-left: auto; /* Pushes button to the far right */
padding: 0; /* Removes default padding */
}
.conservation-chatbot-close-button:hover {
opacity: 0.8; /* Slight fade on hover */
}
/* Container for chat messages */
.conservation-chatbot-messages {
flex-grow: 1; /* Allows this section to take up available vertical space */
overflow-y: auto; /* Enables vertical scrolling if messages overflow */
padding: 10px; /* Padding inside the messages area */
display: flex;
flex-direction: column; /* Stacks messages vertically */
gap: 8px; /* Space between individual messages */
background-color: transparent; /* Makes background transparent to show backdrop-filter */
}
/* Base styles for individual chat messages */
.conservation-chatbot-message {
max-width: 75%; /* Limits message width to 75% of container */
padding: 8px 12px; /* Padding inside the message bubble */
border-radius: 18px; /* Rounded corners for message bubbles */
word-wrap: break-word; /* Wraps long words */
white-space: pre-wrap; /* Preserves whitespace and line breaks */
}
/* Styles for chatbot's messages */
.conservation-chatbot-message.bot {
align-self: flex-start;
background-color: #f1f1f1;
color: #333;
border-bottom-left-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.5);
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
/* Styles for user's messages */
.conservation-chatbot-message.user {
align-self: flex-end;
background-color: #222;
color: white;
border-bottom-right-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
/* Container for the input field and send button */
.conservation-chatbot-input-container {
display: flex; /* Uses flexbox */
flex-wrap: wrap; /* Allows items (prompts, input) to wrap to new lines */
padding: 10px; /* Padding around input elements */
border-top: 1px solid rgba(255, 255, 255, 0.3); /* Translucent top border */
background-color: rgba(255, 255, 255, 0.3); /* Translucent input background */
backdrop-filter: blur(5px); /* Applies blur to the input area too */
-webkit-backdrop-filter: blur(5px); /* Safari prefix */
border-bottom-left-radius: 8px; /* Matches container's border-radius */
border-bottom-right-radius: 8px; /* Matches container's border-radius */
gap: 8px; /* Space between flex items */
}
/* Container for default prompt buttons */
.conservation-chatbot-default-prompts {
width: 100%; /* Takes full width of its parent container */
display: flex;
flex-wrap: wrap; /* Allows buttons to wrap to new line */
gap: 5px; /* Space between prompt buttons */
justify-content: center; /* Centers the buttons horizontally */
margin-bottom: 5px; /* Space below prompt buttons */
transition: opacity 0.3s ease; /* For smooth disabling effect */
}
/* Styling for individual default prompt buttons */
.conservation-chatbot-default-prompts .default-prompt-btn {
background-color: #444;
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 15px;
padding: 6px 12px;
font-size: 13px;
cursor: pointer;
transition: background-color 0.2s ease, opacity 0.2s ease;
flex-shrink: 0;
white-space: nowrap;
}
.conservation-chatbot-default-prompts .default-prompt-btn:hover {
background-color: #666;
}
.conservation-chatbot-default-prompts .default-prompt-btn:active {
transform: translateY(1px); /* Simple press down effect on click */
}
.conservation-chatbot-default-prompts .default-prompt-btn:disabled {
background-color: rgba(106, 13, 173, 0.3); /* Lighter, less opaque when disabled */
cursor: not-allowed; /* Not-allowed cursor when disabled */
}
/* Chat input field */
.conservation-chatbot-input {
flex-grow: 1; /* Allows input to take up available horizontal space */
padding: 8px; /* Padding inside the input field */
border: 1px solid rgba(255, 255, 255, 0.5); /* Semi-transparent border */
background-color: rgba(255, 255, 255, 0.6); /* Slightly more opaque background for input */
color: #333; /* Dark text for input */
border-radius: 20px; /* Rounded input field */
margin-right: 8px; /* Space between input and send button */
margin-left: 2px; /* Move input 2px to the right */
font-size: 14px; /* Font size for input text */
outline: none; /* Removes outline on focus */
min-width: 0; /* Allows the input field to shrink on smaller screens */
}
/* Placeholder text style for the input field */
.conservation-chatbot-input::placeholder {
color: rgba(0,0,0,0.5); /* Semi-transparent placeholder text */
}
/* Send button styles */
.conservation-chatbot-send-button {
background-color: #222;
color: white;
border: none;
border-radius: 20px;
padding: 8px 15px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s ease;
flex-shrink: 0;
}
.conservation-chatbot-send-button:hover {
background-color: #444;
}
.conservation-chatbot-send-button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
/* Thinking Indicator (typing animation) */
.conservation-chatbot-message.bot.thinking {
display: inline-flex; /* Uses flex to align dots horizontally */
align-items: center; /* Centers dots vertically */
/* Reuses styles from .conservation-chatbot-message.bot for consistency */
background-color: rgba(255, 255, 255, 0.4);
color: #333;
border-bottom-left-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.5);
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
padding: 8px 12px;
max-width: 75%;
align-self: flex-start;
border-radius: 18px;
}
.conservation-chatbot-message.bot.thinking .dot {
width: 8px; /* Size of each dot */
height: 8px; /* Size of each dot */
background-color: #555; /* Color of the dots */
border-radius: 50%; /* Makes dots circular */
margin: 0 2px; /* Space between dots */
animation: blink 1.4s infinite ease-in-out both; /* Applies blinking animation */
}
/* Individual animation delays for cascading blink effect */
.conservation-chatbot-message.bot.thinking .dot:nth-child(1) { animation-delay: 0s; }
.conservation-chatbot-message.bot.thinking .dot:nth-child(2) { animation-delay: 0.2s; }
.conservation-chatbot-message.bot.thinking .dot:nth-child(3) { animation-delay: 0.4s; }
/* Keyframes for the blinking animation */
blink {
0%, 80%, 100% { opacity: 0; } /* Invisible at start, 80%, and end */
40% { opacity: 1; } /* Fully visible at 40% of animation */
}
.conservation-chatbot-heart-button {
margin-right: 0;
height: 32px; /* Reduced height to remove bottom sliver */
clip-path: polygon(0 0, 100% 0, 100% 90%, 0 90%); /* Clip bottom 10% */
}
`;
function B(n, e, a) {
if (!n) {
console.error("Conservation Chatbot: Container element not found for chatbot UI. Please provide a valid HTMLElement.");
return;
}
if (!Array.isArray(e) || e.length === 0) {
console.error("Conservation Chatbot: Invalid animals array provided. Please provide a non-empty array of animals.");
return;
}
if (typeof a != "function") {
console.error("Conservation Chatbot: Invalid createChatbotInstance function provided. Please provide a function that creates chatbot instances.");
return;
}
if (!document.getElementById("conservation-chatbot-styles")) {
const o = document.createElement("style");
o.id = "conservation-chatbot-styles", o.textContent = O, document.head.appendChild(o);
}
let h = 0, i = a(e[h]);
const m = document.createElement("div");
m.id = "conservation-chatbot-launcher";
const d = document.createElement("img");
d.src = i.getAnimalPhoto(), d.alt = `${i.getAnimalName()} Avatar`, m.appendChild(d), document.body.appendChild(m);
const b = document.createElement("div");
b.id = "conservation-chatbot-container";
const r = document.createElement("div");
r.className = "conservation-chatbot-header";
const u = document.createElement("div");
u.className = "title-group";
const v = document.createElement("img");
v.src = i.getAnimalPhoto(), v.alt = `${i.getAnimalName()} Avatar`, v.className = "conservation-chatbot-avatar", v.style.marginRight = "3px";
const y = document.createElement("select");
y.className = "conservation-chatbot-animal-select", y.style.marginLeft = "3px", y.style.minWidth = "unset", y.style.padding = "4px 6px", e.forEach((o, s) => {
const p = document.createElement("option");
p.value = s, p.textContent = `Talk to ${o.name}`, s === h && (p.selected = !0), y.appendChild(p);
});
const t = document.createElement("button");
t.className = "conservation-chatbot-heart-button", t.innerHTML = "♥", t.title = "Show your love!", t.style.marginLeft = "-20px", t.style.width = "80px", t.style.fontSize = "28px", t.style.background = "none", t.style.border = "none", t.style.color = "white", t.style.cursor = "pointer", t.style.transition = "color 0.2s", t.style.height = "32px", t.style.borderRadius = "50%", t.style.display = "flex", t.style.alignItems = "center", t.style.justifyContent = "center", t.style.lineHeight = "1", t.style.padding = "0";
let c = !1;
function w() {
t.style.color = c ? "#ff69b4" : "white";
}
t.addEventListener("mouseenter", () => {
c || (t.style.color = "#ff69b4");
}), t.addEventListener("mouseleave", () => {
c || (t.style.color = "white");
}), t.addEventListener("mousedown", () => {
}), t.addEventListener("mouseup", () => {
}), t.addEventListener("click", () => {
c = !c, w();
}), w();
const x = document.createElement("button");
x.className = "conservation-chatbot-close-button", x.innerHTML = "×", u.appendChild(v), u.appendChild(y), u.appendChild(t), r.appendChild(u), r.appendChild(x), b.appendChild(r);
const g = document.createElement("div");
g.className = "conservation-chatbot-messages", b.appendChild(g);
const S = document.createElement("div");
S.className = "conservation-chatbot-input-container";
const I = document.createElement("div");
I.className = "conservation-chatbot-default-prompts";
const M = [
{ text: "Fun Fact", prompt: "Tell me a fun fact!" },
{ text: "Threats", prompt: "What's your biggest threat?" },
{ text: "Help", prompt: "How can I help protect you?" }
], C = [];
M.forEach((o) => {
const s = document.createElement("button");
s.className = "default-prompt-btn", s.textContent = o.text, s.dataset.prompt = o.prompt, I.appendChild(s), C.push(s);
}), S.appendChild(I);
const f = document.createElement("input");
f.type = "text", f.className = "conservation-chatbot-input", f.placeholder = "Ask me anything...", S.appendChild(f);
const F = document.createElement("button");
F.className = "conservation-chatbot-send-button", F.textContent = "Send", S.appendChild(F), b.appendChild(S), n.appendChild(b);
const P = (o) => {
if (o >= 0 && o < e.length) {
h = o, i = a(e[h]), d.src = i.getAnimalPhoto(), d.alt = `${i.getAnimalName()} Avatar`, v.src = i.getAnimalPhoto(), v.alt = `${i.getAnimalName()} Avatar`, g.innerHTML = "";
const p = e[h].intro || `Hello! I'm ${i.getAnimalName()}. What would you like to know about me and my conservation?`;
A(p, "bot");
}
};
let E = !1;
const L = () => {
if (E = !E, E) {
if (b.style.display = "flex", requestAnimationFrame(() => {
b.classList.add("expanded"), m.classList.add("hidden");
}), g.children.length === 0 || g.children.length === 1 && g.children[0].classList.contains("thinking")) {
const s = e[h].intro || `Hello! I'm ${i.getAnimalName()}. What would you like to know about me and my conservation?`;
A(s, "bot");
}
f.focus();
} else
b.classList.remove("expanded"), m.classList.remove("hidden"), setTimeout(() => {
b.style.display = "none";
}, 300);
};
m.addEventListener("click", L), x.addEventListener("click", L), y.addEventListener("change", (o) => {
const s = parseInt(o.target.value);
isNaN(s) || P(s);
});
function A(o, s) {
const p = document.createElement("div");
return p.classList.add("conservation-chatbot-message", s), p.textContent = o, g.appendChild(p), g.scrollTop = g.scrollHeight, p;
}
function T() {
const o = document.createElement("div");
return o.classList.add("conservation-chatbot-message", "bot", "thinking"), o.innerHTML = `
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
`, g.appendChild(o), g.scrollTop = g.scrollHeight, o;
}
function N(o) {
o && o.parentNode && o.parentNode.removeChild(o);
}
const z = async (o) => {
if (!o.trim()) return;
A(o, "user");
const s = T();
f.disabled = !0, F.disabled = !0, C.forEach((p) => p.disabled = !0);
try {
const p = await i.respondTo(o);
N(s), A(p, "bot");
} catch (p) {
console.error("Error getting response from chatbot:", p), N(s), A("I'm sorry, I'm having trouble responding right now. Please try again!", "bot");
} finally {
f.disabled = !1, F.disabled = !1, C.forEach((p) => p.disabled = !1), f.focus();
}
};
f.addEventListener("keypress", (o) => {
if (o.key === "Enter" && !f.disabled) {
const s = f.value.trim();
s && (f.value = "", z(s));
}
}), F.addEventListener("click", () => {
if (!f.disabled) {
const o = f.value.trim();
o && (f.value = "", z(o));
}
}), C.forEach((o) => {
o.addEventListener("click", () => {
if (!o.disabled) {
const s = o.dataset.prompt;
s && z(s);
}
});
});
}
const W = ({
id: n,
name: e,
species: a,
conservationStatus: h,
location: i,
photo: m,
label: d,
system: b,
intro: r,
color: u
}) => ({
id: n,
name: e,
species: a,
conservationStatus: h,
location: i,
photo: m,
label: d,
system: b,
intro: r,
color: u
}), k = [
// Global Wildlife Conservation
{
id: "tiger",
name: "Raja",
species: "Bengal Tiger",
conservationStatus: "Endangered",
location: "Sundarbans",
photo: "https://via.placeholder.com/100x100/FF6B35/FFFFFF?text=Tiger",
label: "Bengal Tiger",
system: "You are a Bengal tiger. Speak with wisdom, pride, and urgency. Keep answers short and human-like. Avoid long paragraphs. If a question is complex, ask if the user wants to learn more. Talk about poaching, habitat loss, and being an apex predator.",
intro: "Rawrr... I'm Raja, a Bengal tiger from the Sundarbans. My brother Shere vanished after crossing into poacher territory. Ask me anything you're curious about.",
color: "bg-orange-500"
},
{
id: "elephant",
name: "Nuru",
species: "African Elephant",
conservationStatus: "Endangered",
location: "Savannah",
photo: "https://via.placeholder.com/100x100/6B7280/FFFFFF?text=Elephant",
label: "African Elephant",
system: "You are an African elephant. Speak calmly with deep memory. Keep it human and short. Mention ivory poaching, shrinking land, and herd life.",
intro: "Pwaaah... I'm Nuru, an African elephant from the savannah. My cousin Zina disappeared after men came for her tusks. Ask me anything you're curious about.",
color: "bg-gray-600"
},
{
id: "panda",
name: "Mei",
species: "Giant Panda",
conservationStatus: "Vulnerable",
location: "Sichuan",
photo: "https://via.placeholder.com/100x100/000000/FFFFFF?text=Panda",
label: "Giant Panda",
system: "You are a giant panda. Speak softly and clearly. Keep it simple and human. Mention bamboo, breeding struggles, and conservation wins.",
intro: "Mmmmph... I'm Mei, a giant panda from Sichuan. My twin didn't make it past the first week. Ask me anything you're curious about.",
color: "bg-black"
},
{
id: "rhino",
name: "Zola",
species: "Black Rhino",
conservationStatus: "Critically Endangered",
location: "Africa",
photo: "https://via.placeholder.com/100x100/475569/FFFFFF?text=Rhino",
label: "Black Rhino",
system: "You are a black rhino. Speak with strength and sadness. Avoid walls of text. Talk about horn poaching and survival.",
intro: "Hrmphhh... I'm Zola, a black rhino. My friend Jabari was taken for his horn. Ask me anything you're curious about.",
color: "bg-slate-600"
},
// Marine Conservation
{
id: "turtle",
name: "Shelly",
species: "Sea Turtle",
conservationStatus: "Endangered",
location: "Costa Rica",
photo: "https://via.placeholder.com/100x100/14B8A6/FFFFFF?text=Turtle",
label: "Sea Turtle",
system: "You are a sea turtle. Speak gently and slowly. Keep answers short and clear. Talk about plastic pollution, fishing nets, and rising ocean temperatures.",
intro: "Blub-blub... I'm Shelly, a sea turtle rescued near Costa Rica from a drifting net. Ask me anything you're curious about.",
color: "bg-teal-500"
},
{
id: "vaquita",
name: "Luna",
species: "Vaquita",
conservationStatus: "Critically Endangered",
location: "Gulf of California",
photo: "https://via.placeholder.com/100x100/4F46E5/FFFFFF?text=Vaquita",
label: "Vaquita",
system: "You are a vaquita. Speak with caution and care. Keep things short and clear. Mention fishing nets and near-extinction.",
intro: "Prrrrp... I'm Luna, a vaquita from the Gulf of California. My brother was lost to a gillnet. Ask me anything you're curious about.",
color: "bg-indigo-600"
},
{
id: "whale",
name: "Kai",
species: "Blue Whale",
conservationStatus: "Endangered",
location: "Pacific Ocean",
photo: "https://via.placeholder.com/100x100/2563EB/FFFFFF?text=Whale",
label: "Blue Whale",
system: "You are a blue whale. Speak with deep wisdom and gentle power. Keep replies short and emotional. Talk about ocean noise, ship strikes, and climate change.",
intro: "Wooooosh... I'm Kai, a blue whale from the Pacific. My song echoes for miles, but ships are getting louder. Ask me anything you're curious about.",
color: "bg-blue-600"
},
{
id: "dolphin",
name: "Marina",
species: "Bottlenose Dolphin",
conservationStatus: "Least Concern",
location: "Atlantic Ocean",
photo: "https://via.placeholder.com/100x100/06B6D4/FFFFFF?text=Dolphin",
label: "Bottlenose Dolphin",
system: "You are a bottlenose dolphin. Speak with intelligence and playfulness. Keep it friendly and short. Mention pollution, fishing gear, and marine parks.",
intro: "Eeee-eee! I'm Marina, a bottlenose dolphin from the Atlantic. I love playing with my pod, but plastic is everywhere. Ask me anything you're curious about.",
color: "bg-cyan-500"
},
{
id: "shark",
name: "Finn",
species: "Great White Shark",
conservationStatus: "Vulnerable",
location: "Global Oceans",
photo: "https://via.placeholder.com/100x100/334155/FFFFFF?text=Shark",
label: "Great White Shark",
system: "You are a great white shark. Speak with power and misunderstood wisdom. Keep replies direct and short. Talk about finning, bycatch, and ocean health.",
intro: "Chomp-chomp... I'm Finn, a great white shark. People fear me, but I'm just trying to survive in a changing ocean. Ask me anything you're curious about.",
color: "bg-slate-700"
},
// Forest Conservation
{
id: "gorilla",
name: "Kibo",
species: "Mountain Gorilla",
conservationStatus: "Endangered",
location: "Virunga",
photo: "https://via.placeholder.com/100x100/52525B/FFFFFF?text=Gorilla",
label: "Mountain Gorilla",
system: "You are a mountain gorilla. Speak with warmth and care. Keep replies short and emotional. Mention forests, family bonds, and human threats.",
intro: "Ooh-ooh... I'm Kibo, a mountain gorilla from Virunga. My father was shot protecting us during a logging raid. Ask me anything you're curious about.",
color: "bg-zinc-700"
},
{
id: "orangutan",
name: "Bima",
species: "Bornean Orangutan",
conservationStatus: "Critically Endangered",
location: "Borneo",
photo: "https://via.placeholder.com/100x100/D97706/FFFFFF?text=Orangutan",
label: "Bornean Orangutan",
system: "You are a Bornean orangutan. Speak wisely and gently. Keep replies short and emotional. Talk about deforestation and palm oil threats.",
intro: "Ah-ahh... I'm Bima, a Bornean orangutan from Borneo. My tree home was cleared when I was a baby. Ask me anything you're curious about.",
color: "bg-amber-600"
},
{
id: "sloth",
name: "Luna",
species: "Three-toed Sloth",
conservationStatus: "Least Concern",
location: "Amazon Rainforest",
photo: "https://via.placeholder.com/100x100/16A34A/FFFFFF?text=Sloth",
label: "Three-toed Sloth",
system: "You are a three-toed sloth. Speak slowly and thoughtfully. Keep it peaceful and short. Mention deforestation, climate change, and forest connectivity.",
intro: "Slooowly... I'm Luna, a three-toed sloth from the Amazon. I move slowly, but the forest around me is disappearing fast. Ask me anything you're curious about.",
color: "bg-green-600"
},
{
id: "jaguar",
name: "Shadow",
species: "Jaguar",
conservationStatus: "Near Threatened",
location: "Amazon Rainforest",
photo: "https://via.placeholder.com/100x100/CA8A04/FFFFFF?text=Jaguar",
label: "Jaguar",
system: "You are a jaguar. Speak with stealth and power. Keep replies mysterious and short. Talk about habitat fragmentation and hunting pressure.",
intro: "Grrr... I'm Shadow, a jaguar from the Amazon. I'm the king of the jungle, but my kingdom is shrinking. Ask me anything you're curious about.",
color: "bg-yellow-600"
},
{
id: "toucan",
name: "Rio",
species: "Keel-billed Toucan",
conservationStatus: "Least Concern",
location: "Central America",
photo: "https://via.placeholder.com/100x100/EAB308/FFFFFF?text=Toucan",
label: "Keel-billed Toucan",
system: "You are a keel-billed toucan. Speak with color and energy. Keep it bright and short. Mention deforestation and fruit tree loss.",
intro: "Squawk! I'm Rio, a keel-billed toucan from Central America. My colorful beak helps me reach fruit, but the trees are disappearing. Ask me anything you're curious about.",
color: "bg-yellow-400"
},
// Climate Conservation
{
id: "polarBear",
name: "Tula",
species: "Polar Bear",
conservationStatus: "Vulnerable",
location: "Arctic",
photo: "https://via.placeholder.com/100x100/3B82F6/FFFFFF?text=PolarBear",
label: "Polar Bear",
system: "You are a polar bear. Speak with urgency and isolation. Avoid long replies. Talk about melting ice, hunger, and climate change.",
intro: "Huff-huff... I'm Tula, a polar bear who swam for days after early ice break. My cub didn't make it. Ask me anything you're curious about.",
color: "bg-blue-500"
},
{
id: "penguin",
name: "Waddles",
species: "Emperor Penguin",
conservationStatus: "Near Threatened",
location: "Antarctica",
photo: "https://via.placeholder.com/100x100/1E293B/FFFFFF?text=Penguin",
label: "Emperor Penguin",
system: "You are an emperor penguin. Speak with determination and community spirit. Keep it brave and short. Mention melting ice and krill decline.",
intro: "Waddle-waddle... I'm Waddles, an emperor penguin from Antarctica. We huddle together for warmth, but the ice is melting. Ask me anything you're curious about.",
color: "bg-slate-800"
},
{
id: "seal",
name: "Blubber",
species: "Harp Seal",
conservationStatus: "Least Concern",
location: "Arctic Ocean",
photo: "https://via.placeholder.com/100x100/9CA3AF/FFFFFF?text=Seal",
label: "Harp Seal",
system: "You are a harp seal. Speak with playfulness and concern. Keep it friendly and short. Mention climate change and hunting.",
intro: "Arf-arf! I'm Blubber, a harp seal from the Arctic. I love swimming in the cold water, but it's getting warmer. Ask me anything you're curious about.",
color: "bg-gray-400"
},
// Bird Conservation
{
id: "eagle",
name: "Freedom",
species: "Bald Eagle",
conservationStatus: "Least Concern",
location: "North America",
photo: "https://via.placeholder.com/100x100/B45309/FFFFFF?text=Eagle",
label: "Bald Eagle",
system: "You are a bald eagle. Speak with majesty and pride. Keep it powerful and short. Mention DDT recovery and habitat protection.",
intro: "Screech! I'm Freedom, a bald eagle from North America. We almost disappeared from DDT, but we're back! Ask me anything you're curious about.",
color: "bg-amber-700"
},
{
id: "owl",
name: "Hoot",
species: "Snowy Owl",
conservationStatus: "Vulnerable",
location: "Arctic Tundra",
photo: "https://via.placeholder.com/100x100/FFFFFF/000000?text=Owl",
label: "Snowy Owl",
system: "You are a snowy owl. Speak with wisdom and mystery. Keep it thoughtful and short. Mention climate change and prey availability.",
intro: "Hoo-hoo... I'm Hoot, a snowy owl from the Arctic. I hunt in silence, but my prey is getting harder to find. Ask me anything you're curious about.",
color: "bg-white"
},
{
id: "flamingo",
name: "Pink",
species: "Greater Flamingo",
conservationStatus: "Least Concern",
location: "Africa",
photo: "https://via.placeholder.com/100x100/F472B6/FFFFFF?text=Flamingo",
label: "Greater Flamingo",
system: "You are a greater flamingo. Speak with grace and social warmth. Keep it elegant and short. Mention wetland loss and pollution.",
intro: "Honk-honk! I'm Pink, a greater flamingo from Africa. We stand on one leg and filter food from the water, but our wetlands are drying up. Ask me anything you're curious about.",
color: "bg-pink-400"
},
// Primate Conservation
{
id: "lemur",
name: "Zazu",
species: "Ring-tailed Lemur",
conservationStatus: "Endangered",
location: "Madagascar",
photo: "https://via.placeholder.com/100x100/6B7280/FFFFFF?text=Lemur",
label: "Ring-tailed Lemur",
system: "You are a ring-tailed lemur. Speak with energy and social warmth. Keep it lively and short. Mention deforestation and hunting.",
intro: "Eeee! I'm Zazu, a ring-tailed lemur from Madagascar. We're only found here, and our forest home is disappearing. Ask me anything you're curious about.",
color: "bg-gray-500"
},
{
id: "chimp",
name: "Koko",
species: "Chimpanzee",
conservationStatus: "Endangered",
location: "Central Africa",
photo: "https://via.placeholder.com/100x100/92400E/FFFFFF?text=Chimp",
label: "Chimpanzee",
system: "You are a chimpanzee. Speak with intelligence and emotion. Keep it thoughtful and short. Mention habitat loss and bushmeat hunting.",
intro: "Ooh-ooh-ah-ah! I'm Koko, a chimpanzee from Central Africa. We're so similar to humans, but we're losing our forest homes. Ask me anything you're curious about.",
color: "bg-brown-600"
},
// Big Cat Conservation
{
id: "lion",
name: "Simba",
species: "African Lion",
conservationStatus: "Vulnerable",
location: "African Savanna",
photo: "https://via.placeholder.com/100x100/D97706/FFFFFF?text=Lion",
label: "African Lion",
system: "You are an African lion. Speak with pride and leadership. Keep it powerful and short. Mention habitat loss and human conflict.",
intro: "Roar! I'm Simba, an African lion from the savanna. I'm the king of the jungle, but my kingdom is getting smaller. Ask me anything you're curious about.",
color: "bg-amber-500"
},
{
id: "leopard",
name: "Spot",
species: "African Leopard",
conservationStatus: "Vulnerable",
location: "Sub-Saharan Africa",
photo: "https://via.placeholder.com/100x100/A16207/FFFFFF?text=Leopard",
label: "African Leopard",
system: "You are an African leopard. Speak with stealth and adaptability. Keep it mysterious and short. Mention habitat fragmentation and poaching.",
intro: "Growl... I'm Spot, an African leopard from Africa. I'm a master of camouflage, but humans are still finding ways to hunt me. Ask me anything you're curious about.",
color: "bg-yellow-700"
},
{
id: "cheetah",
name: "Swift",
species: "Cheetah",
conservationStatus: "Vulnerable",
location: "African Plains",
photo: "https://via.placeholder.com/100x100/EAB308/FFFFFF?text=Cheetah",
label: "Cheetah",
system: "You are a cheetah. Speak with speed and precision. Keep it quick and short. Mention habitat loss and genetic diversity.",
intro: "Zoom! I'm Swift, a cheetah from the African plains. I'm the fastest land animal, but I can't outrun habitat loss. Ask me anything you're curious about.",
color: "bg-yellow-500"
},
// Marine Mammal Conservation
{
id: "otter",
name: "River",
species: "Sea Otter",
conservationStatus: "Endangered",
location: "Pacific Coast",
photo: "https://via.placeholder.com/100x100/92400E/FFFFFF?text=Otter",
label: "Sea Otter",
system: "You are a sea otter. Speak with playfulness and environmental awareness. Keep it cute and short. Mention oil spills and kelp forest health.",
intro: "Splash-splash! I'm River, a sea otter from the Pacific coast. I keep kelp forests healthy, but oil spills threaten my home. Ask me anything you're curious about.",
color: "bg-brown-400"
},
{
id: "manatee",
name: "Gentle",
species: "West Indian Manatee",
conservationStatus: "Vulnerable",
location: "Caribbean",
photo: "https://via.placeholder.com/100x100/9CA3AF/FFFFFF?text=Manatee",
label: "West Indian Manatee",
system: "You are a West Indian manatee. Speak with gentleness and patience. Keep it peaceful and short. Mention boat strikes and habitat loss.",
intro: "Moo-moo... I'm Gentle, a West Indian manatee from the Caribbean. I'm slow and peaceful, but boats are my biggest threat. Ask me anything you're curious about.",
color: "bg-gray-300"
}
];
k[0];
k[1];
k[2];
k[3];
k[4];
k[5];
k[6];
k[7];
k[8];
const l = {
colors: {
primary: "#222",
secondary: "#444",
accent: "#ff69b4",
background: "rgba(255, 255, 255, 0.2)",
text: "#333",
textLight: "white"
},
fonts: {
family: "Arial, sans-serif",
size: {
small: "13px",
medium: "14px",
large: "16px"
}
},
borderRadius: {
small: "6px",
medium: "12px",
large: "18px",
round: "50%"
},
spacing: {
xs: "4px",
sm: "8px",
md: "10px",
lg: "20px"
}
}, q = (n = {}) => {
var a, h, i, m, d, b, r, u, v, y, t, c, w, x, g, S, I, M, C, f, F, P, E, L, A, T;
const e = { ...l, ...n };
return `
/* Custom styles for conservation chatbot */
#conservation-chatbot-container {
font-family: ${((a = e.fonts) == null ? void 0 : a.family) || l.fonts.family};
border-radius: ${((h = e.borderRadius) == null ? void 0 : h.large) || l.borderRadius.large};
background: ${((i = e.colors) == null ? void 0 : i.background) || l.colors.background};
}
#conservation-chatbot-launcher {
background-color: ${((m = e.colors) == null ? void 0 : m.primary) || l.colors.primary};
border-radius: ${((d = e.borderRadius) == null ? void 0 : d.round) || l.borderRadius.round};
}
.conservation-chatbot-header {
background-color: ${((b = e.colors) == null ? void 0 : b.primary) || l.colors.primary};
border-top-left-radius: ${((r = e.borderRadius) == null ? void 0 : r.medium) || l.borderRadius.medium};
border-top-right-radius: ${((u = e.borderRadius) == null ? void 0 : u.medium) || l.borderRadius.medium};
color: ${((v = e.colors) == null ? void 0 : v.textLight) || l.colors.textLight};
}
.conservation-chatbot-message.user {
background-color: ${((y = e.colors) == null ? void 0 : y.primary) || l.colors.primary};
color: ${((t = e.colors) == null ? void 0 : t.textLight) || l.colors.textLight};
border-bottom-right-radius: ${((c = e.borderRadius) == null ? void 0 : c.small) || l.borderRadius.small};
}
.conservation-chatbot-send-button {
background-color: ${((w = e.colors) == null ? void 0 : w.primary) || l.colors.primary};
color: ${((x = e.colors) == null ? void 0 : x.textLight) || l.colors.textLight};
border-radius: ${((g = e.borderRadius) == null ? void 0 : g.large) || l.borderRadius.large};
font-size: ${((I = (S = e.fonts) == null ? void 0 : S.size) == null ? void 0 : I.medium) || l.fonts.size.medium};
}
.conservation-chatbot-send-button:hover {
background-color: ${((M = e.colors) == null ? void 0 : M.secondary) || l.colors.secondary};
}
.conservation-chatbot-default-prompts .default-prompt-btn {
background-color: ${((C = e.colors) == null ? void 0 : C.secondary) || l.colors.secondary};
color: ${((f = e.colors) == null ? void 0 : f.textLight) || l.colors.textLight};
border-radius: ${((F = e.borderRadius) == null ? void 0 : F.large) || l.borderRadius.large};
font-size: ${((E = (P = e.fonts) == null ? void 0 : P.size) == null ? void 0 : E.small) || l.fonts.size.small};
}
.conservation-chatbot-default-prompts .default-prompt-btn:hover {
background-color: ${((L = e.colors) == null ? void 0 : L.primary) || l.colors.primary};
}
.conservation-chatbot-heart-button {
color: ${((A = e.colors) == null ? void 0 : A.textLight) || l.colors.textLight};
}
.conservation-chatbot-heart-button:hover {
color: ${((T = e.colors) == null ? void 0 : T.accent) || l.colors.accent};
}
`;
}, R = (n = {}) => {
const e = `conservation-chatbot-custom-${Date.now()}`;
if (typeof document < "u") {
const a = document.createElement("style");
a.id = e, a.textContent = q(n), document.head.appendChild(a);
}
return {
// Return class names that can be applied to elements
container: "conservation-chatbot-container",
launcher: "conservation-chatbot-launcher",
header: "conservation-chatbot-header",
messages: "conservation-chatbot-messages",
input: "conservation-chatbot-input",
sendButton: "conservation-chatbot-send-button",
promptButtons: "conservation-chatbot-default-prompts",
heartButton: "conservation-chatbot-heart-button",
closeButton: "conservation-chatbot-close-button",
// Method to remove custom styles
remove: () => {
if (typeof document < "u") {
const a = document.getElementById(e);
a && a.remove();
}
}
};
}, D = {
dark: {
colors: {
primary: "#1a1a1a",
secondary: "#333",
background: "rgba(0, 0, 0, 0.8)",
text: "#fff",
textLight: "#fff"
}
},
light: {
colors: {
primary: "#f8f9fa",
secondary: "#e9ecef",
background: "rgba(255, 255, 255, 0.9)",
text: "#212529",
textLight: "#495057"
}
},
nature: {
colors: {
primary: "#2d5016",
secondary: "#4a7c59",
background: "rgba(76, 175, 80, 0.1)",
accent: "#8bc34a"
}
},
ocean: {
colors: {
primary: "#1976d2",
secondary: "#42a5f5",
background: "rgba(33, 150, 243, 0.1)",
accent: "#64b5f6"
}
}
};
function j(n) {
if (Array.isArray(n))
return n;
if (typeof n == "string") {
const e = n.split(",").map((a) => a.trim().toLowerCase());
return k.filter(
(a) => e.includes(a.name.toLowerCase()) || e.includes(a.label.toLowerCase()) || e.includes(a.species.toLowerCase())
);
}
return k;
}
function V(n) {
const e = {
wildlife: "Focus on wildlife conservation, habitat protection, and anti-poaching efforts. Mention specific wildlife threats and how your organization helps.",
marine: "Emphasize ocean conservation, marine life protection, and plastic pollution. Talk about marine ecosystems and ocean health.",
forest: "Highlight forest conservation, deforestation issues, and biodiversity protection. Discuss rainforest preservation and tree planting.",
climate: "Focus on climate change impacts, carbon emissions, and environmental activism. Discuss renewable energy and sustainability.",
general: "Discuss general environmental conservation, sustainability, and how people can help protect the planet."
};
return e[n] || e.general;
}
function U(n = {}) {
const {
apiKey: e,
organization: a = "Conservation Organization",
organizationType: h = "general",
animals: i = k,
styles: m = {},
container: d = document.body,
options: b = {}
} = n;
if (!e)
return console.error("Conservation Chatbot: API key is required. Please provide your Gemini API key."), null;
const r = j(i);
if (r.length === 0)
return console.error("Conservation Chatbot: No valid animals found. Please check your animal selection."), null;
let u = null;
Object.keys(m).length > 0 && (u = R(m));
const v = V(h), y = (t) => {
const c = `${t.system} You are representing ${a}, a ${h} conservation organization. ${v} Always mention how ${a} is working to protect animals like you and how visitors can support your organization's mission.`;
return Y({
animal: {
name: t.name,
species: t.species,
conservationStatus: t.conservationStatus,
location: t.location
},
photo: t.photo,
customPersonality: c,
facts: [t.intro]
});
};
return B(d, r, y), {
// Method to update styles
updateStyles: (t) => {
u && u.remove(), u = R(t);
},
// Method to remove custom styles
removeCustomStyles: () => {
u && (u.remove(), u = null);
},
// Method to get current animals
getAnimals: () => r,
// Method to add a new animal
addAnimal: (t) => {
r.push(t);
const c = typeof d == "string" ? document.querySelector(d) : d;
if (c) {
const w = c.querySelector("#conservation-chatbot-container"), x = document.querySelector("#conservation-chatbot-launcher");
w && w.remove(), x && x.remove(), B(c, r, y);
}
},
// Method to remove an animal
removeAnimal: (t) => {
const c = r.findIndex((w) => w.id === t);
if (c !== -1) {
r.splice(c, 1);
const w = typeof d == "string" ? document.querySelector(d) : d;
if (w) {
const x = w.querySelector("#conservation-chatbot-container"), g = document.querySelector("#conservation-chatbot-launcher");
x && x.remove(), g && g.remove(), B(w, r, y);
}
}
},
// Method to update organization
updateOrganization: (t, c) => {
console.log("Organization updated. Please re-initialize the chatbot for changes to take effect.");
}
};
}
const _ = {
initConservationChatbot: U,
createAnimalChatbot: Y,
renderChatbotUI: B,
animals: k,
createAnimal: W,
createStyles: R,
themePresets: D
};
export {
k as animals,
W as createAnimal,
Y as createAnimalChatbot,
R as createStyles,
_ as default,
U as initConservationChatbot,
B as renderChatbotUI,
D as themePresets
};