Plugin Directory

Changeset 3431321


Ignore:
Timestamp:
01/02/2026 10:22:07 PM (7 weeks ago)
Author:
attorneyconnect
Message:

Update trunk for v1.0.24

Location:
attorneyconnect-ai/trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • attorneyconnect-ai/trunk/attorneyconnect-ai.php

    r3426512 r3431321  
    44 * Plugin URI:  https://attorneyconnect.ai/get-started/
    55 * Description: AttorneyConnect AI is the Most Advanced Conversational Website Receptionist Built for Small Law Firms.
    6  * Version: 1.0.23
     6 * Version: 1.0.24
    77 * Author: AttorneyConnect
    88 * Author URI: https://attorneyconnect.ai/
     
    275275
    276276    // Admin CSS via handle + inline rules (keeps Plugin Check happy)
    277     wp_register_style( 'attorneyconnect-ai-admin', false, array(), '1.0.22' );
     277    wp_register_style( 'attorneyconnect-ai-admin', false, array(), '%%VERSION%%' );
    278278    wp_enqueue_style( 'attorneyconnect-ai-admin' );
    279279    wp_add_inline_style(
     
    288288
    289289    // Admin JS (color validation/sync)
    290     wp_register_script( 'attorneyconnect-ai-admin', false, array(), '1.0.22', true );
     290    wp_register_script( 'attorneyconnect-ai-admin', false, array(), '%%VERSION%%', true );
    291291    wp_enqueue_script( 'attorneyconnect-ai-admin' );
    292292    wp_add_inline_script(
     
    323323        plugins_url( 'js/attorneyconnect-ai.js', __FILE__ ),
    324324        array( 'attorneyconnect-ai-rasa-webchat' ),
    325         '1.0.22',
     325        '%%VERSION%%',
    326326        true
    327327    );
     
    340340        plugins_url( 'css/attorneyconnect-ai.css', __FILE__ ),
    341341        array(),
    342         '1.0.22'
     342        '%%VERSION%%'
    343343    );
    344344
     
    349349    $payload = array(
    350350        'websiteCode'              => $website_code, // may be blank; backend will handle errors
    351         'socketUrl'                => 'ws://apis.attorneyconnect.ai',
     351        'socketUrl'                => '%%SOCKET_URL%%',
    352352        'featureColor'             => get_option( 'attorneyconnect_ai_feature_color', '#0073e6' ),
    353353        'widgetBarPlacement'       => get_option( 'attorneyconnect_ai_widget_bar_placement', 'vertical-right' ),
  • attorneyconnect-ai/trunk/css/attorneyconnect-ai.css

    r3364200 r3431321  
    186186     ============================================= */
    187187/* ===[H2] TALLER INPUT; KEEP SEND CIRCLE; CENTER ALIGN === */
    188 .rw-conversation-container .rw-sender {
     188.rw-conversation-container .rw-sender,
     189#chat-footer-container .rw-sender {
    189190  display: flex !important;
    190191  align-items: center !important;   /* vertical centering */
     192}
     193
     194#chat-footer-container .rw-sender {
     195  padding: 15px 5px;
     196  min-height: 30px;
    191197}
    192198
     
    199205  max-height: 220px !important;
    200206  overflow: auto !important;
     207}
     208
     209#chat-footer-container .rw-new-message {
     210  padding-left: 15px !important;
     211}
     212
     213@supports (-webkit-touch-callout: none) {
     214  .rw-new-message {
     215    font-size: 16px !important;
     216  }
    201217}
    202218
     
    216232   
    217233  /* Icon positioning */
    218   .rw-conversation-container .rw-send svg {
     234  .rw-conversation-container .rw-send svg,
     235  #chat-footer-container .rw-send svg {
    219236    margin-left: 5px;
    220237  }
    221238  .rw-conversation-container .rw-send .rw-send-icon,
    222   .rw-conversation-container .rw-send .rw-send-icon-ready {
     239  .rw-conversation-container .rw-send .rw-send-icon-ready,
     240  #chat-footer-container .rw-send .rw-send-icon,
     241  #chat-footer-container .rw-send .rw-send-icon-ready {
    223242    fill: var(--feature-color) !important;
    224243    stroke: var(--feature-color) !important;
    225244  }
    226245
    227   .rw-conversation-container .rw-sender {
     246  .rw-conversation-container .rw-sender,
     247  #chat-footer-container .rw-sender {
    228248    background-color: #ffffff !important;
    229249  }
     
    312332    z-index: 100000;
    313333  }
     334
     335  /* Call popup */
     336  #call-popup {
     337    position: fixed;
     338    background: #ffffff;
     339    border: 1px solid #e2e8f0;
     340    border-radius: 8px;
     341    padding: 10px 12px;
     342    box-shadow: 0 0 0 1px rgba(11,67,115,0.16), 0 12px 26px rgba(0,0,0,0.22);
     343    z-index: 10002;
     344    display: none;
     345    min-width: 150px;
     346    max-width: 220px;
     347    font-family: sans-serif;
     348  }
     349
     350  #call-popup.open {
     351    display: block;
     352  }
     353
     354  #call-popup .call-popup-row {
     355    position: relative;
     356    display: flex;
     357    align-items: center;
     358    margin-bottom: 8px;
     359  }
     360
     361  #call-popup .call-popup-icon {
     362    position: absolute;
     363    left: 0;
     364    top: 50%;
     365    transform: translateY(-50%);
     366    display: inline-flex;
     367    color: #000000;
     368  }
     369
     370  #call-popup .call-popup-icon svg {
     371    width: 18px;
     372    height: 18px;
     373    transform: rotate(90deg);
     374    transform-origin: center;
     375  }
     376
     377  #call-popup .call-popup-number {
     378    color: #3a3a3a;
     379    display: block;
     380    width: 100%;
     381    text-align: center;
     382    padding: 0 26px;
     383    font-size: 17px;
     384    font-weight: 600;
     385    text-decoration: none;
     386    white-space: nowrap;
     387  }
     388
     389  #call-popup .call-popup-number.is-disabled {
     390    color: #6b7280;
     391    pointer-events: none;
     392  }
     393
     394  #call-popup .call-popup-close {
     395    display: inline-flex;
     396    align-items: center;
     397    justify-content: center;
     398    width: 100%;
     399    background: var(--feature-color);
     400    border: 1px solid var(--feature-color);
     401    color: var(--text-color);
     402    border-radius: 6px;
     403    padding: 8px 12px;
     404    font-size: 14px;
     405    font-weight: 600;
     406    cursor: pointer;
     407  }
     408
     409  #call-popup .call-popup-close:hover {
     410    filter: brightness(0.98);
     411  }
    314412 
    315413  /* =============================================
     
    323421    padding: 0 0 10px;
    324422    background: #fff;
     423  }
     424
     425  #legal-disclaimer-text {
     426    line-height: 1.2;
     427  }
     428
     429  #end-chat-button {
     430    flex-shrink: 0;
     431    white-space: nowrap;
     432  }
     433
     434  /* Footer layout: End Chat + Powered By on one row, disclaimer below */
     435  #footer-row {
     436    display: grid !important;
     437    grid-template-columns: auto 1fr auto;
     438    grid-template-rows: auto auto;
     439    align-items: center !important;
     440    column-gap: 8px;
     441    row-gap: 4px;
     442  }
     443
     444  #footer-row #end-chat-button {
     445    grid-column: 1;
     446    grid-row: 1;
     447  }
     448
     449  #footer-row a {
     450    grid-column: 3;
     451    grid-row: 1;
     452    justify-self: end;
     453    text-decoration: none;
     454    font-size: 12px;
     455    white-space: nowrap;
     456  }
     457
     458  #footer-row #legal-disclaimer-text {
     459    grid-column: 1 / -1;
     460    grid-row: 2;
     461    width: 100% !important;
     462    max-width: 100% !important;
     463    margin-top: 2px;
     464    text-align: center;
     465    white-space: normal;
     466  }
     467
     468  #chat-footer-container {
     469    flex: 0 0 auto;
     470    background: #fff;
     471    padding-bottom: env(safe-area-inset-bottom, 0px);
     472    z-index: 4;
     473  }
     474
     475  #chat-footer-container.acai-vv-active {
     476    padding-bottom: 0;
    325477  }
    326478 
     
    784936@media (max-width: 768px) {
    785937  #rasaWebchatWrapper { padding-bottom: 0 !important; width: 100% !important; }
     938  .rw-send {
     939    width: 60px !important;
     940    height: 60px !important;
     941    min-width: 60px !important;
     942    min-height: 60px !important;
     943  }
     944  #footer-row #legal-disclaimer-text {
     945    font-size: 10px;
     946    padding: 0 12px 8px;
     947    line-height: 1.25;
     948  }
    786949}
    787950
     
    816979#widget-bar.horizontal {
    817980  margin-right: 5px !important; /* same spacing as vertical + floating button */
     981}
     982
     983/* Divider between logo and first button (vertical + horizontal) */
     984#widget-bar.vertical .widget-logo + .widget-button.first-visible {
     985  border-top: 1px solid #ccc !important;
     986}
     987#widget-bar.horizontal .widget-logo + .widget-button.first-visible {
     988  border-left: 1px solid #ccc !important;
    818989}
    819990
  • attorneyconnect-ai/trunk/js/attorneyconnect-ai.js

    r3426512 r3431321  
    44  let fallbackMode = false;
    55  let isFormView   = false;
     6  let callPopup = null;
     7  let callPhoneNumber = "";
    68
    79  // === CONNECTION CONSTANTS / STATE ===
     
    7274      } else {
    7375        window.businessEntity = data;
    74         var callBtn = document.getElementById("call-button");
    75         if (callBtn) {
    76           callBtn.onclick = function () {
    77             window.location.href = "tel:" + data.phone_num;
    78           };
    79         }
     76        setCallPhoneNumber(data.phone_num);
    8077        var chatHeader = document.getElementById("chat-header");
    8178        if (chatHeader) {
     
    9289let floatingBtn = null;
    9390
     91function isMobileViewport() {
     92  const vw = window.innerWidth || document.documentElement.clientWidth;
     93  return vw <= 768;
     94}
     95
    9496function updateFloatingChatButtonVisibility(widgetBarPlacement) {
    9597  if (!floatingBtn) return;
    9698
    97   const vw = window.innerWidth || document.documentElement.clientWidth;
    98   const mobile = vw <= 768;
     99  const mobile = isMobileViewport();
    99100
    100101  const wrapperOpen = document.getElementById("rasaWebchatWrapper")?.classList.contains("open");
     
    144145  document.body.appendChild(floatingBtn);
    145146  floatingBtn.addEventListener('click', () => {
     147    hideCallPopup();
    146148    fallbackMode = false;
    147149    isFormView  = false;
     
    154156  window.addEventListener('orientationchange', () => updateFloatingChatButtonVisibility(widgetBarPlacement));
    155157}
     158
     159  function ensureCallPopup() {
     160    if (callPopup) return callPopup;
     161    callPopup = document.createElement("div");
     162    callPopup.id = "call-popup";
     163    callPopup.setAttribute("aria-hidden", "true");
     164    callPopup.innerHTML = `
     165      <div class="call-popup-row">
     166        <span class="call-popup-icon" aria-hidden="true">
     167          <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 512 512">
     168            <path fill="currentColor"
     169              d="M493.4 24.6l-104-24c-11.4-2.6-23.3 3.3-27.7 13.7l-48 112
     170                 c-4.2 9.8-1.4 21.3 6.9 28.1l60.6 49.6C350.3 264.2 274.7 339.8
     171                 214 380.5l-49.6-60.6c-6.8-8.3-18.3-11.1-28.1-6.9l-112 48
     172                 c-10.4 4.4-16.3 16.3-13.7 27.7l24 104C36.9 503.3 46.9 512 58 512
     173                 C299.8 512 512 299.8 512 58c0-11.1-8.7-21.1-18.6-23.4z"/>
     174          </svg>
     175        </span>
     176        <a class="call-popup-number" href="#">Phone unavailable</a>
     177      </div>
     178      <button type="button" class="call-popup-close">Close</button>
     179    `;
     180    document.body.appendChild(callPopup);
     181
     182    const closeBtn = callPopup.querySelector(".call-popup-close");
     183    if (closeBtn) {
     184      closeBtn.addEventListener("click", function() {
     185        hideCallPopup();
     186      });
     187    }
     188
     189    applyCallPopupNumber();
     190    return callPopup;
     191  }
     192
     193  function getCallPhoneNumber() {
     194    const callBtnEl = document.getElementById("call-button");
     195    const datasetPhone = callBtnEl && callBtnEl.dataset ? callBtnEl.dataset.phoneNumber : "";
     196    return (datasetPhone || callPhoneNumber || "").trim();
     197  }
     198
     199  function triggerDirectCall() {
     200    const phone = getCallPhoneNumber();
     201    if (!phone) return false;
     202    hideCallPopup();
     203    window.location.href = "tel:" + phone;
     204    return true;
     205  }
     206
     207  function applyCallPopupNumber() {
     208    if (!callPopup) return;
     209    const numberEl = callPopup.querySelector(".call-popup-number");
     210    if (!numberEl) return;
     211    const phone = getCallPhoneNumber();
     212    if (phone) {
     213      numberEl.textContent = phone;
     214      numberEl.href = "tel:" + phone;
     215      numberEl.classList.remove("is-disabled");
     216      numberEl.setAttribute("aria-disabled", "false");
     217    } else {
     218      numberEl.textContent = "Phone unavailable";
     219      numberEl.href = "#";
     220      numberEl.classList.add("is-disabled");
     221      numberEl.setAttribute("aria-disabled", "true");
     222    }
     223  }
     224
     225  function setCallPhoneNumber(phone) {
     226    callPhoneNumber = (phone || "").trim();
     227    const callBtnEl = document.getElementById("call-button");
     228    if (callBtnEl) {
     229      callBtnEl.dataset.phoneNumber = callPhoneNumber;
     230    }
     231    applyCallPopupNumber();
     232    if (callPopup && callPopup.classList.contains("open")) {
     233      requestAnimationFrame(positionCallPopup);
     234    }
     235  }
     236
     237  function positionCallPopup() {
     238    if (!callPopup || !callPopup.classList.contains("open")) return;
     239    const callBtnEl = document.getElementById("call-button");
     240    if (!callBtnEl) return;
     241    const rect = callBtnEl.getBoundingClientRect();
     242    const popupRect = callPopup.getBoundingClientRect();
     243    const gap = 8;
     244    const vw = window.innerWidth || document.documentElement.clientWidth;
     245    const vh = window.innerHeight || document.documentElement.clientHeight;
     246
     247    const isVertical = widgetBarPlacement && widgetBarPlacement.indexOf("vertical") !== -1;
     248    if (isVertical) {
     249      let left = rect.left - popupRect.width - gap;
     250      let top = rect.top + rect.height / 2 - popupRect.height / 2;
     251      left = Math.max(8, left);
     252      top = Math.max(8, Math.min(top, vh - popupRect.height - 8));
     253      callPopup.style.left = left + "px";
     254      callPopup.style.top = top + "px";
     255      return;
     256    }
     257
     258    let top = rect.top - popupRect.height - gap;
     259    if (top < 8) {
     260      top = rect.bottom + gap;
     261    }
     262    let left = rect.left + rect.width / 2 - popupRect.width / 2;
     263    left = Math.max(8, Math.min(left, vw - popupRect.width - 8));
     264    callPopup.style.left = left + "px";
     265    callPopup.style.top = top + "px";
     266  }
     267
     268  function openCallPopup() {
     269    const popup = ensureCallPopup();
     270    applyCallPopupNumber();
     271    popup.classList.add("open");
     272    popup.setAttribute("aria-hidden", "false");
     273    const callBtnEl = document.getElementById("call-button");
     274    if (callBtnEl) {
     275      callBtnEl.setAttribute("aria-expanded", "true");
     276    }
     277    requestAnimationFrame(positionCallPopup);
     278  }
     279
     280  function hideCallPopup() {
     281    if (!callPopup) return;
     282    callPopup.classList.remove("open");
     283    callPopup.setAttribute("aria-hidden", "true");
     284    const callBtnEl = document.getElementById("call-button");
     285    if (callBtnEl) {
     286      callBtnEl.setAttribute("aria-expanded", "false");
     287    }
     288  }
     289
     290  function toggleCallPopup(forceOpen) {
     291    const popup = ensureCallPopup();
     292    const shouldOpen = typeof forceOpen === "boolean"
     293      ? forceOpen
     294      : !popup.classList.contains("open");
     295    if (shouldOpen) {
     296      openCallPopup();
     297    } else {
     298      hideCallPopup();
     299    }
     300  }
     301
     302  document.addEventListener("click", function(e) {
     303    if (!callPopup || !callPopup.classList.contains("open")) return;
     304    if (e.target.closest("#call-popup") || e.target.closest("#call-button")) return;
     305    hideCallPopup();
     306  });
     307
     308  window.addEventListener("resize", positionCallPopup);
     309  window.addEventListener("orientationchange", positionCallPopup);
    156310
    157311  // 1) CREATE / CONFIGURE WIDGET BAR
     
    262416  if (chatCfg.includeCallButton !== "1") {
    263417    widgetBar.querySelector("#call-button").style.display = "none";
     418    hideCallPopup();
    264419  }
    265420  if (chatCfg.includeEmailButton !== "1") {
     
    277432  if (emailBtn) emailBtn.style.order = "2";
    278433  if (chatBtn)  chatBtn.style.order  = "3";
     434  if (callBtn) {
     435    callBtn.setAttribute("aria-haspopup", "dialog");
     436    callBtn.setAttribute("aria-expanded", "false");
     437    callBtn.addEventListener("click", function(e) {
     438      e.preventDefault();
     439      if (isMobileViewport() && triggerDirectCall()) {
     440        return;
     441      }
     442      toggleCallPopup();
     443    });
     444  }
    279445
    280446  var allButtons = widgetBar.querySelectorAll(".widget-button");
     
    285451  function openChatOrForm() {
    286452    document.getElementById("engagement-bubble")?.classList.add("hidden");
     453    hideCallPopup();
    287454    togglePopup(true);
    288455    if (sessionStorage.getItem("messageSent") === "true") {
     
    338505 
    339506    // 3) show the footer/disclaimer
    340     document.getElementById("legal-disclaimer").style.display = "";
     507    const footerContainer = document.getElementById("chat-footer-container");
     508    if (footerContainer) {
     509      footerContainer.style.display = "";
     510      footerViewportManager.scheduleUpdate();
     511    } else {
     512      document.getElementById("legal-disclaimer").style.display = "";
     513    }
    341514 
    342515    // 4) let flexbox handle height; clear any old hardcoded values
     
    361534    formWrapper.style.display = "block";
    362535    document.getElementById("rasaWebchatPro").style.display = "none";
    363     document.getElementById("legal-disclaimer").style.display = "none";
     536    const footerContainer = document.getElementById("chat-footer-container");
     537    if (footerContainer) {
     538      footerContainer.style.display = "none";
     539    } else {
     540      document.getElementById("legal-disclaimer").style.display = "none";
     541    }
    364542 
    365543    // if already sent ✔️ show thank-you
     
    515693    }
    516694 
    517     const disclaimer = document.getElementById("legal-disclaimer");
    518     if (disclaimer) {
    519       disclaimer.style.flex = "0 0 auto";
    520       disclaimer.style.background = "#FFFFFF";
    521     }
    522   }
     695    const footerContainer = document.getElementById("chat-footer-container");
     696    if (footerContainer) {
     697      footerContainer.style.flex = "0 0 auto";
     698      footerContainer.style.background = "#FFFFFF";
     699    } else {
     700      const disclaimer = document.getElementById("legal-disclaimer");
     701      if (disclaimer) {
     702        disclaimer.style.flex = "0 0 auto";
     703        disclaimer.style.background = "#FFFFFF";
     704      }
     705    }
     706  }
     707
     708  const isIOSLike = /iP(ad|hone|od)/.test(navigator.userAgent) ||
     709    (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
     710
     711  const footerViewportManager = (() => {
     712    let rafId = null;
     713    let mutationObserver = null;
     714    let resizeObserver = null;
     715
     716    const getFooterContainer = () => document.getElementById("chat-footer-container");
     717
     718    const ensureFooterContainer = () => {
     719      if (!isIOSLike) return null;
     720      const wrapper = document.getElementById("rasaWebchatWrapper");
     721      if (!wrapper) return null;
     722
     723      let container = getFooterContainer();
     724      if (!container) {
     725        container = document.createElement("div");
     726        container.id = "chat-footer-container";
     727        wrapper.appendChild(container);
     728      }
     729      container.style.position = "absolute";
     730      container.style.left = "0";
     731      container.style.right = "0";
     732      container.style.bottom = "0";
     733
     734      const disclaimer = document.getElementById("legal-disclaimer");
     735      if (disclaimer && disclaimer.parentElement !== container) {
     736        container.appendChild(disclaimer);
     737      }
     738
     739      const senders = wrapper.querySelectorAll(".rw-sender");
     740      if (senders.length) {
     741        let primarySender = null;
     742        senders.forEach(sender => {
     743          if (!primarySender && sender.closest("#rasaWebchatPro")) {
     744            primarySender = sender;
     745          }
     746        });
     747        if (!primarySender) {
     748          primarySender = senders[senders.length - 1];
     749        }
     750        if (primarySender && primarySender.parentElement !== container) {
     751          container.insertBefore(primarySender, container.firstChild);
     752        }
     753        senders.forEach(sender => {
     754          if (sender !== primarySender) {
     755            sender.remove();
     756          }
     757        });
     758      }
     759
     760      return container;
     761    };
     762
     763    const computeBottomInset = () => {
     764      if (!window.visualViewport) return 0;
     765      const vv = window.visualViewport;
     766      const wrapper = document.getElementById("rasaWebchatWrapper");
     767      const wrapperHeight = wrapper ? wrapper.getBoundingClientRect().height : 0;
     768      const layoutHeight = Math.max(
     769        wrapperHeight,
     770        window.innerHeight || 0,
     771        document.documentElement.clientHeight || 0
     772      );
     773      return Math.max(0, Math.round(layoutHeight - (vv.height + vv.offsetTop)));
     774    };
     775
     776    const updateMessagesPadding = (container, bottomInset) => {
     777      const messages = document.querySelector("#rasaWebchatWrapper .rw-messages-container");
     778      if (!messages) return;
     779      if (!messages.dataset.acaiBasePadding) {
     780        const base = parseFloat(getComputedStyle(messages).paddingBottom) || 0;
     781        messages.dataset.acaiBasePadding = String(base);
     782      }
     783      const base = parseFloat(messages.dataset.acaiBasePadding) || 0;
     784      const footerHeight = container.getBoundingClientRect().height;
     785      messages.style.paddingBottom = Math.ceil(base + footerHeight + bottomInset) + "px";
     786    };
     787
     788    const updateFooterPosition = () => {
     789      if (!isIOSLike) return;
     790      const container = ensureFooterContainer();
     791      if (!container) return;
     792
     793      const bottomInset = computeBottomInset();
     794      if (window.visualViewport) {
     795        container.classList.add("acai-vv-active");
     796      } else {
     797        container.classList.remove("acai-vv-active");
     798      }
     799      container.style.transform = "";
     800      container.style.bottom = bottomInset ? `${bottomInset}px` : "0px";
     801      updateMessagesPadding(container, bottomInset);
     802    };
     803
     804    const scheduleUpdate = () => {
     805      if (rafId) return;
     806      rafId = requestAnimationFrame(() => {
     807        rafId = null;
     808        updateFooterPosition();
     809      });
     810    };
     811
     812    const bind = () => {
     813      if (!isIOSLike) return;
     814      ensureFooterContainer();
     815      scheduleUpdate();
     816
     817      window.addEventListener("orientationchange", scheduleUpdate, { passive: true });
     818      window.addEventListener("resize", scheduleUpdate, { passive: true });
     819      if (window.visualViewport) {
     820        window.visualViewport.addEventListener("resize", scheduleUpdate, { passive: true });
     821        window.visualViewport.addEventListener("scroll", scheduleUpdate, { passive: true });
     822      }
     823
     824      document.addEventListener("focusin", scheduleUpdate, true);
     825      document.addEventListener("focusout", scheduleUpdate, true);
     826      document.addEventListener("visibilitychange", scheduleUpdate, true);
     827      window.addEventListener("pageshow", scheduleUpdate, { passive: true });
     828
     829      const chatRoot = document.getElementById("rasaWebchatPro");
     830      if (chatRoot && !mutationObserver) {
     831        mutationObserver = new MutationObserver(() => scheduleUpdate());
     832        mutationObserver.observe(chatRoot, { childList: true, subtree: true });
     833      }
     834
     835      if ("ResizeObserver" in window && !resizeObserver) {
     836        resizeObserver = new ResizeObserver(() => scheduleUpdate());
     837        const container = ensureFooterContainer();
     838        if (container) resizeObserver.observe(container);
     839      }
     840    };
     841
     842    return { bind, scheduleUpdate, ensureFooterContainer };
     843  })();
    523844 
    524845 
     
    579900     <div id="rasaWebchatPro"          style="display:none; height:100%; background:#FFF;"></div>
    580901   `;
    581    if (!widgetLoaded) {
     902  if (!widgetLoaded) {
    582903    widgetLoaded = true;
    583904    loadWebchat(document.getElementById("rasaWebchatPro"));
     
    594915      <div id="footer-row" style="display:flex;justify-content:space-between;align-items:center;background:#FFFFFF;padding:5px;">
    595916        <button id="end-chat-button" style="padding:5px 10px;font-size:12px;cursor:pointer;background:${featureColor};color:${textColor};border:none;">End Chat</button>
     917        <div id="legal-disclaimer-text" style="margin:0;font-size:11px;text-align:center;color:#666;background:#FFFFFF;">
     918          This chat is provided for informational purposes only and does not constitute legal advice.
     919        </div>
    596920        ${chatCfg.showPoweredBy === '1'
    597921          ? `<a href="https://AttorneyConnect.ai" target="_blank" rel="noopener noreferrer" style="text-decoration:none;font-size:12px;">
     
    601925      </div>`;
    602926
    603     disclaimer.innerHTML = footer + `
    604       <div id="legal-disclaimer-text" style="margin:0;font-size:11px;text-align:center;color:#666;background:#FFFFFF;">
    605         This chat is provided for informational purposes only and does not constitute legal advice.
    606       </div>`;
     927    disclaimer.innerHTML = footer;
    607928
    608929    document.getElementById("rasaWebchatWrapper").appendChild(disclaimer);
    609930    document.getElementById("end-chat-button").addEventListener("click", showEndChatAlert);
    610931  }
     932  footerViewportManager.bind();
    611933
    612934  applyLayoutFixes();
     
    6811003    const wrap = document.getElementById("rasaWebchatWrapper");
    6821004    if (wrap) {
     1005      hideCallPopup();
    6831006      fallbackMode = false;
    6841007      isFormView   = false;
Note: See TracChangeset for help on using the changeset viewer.