From e9c363639f60c7decd6b4a95c962711103eed4f6 Mon Sep 17 00:00:00 2001 From: rajchales-monito Date: Thu, 30 Jul 2026 17:05:02 +0200 Subject: [PATCH] Add widget launcher preview --- public/admin.css | 184 ++++++++++++++++++++++++++++++++++++++++++++++ public/admin.html | 9 +++ public/admin.js | 34 +++++++++ 3 files changed, 227 insertions(+) diff --git a/public/admin.css b/public/admin.css index e9f6476..1dae6e1 100644 --- a/public/admin.css +++ b/public/admin.css @@ -231,6 +231,190 @@ label { display: grid; gap: 6px; color: var(--muted); font-size: 13px; } width: auto; margin: 0; } +.launcher-preview-settings { + grid-template-columns: 1fr; +} +.launcher-preview-stage { + min-height: 112px; + display: grid; + place-items: center; + border: 1px solid var(--line); + border-radius: 8px; + background: + linear-gradient(90deg, rgb(15 143 111 / 5%) 1px, transparent 1px), + linear-gradient(rgb(15 143 111 / 5%) 1px, transparent 1px), + #ffffff; + background-size: 18px 18px; +} +.launcher-preview { + --preview-brand: var(--brand); + --preview-text: #ffffff; + position: relative; + width: 58px; + height: 58px; + display: grid; + place-items: center; + border: 0; + border-radius: 50%; + background: var(--preview-brand); + color: var(--preview-text); + box-shadow: 0 12px 30px rgb(0 0 0 / 22%); + cursor: default; + overflow: visible; +} +.launcher-preview.effect-breathe { + animation: launcherPreviewBreathe 2.8s ease-in-out infinite; +} +.launcher-preview.effect-ring::before { + position: absolute; + inset: -5px; + border: 2px solid var(--preview-brand); + border-radius: 50%; + opacity: .62; + content: ""; + animation: launcherPreviewRing 2.2s ease-out infinite; +} +.launcher-preview-label { + position: absolute; + right: 68px; + top: 50%; + max-width: 150px; + padding: 7px 10px; + border-radius: 999px; + background: var(--ink); + color: #ffffff; + font-size: 12px; + white-space: nowrap; + box-shadow: 0 10px 24px rgb(0 0 0 / 18%); + opacity: 0; + transform: translateY(-50%) translateX(8px); + transition: opacity .18s ease, transform .18s ease; +} +.launcher-preview.show-label .launcher-preview-label { + opacity: 1; + transform: translateY(-50%) translateX(0); +} +.launcher-preview-icon, +.launcher-preview-icon::before, +.launcher-preview-icon::after { + display: block; + content: ""; +} +.launcher-preview-icon { + position: relative; + margin: 0 auto; + color: var(--preview-text); +} +.preview-shape-circle .launcher-preview-icon, +.preview-shape-circle .launcher-preview-icon::before, +.preview-shape-circle .launcher-preview-icon::after { + height: 3px; + border-radius: 4px; + background: var(--preview-text); +} +.preview-shape-circle .launcher-preview-icon { + width: 26px; +} +.preview-shape-circle .launcher-preview-icon::before { + transform: translateY(-8px); +} +.preview-shape-circle .launcher-preview-icon::after { + width: 18px; + transform: translateY(5px); +} +.preview-shape-bubble .launcher-preview-icon { + width: 30px; + height: 23px; + border: 3px solid var(--preview-text); + border-radius: 13px; + background: transparent; +} +.preview-shape-bubble .launcher-preview-icon::before { + position: absolute; + left: 5px; + top: 7px; + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--preview-text); + box-shadow: 8px 0 0 var(--preview-text), 16px 0 0 var(--preview-text); +} +.preview-shape-bubble .launcher-preview-icon::after { + position: absolute; + right: 2px; + bottom: -8px; + width: 10px; + height: 10px; + border-left: 3px solid var(--preview-text); + border-bottom: 3px solid var(--preview-text); + border-bottom-left-radius: 8px; + background: transparent; + transform: rotate(-18deg); +} +.preview-shape-messenger .launcher-preview-icon { + width: 32px; + height: 32px; + border-radius: 50%; + background: var(--preview-text); +} +.preview-shape-messenger .launcher-preview-icon::before { + position: absolute; + left: 8px; + top: 8px; + width: 16px; + height: 16px; + background: var(--preview-brand); + clip-path: polygon(0 56%, 42% 56%, 58% 22%, 100% 22%, 58% 78%, 42% 78%); +} +.preview-shape-messenger .launcher-preview-icon::after { + position: absolute; + right: 5px; + bottom: 1px; + width: 8px; + height: 8px; + border-radius: 0 0 8px 0; + background: var(--preview-text); + transform: rotate(25deg); +} +.preview-shape-whatsapp .launcher-preview-icon { + width: 32px; + height: 32px; + border: 3px solid var(--preview-text); + border-radius: 50%; + background: transparent; +} +.preview-shape-whatsapp .launcher-preview-icon::before { + position: absolute; + left: 8px; + top: 7px; + width: 12px; + height: 15px; + border: 3px solid var(--preview-text); + border-left-color: transparent; + border-top-color: transparent; + border-radius: 0 0 12px 0; + transform: rotate(-35deg); +} +.preview-shape-whatsapp .launcher-preview-icon::after { + position: absolute; + left: 1px; + bottom: -4px; + width: 10px; + height: 10px; + border-left: 3px solid var(--preview-text); + border-bottom: 3px solid var(--preview-text); + background: transparent; + transform: rotate(12deg); +} +@keyframes launcherPreviewBreathe { + 0%, 100% { transform: scale(1); box-shadow: 0 12px 30px rgb(0 0 0 / 22%); } + 50% { transform: scale(1.035); box-shadow: 0 16px 36px rgb(0 0 0 / 20%), 0 0 0 8px rgb(15 143 111 / 10%); } +} +@keyframes launcherPreviewRing { + 0% { transform: scale(.92); opacity: .58; } + 70% { transform: scale(1.22); opacity: 0; } + 100% { transform: scale(.92); opacity: 0; } +} .widget-language-settings { grid-template-columns: 112px minmax(230px, 1fr); } diff --git a/public/admin.html b/public/admin.html index 424ded5..c03e9a6 100644 --- a/public/admin.html +++ b/public/admin.html @@ -174,6 +174,15 @@ +
+ Nahled tlacitka +
+ +
+
Efekt pri nove zprave diff --git a/public/admin.js b/public/admin.js index 9c4b012..f723ea2 100644 --- a/public/admin.js +++ b/public/admin.js @@ -130,14 +130,27 @@ document.querySelectorAll("[data-settings-tab]").forEach((button) => { }); $("#settingsForm").brand.addEventListener("input", (event) => { $("#settingsForm").brandHex.value = normalizeHexColor(event.target.value) || event.target.value; + renderLauncherPreview(); }); $("#settingsForm").brandHex.addEventListener("input", (event) => { const color = normalizeHexColor(event.target.value); if (color) $("#settingsForm").brand.value = color; + renderLauncherPreview(); }); $("#settingsForm").defaultLanguage.addEventListener("change", () => { updateDefaultLanguagePicker(); renderWidgetCopyFields($("#settingsForm").defaultLanguage.value); + renderLauncherPreview(); +}); +$("#settingsForm").addEventListener("input", (event) => { + if (event.target.matches("[name='launcherShape'], [name='launcherBreathe'], [name='launcherRing'], [name='launcherHoverLabel'], [name='copyOnlineLabel']")) { + renderLauncherPreview(); + } +}); +$("#settingsForm").addEventListener("change", (event) => { + if (event.target.matches("[name='launcherShape'], [name='launcherBreathe'], [name='launcherRing'], [name='launcherHoverLabel']")) { + renderLauncherPreview(); + } }); $("#defaultLanguagePicker").addEventListener("click", (event) => { event.stopPropagation(); @@ -536,6 +549,7 @@ function renderSite() { renderModelOptions(s.translations?.model || "gpt-5-mini"); renderPresenceSettings(s.adminPresence || {}); renderTelegramSettings(s.telegram || {}); + renderLauncherPreview(); } function renderWidgetLanguageOptions(form, settings) { @@ -625,6 +639,26 @@ function renderWidgetCopyFields(language) { form.copyInfoText.value = copy.infoText || ""; } +function renderLauncherPreview() { + const preview = $("#launcherPreview"); + const form = $("#settingsForm"); + if (!preview || !form) return; + const shape = ["circle", "bubble", "messenger", "whatsapp"].includes(form.launcherShape.value) + ? form.launcherShape.value + : "bubble"; + const brand = normalizeHexColor(form.brandHex.value) || normalizeHexColor(form.brand.value) || "#0f8f6f"; + preview.style.setProperty("--preview-brand", brand); + preview.className = [ + "launcher-preview", + `preview-shape-${shape}`, + form.launcherBreathe.checked ? "effect-breathe" : "", + form.launcherRing.checked ? "effect-ring" : "", + form.launcherHoverLabel.checked ? "show-label" : "" + ].filter(Boolean).join(" "); + const label = preview.querySelector(".launcher-preview-label"); + if (label) label.textContent = form.copyOnlineLabel.value || "Napiste nam"; +} + function widgetCopyForAdmin(settings, language) { const lang = language || "cs"; const copy = settings.widgetCopy?.[lang] || {};