diff --git a/shaarli-pro/css/custom_views.css b/shaarli-pro/css/custom_views.css index ccdf911..6f7efdc 100644 --- a/shaarli-pro/css/custom_views.css +++ b/shaarli-pro/css/custom_views.css @@ -774,11 +774,16 @@ body.view-notes .content-container { background: var(--background-secondary, #ffffff); border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 12px; - padding: 10px; - box-shadow: 0 10px 24px rgba(0, 0, 0, 0.18); + padding: 12px; + box-shadow: 0 14px 34px rgba(0, 0, 0, 0.22); z-index: 50; width: max-content; max-width: min(340px, calc(100vw - 32px)); + max-height: min(320px, calc(100vh - 140px)); + overflow: auto; + overscroll-behavior: contain; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); } [data-theme="dark"] .palette-popup { @@ -790,25 +795,84 @@ body.view-notes .content-container { display: block; } +.palette-section { + display: flex; + flex-direction: column; + gap: 10px; +} + +.palette-section-title { + font-size: 0.75rem; + line-height: 1; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + color: rgba(0, 0, 0, 0.62); +} + +[data-theme="dark"] .palette-section-title { + color: rgba(255, 255, 255, 0.72); +} + +.palette-divider { + height: 1px; + background: rgba(0, 0, 0, 0.10); + margin: 10px 0; +} + +[data-theme="dark"] .palette-divider { + background: rgba(255, 255, 255, 0.12); +} + .palette-row { display: flex; flex-wrap: wrap; gap: 8px; } +.palette-row-colors { + flex-wrap: nowrap; + overflow-x: hidden; + overflow-y: hidden; + padding-bottom: 4px; + scrollbar-width: thin; +} + +.palette-row-colors::-webkit-scrollbar { + height: 8px; +} + +.palette-row-colors::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.18); + border-radius: 10px; +} + +[data-theme="dark"] .palette-row-colors::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.18); +} + +.palette-row-backgrounds { + display: grid; + grid-template-columns: repeat(auto-fill, 28px); + gap: 10px; + justify-content: start; +} + .palette-row + .palette-row { margin-top: 10px; } .palette-btn { - width: 24px; - height: 24px; + width: 28px; + height: 28px; border-radius: 50%; border: 1px solid rgba(0, 0, 0, 0.18); cursor: pointer; padding: 0; background-position: center bottom; background-size: cover; + flex: 0 0 auto; + transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease; } [data-theme="dark"] .palette-btn { @@ -820,6 +884,20 @@ body.view-notes .content-container { outline-offset: 2px; } +.palette-btn:hover { + transform: translateY(-1px); + box-shadow: 0 6px 14px rgba(0, 0, 0, 0.18); +} + +[data-theme="dark"] .palette-btn:hover { + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.45); +} + +.palette-btn:focus-visible { + outline: 2px solid var(--primary-color, #2563eb); + outline-offset: 2px; +} + .palette-btn-bg-none { display: flex; align-items: center; @@ -835,6 +913,385 @@ body.view-notes .content-container { top: calc(100% + 8px); } +.bg-studio-panel { + position: fixed; + z-index: 1200; + width: min(420px, calc(100vw - 24px)); + border-radius: 20px; + background: rgba(255, 255, 255, 0.92); + border: 1px solid var(--border, #d1d5db); + box-shadow: 0 20px 50px rgba(15, 23, 42, 0.22); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + color: var(--text-main, #0f172a); + padding: 16px; +} + +[data-theme="dark"] .bg-studio-panel { + background: rgba(31, 37, 41, 0.92); + border-color: #2A3238; + box-shadow: 0 28px 70px rgba(0, 0, 0, 0.55); + color: rgba(255, 255, 255, 0.92); +} + +.bg-studio-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 12px; +} + +.bg-studio-title { + font-size: 18px; + font-weight: 600; + letter-spacing: 0.01em; +} + +.bg-studio-close { + width: 34px; + height: 34px; + border-radius: 12px; + border: 1px solid rgba(15, 23, 42, 0.16); + background: rgba(15, 23, 42, 0.05); + color: inherit; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.bg-studio-close:hover { + background: rgba(15, 23, 42, 0.10); +} + +[data-theme="dark"] .bg-studio-close { + border-color: rgba(255, 255, 255, 0.12); + background: rgba(255, 255, 255, 0.06); +} + +[data-theme="dark"] .bg-studio-close:hover { + background: rgba(255, 255, 255, 0.10); +} + +.bg-studio-gallery { + display: grid; + grid-auto-flow: column; + grid-template-rows: repeat(2, 102px); + grid-auto-columns: 84px; + gap: 12px; + overflow-x: auto; + overflow-y: hidden; + padding: 2px 2px 10px; + margin-bottom: 12px; + overscroll-behavior: contain; + scrollbar-width: thin; +} + +.bg-studio-gallery::-webkit-scrollbar { + height: 10px; +} + +.bg-studio-gallery::-webkit-scrollbar-thumb { + background: rgba(15, 23, 42, 0.20); + border-radius: 10px; +} + +[data-theme="dark"] .bg-studio-gallery::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.18); +} + +.bg-studio-thumb { + width: 84px; + height: 72px; + border-radius: 12px; + border: 1px solid rgba(15, 23, 42, 0.16); + background: rgba(15, 23, 42, 0.05); + background-position: center bottom; + background-size: cover; + cursor: pointer; + padding: 0; + position: relative; + transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease; + flex: 0 0 auto; + overflow: hidden; + isolation: isolate; +} + +.bg-studio-thumb:hover { + transform: translateY(-2px); + box-shadow: 0 12px 30px rgba(15, 23, 42, 0.28); +} + +.bg-studio-thumb.is-active { + border: 3px solid #3b82f6; + box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.22); +} + +.bg-studio-thumb:focus-visible { + outline: 3px solid rgba(59, 130, 246, 0.9); + outline-offset: 3px; +} + +.bg-studio-thumb-color, +.bg-studio-thumb-solid { + position: absolute; + inset: 0; + border-radius: 12px; +} + +.bg-studio-thumb-solid { + background: linear-gradient(135deg, rgba(255,255,255,0.15), rgba(255,255,255,0.02)); +} + +.bg-studio-tile { + display: grid; + grid-template-rows: 72px auto; + gap: 6px; + width: 84px; +} + +.bg-studio-thumb-label { + font-size: 0.72rem; + line-height: 1.1; + text-align: center; + color: var(--text-muted, rgba(15, 23, 42, 0.62)); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +[data-theme="dark"] .bg-studio-thumb-label { + color: rgba(255, 255, 255, 0.72); +} + +.bg-studio-rows { + display: grid; + grid-template-columns: 1fr; + gap: 10px; + margin-bottom: 12px; +} + +.bg-studio-row { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + align-items: center; + gap: 12px; +} + +.bg-studio-row-heading { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.bg-studio-row-label { + min-width: 108px; + font-size: 0.9rem; + font-weight: 600; + color: inherit; +} + +.bg-studio-swatches { + display: grid; + grid-template-columns: repeat(6, 26px); + grid-auto-rows: 26px; + gap: 8px; + min-width: 0; + width: 100%; + overflow: hidden; +} + +.bg-studio-swatch { + width: 26px; + height: 26px; + border-radius: 999px; + border: 1px solid rgba(15, 23, 42, 0.22); + cursor: pointer; + padding: 0; + transition: transform 0.12s ease, box-shadow 0.12s ease; + flex: 0 0 auto; +} + +.bg-studio-swatch:hover { + transform: scale(1.15); + box-shadow: 0 10px 20px rgba(0,0,0,0.35); +} + +.bg-studio-swatch.is-active { + border: 2px solid rgba(255, 255, 255, 0.95); + box-shadow: 0 8px 16px rgba(0,0,0,0.35); +} + +[data-theme="dark"] .bg-studio-swatch { + border-color: rgba(255, 255, 255, 0.28); +} + +.bg-studio-filters { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + width: 100%; + overflow-x: auto; + overflow-y: hidden; +} + +.bg-studio-filter-btn { + width: 30px; + height: 30px; + border-radius: 8px; + border: 1px solid rgba(15, 23, 42, 0.16); + background: rgba(15, 23, 42, 0.05); + color: inherit; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.bg-studio-filter-btn.is-active { + border-color: rgba(59, 130, 246, 0.9); + color: #60a5fa; +} + +[data-theme="dark"] .bg-studio-filter-btn { + border-color: rgba(255, 255, 255, 0.16); + background: rgba(255, 255, 255, 0.06); +} + +.bg-studio-reset { + height: 28px; + padding: 0 12px; + border-radius: 999px; + border: 1px solid rgba(15, 23, 42, 0.18); + background: linear-gradient(180deg, #ffffff, #f3f4f6); + color: #111827; + font-size: 0.82rem; + font-weight: 600; + letter-spacing: 0.01em; + cursor: pointer; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.16); + transition: background 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease; +} + +.bg-studio-reset:hover { + border-color: rgba(59, 130, 246, 0.5); + background: linear-gradient(180deg, #ffffff, #e9eefb); + box-shadow: 0 6px 14px rgba(59, 130, 246, 0.2); + transform: translateY(-1px); +} + +.bg-studio-reset:focus-visible { + outline: 2px solid rgba(59, 130, 246, 0.9); + outline-offset: 2px; +} + +[data-theme="dark"] .bg-studio-reset { + border-color: rgba(255, 255, 255, 0.2); + background: linear-gradient(180deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.08)); + color: rgba(255, 255, 255, 0.92); + box-shadow: none; +} + +[data-theme="dark"] .bg-studio-reset:hover { + border-color: rgba(96, 165, 250, 0.65); + background: linear-gradient(180deg, rgba(96, 165, 250, 0.26), rgba(96, 165, 250, 0.16)); + box-shadow: 0 8px 16px rgba(2, 6, 23, 0.45); +} + +.bg-studio-search { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 10px 12px; + border-radius: 14px; + border: 1px solid var(--border, rgba(15, 23, 42, 0.16)); + background: rgba(15, 23, 42, 0.05); + margin-bottom: 12px; + transition: border-color 0.16s ease, box-shadow 0.16s ease, background-color 0.16s ease; +} + +.bg-studio-search:focus-within { + border-color: rgba(59, 130, 246, 0.55); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.18); +} + +.bg-studio-search i { + color: var(--text-muted, rgba(15, 23, 42, 0.62)); +} + +.bg-studio-search-input { + flex: 1; + border: 0 !important; + background: transparent !important; + box-shadow: none !important; + border-radius: 0; + color: inherit; + outline: none; + font-size: 0.95rem; + -webkit-appearance: none; + appearance: none; + caret-color: currentColor; +} + +.bg-studio-search-input:focus, +.bg-studio-search-input:focus-visible { + outline: none; + box-shadow: none; +} + +.bg-studio-search-input::-webkit-search-cancel-button { + -webkit-appearance: none; + appearance: none; +} + +.bg-studio-search-input::placeholder { + color: var(--text-muted, rgba(15, 23, 42, 0.52)); +} + +[data-theme="dark"] .bg-studio-search { + border-color: rgba(255, 255, 255, 0.18); + background: rgba(255, 255, 255, 0.08); +} + +[data-theme="dark"] .bg-studio-search:focus-within { + border-color: rgba(96, 165, 250, 0.75); + background: rgba(15, 23, 42, 0.34); + box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.2); +} + +[data-theme="dark"] .bg-studio-search i { + color: rgba(255, 255, 255, 0.82); +} + +[data-theme="dark"] .bg-studio-search-input { + color: rgba(255, 255, 255, 0.95); +} + +[data-theme="dark"] .bg-studio-search-input::placeholder { + color: rgba(255, 255, 255, 0.62); +} + +.bg-studio-clear { + width: 28px; + height: 28px; + border-radius: 999px; + border: 1px solid rgba(15, 23, 42, 0.16); + background: rgba(15, 23, 42, 0.05); + color: inherit; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; +} + +[data-theme="dark"] .bg-studio-clear { + border-color: rgba(255, 255, 255, 0.16); + background: rgba(255, 255, 255, 0.06); +} + .spacer { flex: 1; } diff --git a/shaarli-pro/img/note-bg-dark/bg-dark-radio.jpg b/shaarli-pro/img/note-bg-dark/bg-dark-radio.jpg new file mode 100644 index 0000000..72894c3 Binary files /dev/null and b/shaarli-pro/img/note-bg-dark/bg-dark-radio.jpg differ diff --git a/shaarli-pro/img/note-bg-light/bg-light-radio.jpg b/shaarli-pro/img/note-bg-light/bg-light-radio.jpg new file mode 100644 index 0000000..ffa07dd Binary files /dev/null and b/shaarli-pro/img/note-bg-light/bg-light-radio.jpg differ diff --git a/shaarli-pro/js/backgrounds-manifest.js b/shaarli-pro/js/backgrounds-manifest.js index 55f4d0f..4d56b24 100644 --- a/shaarli-pro/js/backgrounds-manifest.js +++ b/shaarli-pro/js/backgrounds-manifest.js @@ -103,6 +103,14 @@ window.SHAARLI_NOTE_BACKGROUNDS_MANIFEST = [ dark: "bg-dark-ocean.jpg", }, }, + { + "key": "radio", + "label": "Radio", + "files": { + "light": "bg-light-radio.jpg", + "dark": "bg-dark-radio.jpg" + } + }, { key: "sports", label: "Sports", diff --git a/shaarli-pro/js/backgrounds-manifest.json b/shaarli-pro/js/backgrounds-manifest.json index 17536df..77a5755 100644 --- a/shaarli-pro/js/backgrounds-manifest.json +++ b/shaarli-pro/js/backgrounds-manifest.json @@ -103,6 +103,14 @@ "dark": "bg-dark-ocean.jpg" } }, + { + "key": "radio", + "label": "Radio", + "files": { + "light": "bg-light-radio.jpg", + "dark": "bg-dark-radio.jpg" + } + }, { "key": "sports", "label": "Sports", @@ -143,4 +151,5 @@ "dark": "bg-dark-voyage.jpg" } } + ] diff --git a/shaarli-pro/js/custom_views.js b/shaarli-pro/js/custom_views.js index 568b454..5df2183 100644 --- a/shaarli-pro/js/custom_views.js +++ b/shaarli-pro/js/custom_views.js @@ -173,6 +173,7 @@ const NOTE_BACKGROUND_MANIFEST_INLINE = [ { key: "legumes", label: "Légumes", files: { light: "bg-light-legumes.jpg", dark: "bg-dark-legumes.jpg" } }, { key: "montagnes", label: "Montagnes", files: { light: "bg-light-montagnes.jpg", dark: "bg-dark-montagnes.jpg" } }, { key: "ocean", label: "Océan", files: { light: "bg-light-ocean.jpg", dark: "bg-dark-ocean.jpg" } }, + { key: "radio", label: "Radio", files: { light: "bg-light-radio.jpg", dark: "bg-dark-radio.jpg" } }, { key: "sports", label: "Sports", files: { light: "bg-light-sports.jpg", dark: "bg-dark-sports.jpg" } }, { key: "vague1", label: "Vague 1", files: { light: "bg-light-vague1.jpg", dark: "bg-dark-vague1.jpg" } }, { key: "vague2", label: "Vague 2", files: { light: "bg-light-vague2.jpg", dark: "bg-dark-vague2.jpg" } }, @@ -499,6 +500,428 @@ function positionPalettePopup(popup) { } } +let backgroundStudioPanelInitialized = false; + +function ensureBackgroundStudioPanel() { + if (backgroundStudioPanelInitialized) return; + if (document.getElementById("shaarli-bg-studio")) { + backgroundStudioPanelInitialized = true; + return; + } + + const panel = document.createElement("div"); + panel.id = "shaarli-bg-studio"; + panel.className = "bg-studio-panel"; + panel.setAttribute("role", "dialog"); + panel.setAttribute("aria-modal", "false"); + panel.setAttribute("aria-hidden", "true"); + panel.style.display = "none"; + document.body.appendChild(panel); + + document.addEventListener("click", (e) => { + const p = document.getElementById("shaarli-bg-studio"); + if (!p || !p.classList.contains("open")) return; + if (e.target.closest("#shaarli-bg-studio")) return; + const anchor = p.__anchorEl; + if (anchor && (e.target === anchor || e.target.closest(`#${anchor.id}`))) return; + closeBackgroundStudioPanel(); + }); + + document.addEventListener("keydown", (e) => { + if (e.key !== "Escape") return; + const p = document.getElementById("shaarli-bg-studio"); + if (!p || !p.classList.contains("open")) return; + closeBackgroundStudioPanel(); + }); + + panel.addEventListener("click", (e) => { + const actionBtn = e.target.closest("button[data-bg-studio-action]"); + if (!actionBtn) return; + + const panelEl = document.getElementById("shaarli-bg-studio"); + if (!panelEl) return; + + const mode = panelEl.dataset.mode || "entity"; + const entityId = panelEl.dataset.entityId || ""; + const editUrl = panelEl.dataset.editUrl || ""; + + const action = actionBtn.dataset.bgStudioAction; + if (action === "close") { + closeBackgroundStudioPanel(); + return; + } + + if (action === "set-color") { + const key = actionBtn.dataset.colorKey || "default"; + if (mode === "modal") setModalNoteColor(key); + else setNoteColor(entityId, key, editUrl); + return; + } + + if (action === "set-background") { + const key = actionBtn.dataset.bgKey || "none"; + if (mode === "modal") setModalNoteBackground(key); + else setNoteBackground(entityId, key, editUrl); + return; + } + + if (action === "set-filter") { + panelEl.dataset.filter = actionBtn.dataset.filter || "all"; + renderBackgroundStudioPanel(panelEl); + return; + } + + if (action === "set-query") { + panelEl.dataset.query = ""; + renderBackgroundStudioPanel(panelEl); + const input = panelEl.querySelector(".bg-studio-search-input"); + if (input) input.focus(); + return; + } + + if (action === "set-defaults") { + if (mode === "modal") { + setModalNoteColor("default"); + setModalNoteBackground("none"); + } else { + setNoteColor(entityId, "default", editUrl); + setNoteBackground(entityId, "none", editUrl); + } + return; + } + }); + + panel.addEventListener("keydown", (e) => { + const targetBtn = e.target && e.target.closest ? e.target.closest("button") : null; + if (!targetBtn) return; + + if (e.key === "Enter" || e.key === " ") { + if (targetBtn.hasAttribute("data-bg-studio-action") || targetBtn.classList.contains("bg-studio-thumb") || targetBtn.classList.contains("bg-studio-swatch")) { + e.preventDefault(); + targetBtn.click(); + } + return; + } + + const moveFocus = (buttons, nextIndex) => { + if (!buttons || buttons.length === 0) return; + const idx = Math.max(0, Math.min(buttons.length - 1, nextIndex)); + const el = buttons[idx]; + if (el && typeof el.focus === "function") el.focus(); + }; + + if (targetBtn.classList.contains("bg-studio-thumb")) { + const buttons = Array.from(panel.querySelectorAll(".bg-studio-gallery .bg-studio-thumb")); + const currentIndex = buttons.indexOf(targetBtn); + if (currentIndex === -1) return; + + const colSize = 2; + if (e.key === "ArrowRight") { + e.preventDefault(); + moveFocus(buttons, currentIndex + colSize); + } else if (e.key === "ArrowLeft") { + e.preventDefault(); + moveFocus(buttons, currentIndex - colSize); + } else if (e.key === "ArrowDown") { + e.preventDefault(); + moveFocus(buttons, currentIndex + 1); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + moveFocus(buttons, currentIndex - 1); + } + return; + } + + if (targetBtn.classList.contains("bg-studio-swatch")) { + const buttons = Array.from(panel.querySelectorAll(".bg-studio-swatches .bg-studio-swatch")); + const currentIndex = buttons.indexOf(targetBtn); + if (currentIndex === -1) return; + + if (e.key === "ArrowRight") { + e.preventDefault(); + moveFocus(buttons, currentIndex + 1); + } else if (e.key === "ArrowLeft") { + e.preventDefault(); + moveFocus(buttons, currentIndex - 1); + } + return; + } + }); + + panel.addEventListener("input", (e) => { + const input = e.target && e.target.matches(".bg-studio-search-input") ? e.target : null; + if (!input) return; + + const panelEl = document.getElementById("shaarli-bg-studio"); + if (!panelEl) return; + panelEl.dataset.query = input.value || ""; + + window.clearTimeout(panelEl.__searchTimer); + panelEl.__searchTimer = window.setTimeout(() => { + renderBackgroundStudioPanel(panelEl); + }, 150); + }); + + backgroundStudioPanelInitialized = true; +} + +function closeBackgroundStudioPanel() { + const panel = document.getElementById("shaarli-bg-studio"); + if (!panel) return; + panel.classList.remove("open"); + panel.style.display = "none"; + panel.setAttribute("aria-hidden", "true"); + panel.__anchorEl = null; +} + +function openBackgroundStudioPanel({ + anchorEl, + mode, + entityId, + editUrl, + currentColor, + currentBackground, + title, +}) { + ensureBackgroundStudioPanel(); + const panel = document.getElementById("shaarli-bg-studio"); + if (!panel) return; + + panel.dataset.mode = mode || "entity"; + panel.dataset.entityId = entityId || ""; + panel.dataset.editUrl = editUrl || ""; + panel.dataset.color = currentColor || "default"; + panel.dataset.background = normalizeBackgroundKey(currentBackground || "") || "none"; + panel.dataset.filter = panel.dataset.filter || "all"; + panel.dataset.query = panel.dataset.query || ""; + panel.dataset.title = title || "Mes images & couleurs"; + panel.__anchorEl = anchorEl || null; + + renderBackgroundStudioPanel(panel); + + panel.style.display = "block"; + panel.classList.add("open"); + panel.setAttribute("aria-hidden", "false"); + + positionBackgroundStudioPanel(panel, anchorEl); + + window.requestAnimationFrame(() => { + const input = panel.querySelector(".bg-studio-search-input"); + if (input && typeof input.focus === "function") { + input.focus(); + input.select(); + } + }); +} + +function positionBackgroundStudioPanel(panel, anchorEl) { + if (!panel) return; + + const viewportPadding = 12; + const rect = anchorEl && anchorEl.getBoundingClientRect ? anchorEl.getBoundingClientRect() : null; + + panel.style.left = ""; + panel.style.top = ""; + panel.style.right = ""; + panel.style.bottom = ""; + + const panelRect = panel.getBoundingClientRect(); + const preferredTop = rect ? rect.top - panelRect.height - 10 : viewportPadding; + const preferredLeft = rect ? rect.left : viewportPadding; + + let top = preferredTop; + if (top < viewportPadding) { + top = rect ? rect.bottom + 10 : viewportPadding; + } + + let left = preferredLeft; + if (left + panelRect.width > window.innerWidth - viewportPadding) { + left = window.innerWidth - viewportPadding - panelRect.width; + } + if (left < viewportPadding) left = viewportPadding; + + if (top + panelRect.height > window.innerHeight - viewportPadding) { + top = window.innerHeight - viewportPadding - panelRect.height; + } + if (top < viewportPadding) top = viewportPadding; + + panel.style.left = `${Math.round(left)}px`; + panel.style.top = `${Math.round(top)}px`; +} + +function normalizeSearchText(value) { + return String(value || "") + .toLowerCase() + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .trim(); +} + +function categorizeBackgroundKey(key, label) { + const hay = normalizeSearchText(`${key || ""} ${label || ""}`); + + if (!key || key === "none") return "solid"; + if (hay.includes("degrade") || hay.includes("gradient")) return "gradient"; + if (hay.includes("grid") || hay.includes("quadrill") || hay.includes("lign") || hay.includes("feuille")) return "grid"; + if (hay.includes("damier") || hay.includes("checker") || hay.includes("texture")) return "texture"; + + return "image"; +} + +function getBackgroundStudioItems() { + const colors = NOTE_COLOR_OPTIONS.map((opt) => ({ + type: "color", + key: opt.key, + label: opt.label, + color: getThemeColorValue(opt), + keywords: [opt.key, opt.label], + })); + + const backgrounds = [ + { + type: "background", + category: "solid", + key: "none", + label: "Couleur unie", + keywords: ["none", "sans image", "solid", "uni"], + }, + ...getAvailableBackgroundOptionsForMode().map((bg) => ({ + type: "background", + category: categorizeBackgroundKey(bg.key, bg.label), + key: bg.key, + label: bg.label, + url: getNoteBackgroundUrl(bg.key), + keywords: [bg.key, bg.label, "image", "background"], + })), + ]; + + return { colors, backgrounds }; +} + +function renderBackgroundStudioPanel(panel) { + if (!panel) return; + + const activeEl = document.activeElement; + const wasSearchFocused = !!( + activeEl && + panel.contains(activeEl) && + activeEl.classList && + activeEl.classList.contains("bg-studio-search-input") + ); + const caretStart = wasSearchFocused && typeof activeEl.selectionStart === "number" ? activeEl.selectionStart : null; + const caretEnd = wasSearchFocused && typeof activeEl.selectionEnd === "number" ? activeEl.selectionEnd : null; + + const title = panel.dataset.title || "Mes images & couleurs"; + const color = panel.dataset.color || "default"; + const background = normalizeBackgroundKey(panel.dataset.background || "") || "none"; + const filter = panel.dataset.filter || "all"; + const query = normalizeSearchText(panel.dataset.query || ""); + + const { colors, backgrounds } = getBackgroundStudioItems(); + + const filterMatch = (item) => { + if (filter === "all") return true; + if (filter === "color") return item.type === "color"; + if (item.type !== "background") return false; + if (filter === "solid") return item.category === "solid"; + if (filter === "gradient") return item.category === "gradient"; + if (filter === "grid") return item.category === "grid"; + if (filter === "image") return item.category === "image"; + if (filter === "texture") return item.category === "texture"; + return true; + }; + + const queryMatch = (item) => { + if (!query) return true; + const hay = normalizeSearchText([item.label, ...(item.keywords || [])].join(" ")); + return hay.includes(query); + }; + + const galleryItems = backgrounds + .filter((b) => filterMatch(b) && queryMatch(b)) + .map((b) => ({ ...b, kind: "background" })); + + const galleryHtml = galleryItems + .map((item) => { + const isActive = background === item.key; + const common = `class="bg-studio-thumb ${isActive ? "is-active" : ""}" type="button"`; + const thumb = + item.key === "none" + ? `` + : ``; + + return `