Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions source/_static/script/chatbot-bubble.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
(function () {
document.addEventListener("DOMContentLoaded", function () {
var icon = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 64 64" aria-hidden="true" focusable="false">
<!-- rounded head -->
<rect x="10" y="14" width="44" height="32" rx="8" fill="#fff"/>
<!-- antenna -->
<line x1="32" y1="14" x2="32" y2="6" stroke="#fff" stroke-width="3" stroke-linecap="round"/>
<circle cx="32" cy="5" r="3" fill="#fff"/>
<!-- eyes -->
<rect x="18" y="25" width="10" height="8" rx="3" fill="#4AABE3"/>
<rect x="36" y="25" width="10" height="8" rx="3" fill="#4AABE3"/>
<!-- mouth -->
<rect x="20" y="37" width="24" height="4" rx="2" fill="#4AABE3"/>
<!-- chin triangle / speech pointer -->
<polygon points="24,46 40,46 32,54" fill="#fff"/>
</svg>`;

var message = "Ask our Tufts Research Technology Guides AI Assistant";

var wrapper = document.createElement("div");
wrapper.id = "chatbot-bubble-wrap";

var link = document.createElement("a");
link.id = "chatbot-bubble";
link.href = "/hpc/chat.html";
link.setAttribute("aria-label", message);

var btn = document.createElement("span");
btn.id = "chatbot-bubble-btn";
btn.innerHTML = icon;

var label = document.createElement("span");
label.id = "chatbot-bubble-label";
label.textContent = message;

link.appendChild(label);
link.appendChild(btn);

// Mobile popup — shown on first tap instead of navigating directly
var popup = document.createElement("div");
popup.id = "chatbot-bubble-popup";
popup.setAttribute("hidden", "");

var popupText = document.createElement("p");
popupText.textContent = message;

var popupLink = document.createElement("a");
popupLink.id = "chatbot-popup-link";
popupLink.href = "/hpc/chat.html";
popupLink.textContent = "Open Chat";

popup.appendChild(popupText);
popup.appendChild(popupLink);

wrapper.appendChild(popup);
wrapper.appendChild(link);

var isTouchDevice = function () {
return window.matchMedia("(hover: none) and (pointer: coarse)").matches;
};

link.addEventListener("click", function (e) {
if (isTouchDevice()) {
e.preventDefault();
if (popup.hasAttribute("hidden")) {
popup.removeAttribute("hidden");
link.setAttribute("aria-expanded", "true");
popupLink.focus();
} else {
popup.setAttribute("hidden", "");
link.setAttribute("aria-expanded", "false");
}
}
});

document.addEventListener("click", function (e) {
if (!wrapper.contains(e.target)) {
popup.setAttribute("hidden", "");
link.setAttribute("aria-expanded", "false");
}
});

document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !popup.hasAttribute("hidden")) {
popup.setAttribute("hidden", "");
link.setAttribute("aria-expanded", "false");
link.focus();
}
});

var footer = document.querySelector("footer.bd-footer");
if (footer) {
document.body.insertBefore(wrapper, footer);
} else {
document.body.appendChild(wrapper);
}
});
})();
116 changes: 116 additions & 0 deletions source/_static/style/chatbot-bubble.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#chatbot-bubble-wrap {
position: fixed;
bottom: 1.75rem;
right: 1.75rem;
z-index: 9999;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0.5rem;
}

#chatbot-bubble {
display: flex;
align-items: center;
gap: 0.6rem;
text-decoration: none;
}

#chatbot-bubble-btn {
width: 56px;
height: 56px;
border-radius: 50%;
background: linear-gradient(135deg, #5ec4f0 0%, #4AABE3 100%);
box-shadow: 0 4px 14px rgba(74, 171, 227, 0.45);
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s ease, box-shadow 0.2s ease;
flex-shrink: 0;
}

#chatbot-bubble:hover #chatbot-bubble-btn,
#chatbot-bubble:focus-visible #chatbot-bubble-btn {
transform: scale(1.08);
box-shadow: 0 6px 20px rgba(74, 171, 227, 0.55);
}

#chatbot-bubble:focus-visible {
outline: none;
}

#chatbot-bubble:focus-visible #chatbot-bubble-btn {
outline: 3px solid #4AABE3;
outline-offset: 3px;
}

#chatbot-bubble-label {
background: #fff;
color: #333;
font-size: 0.85rem;
font-weight: 600;
padding: 0.75rem 1rem;
border-radius: 12px;
white-space: nowrap;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
opacity: 0;
transform: translateX(6px);
transition: opacity 0.2s ease, transform 0.2s ease;
pointer-events: none;
}

#chatbot-bubble:hover #chatbot-bubble-label,
#chatbot-bubble:focus-visible #chatbot-bubble-label {
opacity: 1;
transform: translateX(0);
}

/* Mobile popup */
#chatbot-bubble-popup {
background: #fff;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
padding: 1rem;
max-width: 220px;
text-align: center;
}

#chatbot-bubble-popup p {
margin: 0 0 0.75rem;
font-size: 0.85rem;
color: #333;
line-height: 1.4;
}

#chatbot-popup-link {
display: inline-block;
background: #4AABE3;
color: #fff;
padding: 0.4rem 1.2rem;
border-radius: 999px;
text-decoration: none;
font-weight: 600;
font-size: 0.85rem;
transition: background 0.2s ease;
}

#chatbot-popup-link:hover,
#chatbot-popup-link:focus-visible {
background: #3596cc;
outline: 2px solid #4AABE3;
outline-offset: 2px;
}

/* Hide hover label on touch devices — popup handles the interaction instead */
@media (hover: none) and (pointer: coarse) {
#chatbot-bubble-label {
display: none;
}
}

@media (prefers-reduced-motion: reduce) {
#chatbot-bubble-btn,
#chatbot-bubble-label {
transition: none;
}
}
3 changes: 2 additions & 1 deletion source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@
"style/navbar.css",
"style/sidebar.css",
"style/switcher.css",
"style/chatbot-bubble.css",
]
html_favicon = "_static/favicon.ico"
html_last_updated_fmt = ""
html_js_files = ["script/dynamic-nav-dropdown.js"]
html_js_files = ["script/dynamic-nav-dropdown.js", "script/chatbot-bubble.js"]
html_logo = "_static/jumbo.png"
html_static_path = ["_static"]
html_theme = "pydata_sphinx_theme"
Expand Down