diff --git a/hlx_statics/blocks/ai-assistant/ai-assistant.css b/hlx_statics/blocks/ai-assistant/ai-assistant.css index 8820c3e3..70e41d27 100644 --- a/hlx_statics/blocks/ai-assistant/ai-assistant.css +++ b/hlx_statics/blocks/ai-assistant/ai-assistant.css @@ -39,6 +39,7 @@ */ .ai-assistant-panel .chat-button { --background-color: #ffffff; + --transition-delay: calc(var(--opening-transition-duration) * 0.9); position: relative; width: var(--button-size); @@ -54,9 +55,11 @@ var(--ai-border-linear-gradient) border-box; align-self: flex-end; pointer-events: auto; + transform: scale(1); visibility: visible; - transition: visibility 0s linear - calc(var(--opening-transition-duration) * 0.9); + transition: + visibility 0s linear var(--transition-delay), + transform 0.15s cubic-bezier(0, 0, 0.4, 1) var(--transition-delay); &:hover { --background-color: #f0f0f0; @@ -65,9 +68,36 @@ &.hidden { visibility: hidden; transition: visibility 0s linear; + transform: scale(0.9); } } +.ai-assistant-panel .chat-button .chat-button-badge { + position: absolute; + top: -10px; + right: -10px; + background: var(--ai-border-linear-gradient); + color: #fff; + font-size: 9px; + font-weight: 800; + letter-spacing: 0.3px; + padding: 2px 6px; + border-radius: 8px; + border: 2px solid #fff; + pointer-events: none; + line-height: 1.4; + opacity: 1; + transform: scale(1); + transition: + opacity 0.15s cubic-bezier(0, 0, 0.4, 1), + transform 0.15s cubic-bezier(0, 0, 0.4, 1); +} + +.ai-assistant-panel .chat-button .chat-button-badge.hidden { + opacity: 0; + transform: scale(0.6); +} + /* * MARK: Chat Window */ diff --git a/hlx_statics/blocks/ai-assistant/ai-assistant_chat-controller.js b/hlx_statics/blocks/ai-assistant/ai-assistant_chat-controller.js index 91d42651..99b39518 100644 --- a/hlx_statics/blocks/ai-assistant/ai-assistant_chat-controller.js +++ b/hlx_statics/blocks/ai-assistant/ai-assistant_chat-controller.js @@ -81,6 +81,7 @@ export const openChatWindow = () => { ELEMENTS.CHAT_BUTTON.ariaLabel = CHAT_BUTTON_LABEL_MINIMIZE; ELEMENTS.CHAT_WINDOW.classList.add("show"); ELEMENTS.CHAT_BUTTON.classList.add("hidden"); + ELEMENTS.CHAT_BUTTON_BADGE?.classList.add("hidden"); // Initial messages if (chatHistory.isEmpty()) { @@ -96,6 +97,44 @@ export const minimizeChatWindow = () => { ELEMENTS.CHAT_BUTTON.ariaLabel = CHAT_BUTTON_LABEL_OPEN; ELEMENTS.CHAT_BUTTON?.classList.remove("hidden"); ELEMENTS.CHAT_WINDOW?.classList.remove("show"); + + restoreBadgeAfterClose(); +}; + +/** + * Restores the beta badge after the chat window's collapse (transform) transition + * completes. + */ +const restoreBadgeAfterClose = () => { + const chatWindow = ELEMENTS.CHAT_WINDOW; + if (!chatWindow) return; + + // With reduced motion the window collapses instantly (no transform transition), + // so `transitionend` may never fire — restore the badge right away instead. + const prefersReducedMotion = window.matchMedia?.( + "(prefers-reduced-motion: reduce)", + )?.matches; + if (prefersReducedMotion) { + if (!chatWindow.classList.contains("show")) { + ELEMENTS.CHAT_BUTTON_BADGE?.classList.remove("hidden"); + } + return; + } + + /** @param {TransitionEvent} event */ + const onTransitionEnd = (event) => { + // The window animates both `transform` and `visibility`; only react to the + // transform transition, which is the visible collapse. + if (event.target !== chatWindow || event.propertyName !== "transform") { + return; + } + chatWindow.removeEventListener("transitionend", onTransitionEnd); + if (!chatWindow.classList.contains("show")) { + ELEMENTS.CHAT_BUTTON_BADGE?.classList.remove("hidden"); + } + }; + + chatWindow.addEventListener("transitionend", onTransitionEnd); }; export const clearConversation = () => { @@ -420,4 +459,4 @@ export const restoreChatHistory = async () => { } else { hideSuggestedQuestions(); } -}; \ No newline at end of file +}; diff --git a/hlx_statics/blocks/ai-assistant/ai-assistant_chat-ui.js b/hlx_statics/blocks/ai-assistant/ai-assistant_chat-ui.js index 9ca2b7e8..853daeef 100644 --- a/hlx_statics/blocks/ai-assistant/ai-assistant_chat-ui.js +++ b/hlx_statics/blocks/ai-assistant/ai-assistant_chat-ui.js @@ -158,7 +158,16 @@ export const createChatButton = () => { "daa-ll": "DevsiteAI Assistant:Open", }); chatButton.innerHTML = ``; + + const badge = createTag("span", { + class: "chat-button-badge", + "aria-hidden": "true", + }); + badge.textContent = "BETA"; + chatButton.appendChild(badge); + ELEMENTS.CHAT_BUTTON = chatButton; + ELEMENTS.CHAT_BUTTON_BADGE = badge; return chatButton; }; diff --git a/hlx_statics/blocks/ai-assistant/ai-assistant_constants.js b/hlx_statics/blocks/ai-assistant/ai-assistant_constants.js index fb6090b1..efb8bf67 100644 --- a/hlx_statics/blocks/ai-assistant/ai-assistant_constants.js +++ b/hlx_statics/blocks/ai-assistant/ai-assistant_constants.js @@ -10,6 +10,7 @@ export const CHAT_WINDOW_LABEL_ID = "ai-assistant-label"; */ export const ELEMENTS = { CHAT_BUTTON: null, + CHAT_BUTTON_BADGE: null, CHAT_WINDOW_CLOSE_BUTTON: null, CHAT_WINDOW_CLEAR_BUTTON: null, CHAT_SEND_BUTTON: null, diff --git a/hlx_statics/scripts/lib-helix.js b/hlx_statics/scripts/lib-helix.js index 4ed9f896..d78ece59 100644 --- a/hlx_statics/scripts/lib-helix.js +++ b/hlx_statics/scripts/lib-helix.js @@ -11,6 +11,7 @@ */ import { loadFragment } from '../blocks/fragment/fragment.js'; +import { isLocalHostEnvironment, isStageEnvironment } from './lib-adobeio.js'; /** * log RUM if part of the sample. @@ -118,7 +119,7 @@ export const IS_DEV_DOCS = Boolean(getMetadata('githubblobpath')); if (localStorage.getItem('ai-assistant-enabled') !== 'false') { localStorage.setItem('ai-assistant-enabled', 'true'); } -export const IS_AI_ASSISTANT_ENABLED = localStorage.getItem('ai-assistant-enabled') === 'true'; +export const IS_AI_ASSISTANT_ENABLED = localStorage.getItem('ai-assistant-enabled') === 'true' && (isStageEnvironment(window.location.host, true) || isLocalHostEnvironment(window.location.host)); window.ENABLE_AI_ASSISTANT = () => { localStorage.setItem('ai-assistant-enabled', 'true'); window.location.reload(); diff --git a/hlx_statics/scripts/scripts.js b/hlx_statics/scripts/scripts.js index be1bf67e..11de7d00 100644 --- a/hlx_statics/scripts/scripts.js +++ b/hlx_statics/scripts/scripts.js @@ -801,7 +801,7 @@ async function loadLazy(doc) { } } - if (IS_AI_ASSISTANT_ENABLED && isStageEnvironment(window.location.host, true)) { + if (IS_AI_ASSISTANT_ENABLED) { buildAiAssistant(main); loadAiAssistant(doc.querySelector('.ai-assistant-wrapper')); }