From b104d9fab8445a2ea135c9eb23d8f547a1048f3f Mon Sep 17 00:00:00 2001 From: Inferencebot Date: Thu, 1 Oct 2026 15:14:34 -0600 Subject: [PATCH] Chat theme: pure-CSS dots layer (no JS injection point needed); serve as coop.css with @import watchdog --- chat-theme/openwebui-custom.css | 58 ++++++++++++++++++++++++++++----- chat-theme/openwebui-dots.js | 50 ++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 chat-theme/openwebui-dots.js diff --git a/chat-theme/openwebui-custom.css b/chat-theme/openwebui-custom.css index fed997f..ec620d2 100644 --- a/chat-theme/openwebui-custom.css +++ b/chat-theme/openwebui-custom.css @@ -133,11 +133,53 @@ html.dark ::selection { background: #3e5a3d; } html:not(.dark) a { color: #3f6e3e; } html.dark a { color: #8fc98f; } -/* ---------- Background dots (homepage motif echo) ---------- */ -/* Fixed layer of light green + bronze dots, varied sizes, gentle parallax on - scroll. Rendered behind all app content (z-index 0). */ -#coop-dots-layer { position: fixed; inset: 0; z-index: 0; pointer-events: none; } -#coop-dots-layer circle { opacity: 0.28; } -#coop-dots-layer circle.big { opacity: 0.22; } -html.dark #coop-dots-layer circle { opacity: 0.16; } -html.dark #coop-dots-layer circle.big { opacity: 0.13; } +/* ---------- Background dots (homepage motif echo, pure CSS) ---------- */ +body::before { + content: ""; + position: fixed; + inset: 0; + z-index: 0; + pointer-events: none; + background-image: + radial-gradient(circle 16px at 7% 58%, #b7d8c2 0 100%, transparent 100%), + radial-gradient(circle 20px at 28% 52%, #bfdccd 0 100%, transparent 100%), + radial-gradient(circle 24px at 55% 30%, #bfdccd 0 100%, transparent 100%), + radial-gradient(circle 18px at 68% 42%, #b7d8c2 0 100%, transparent 100%), + radial-gradient(circle 22px at 86% 38%, #b7d8c2 0 100%, transparent 100%), + radial-gradient(circle 17px at 81% 72%, #bfdccd 0 100%, transparent 100%), + radial-gradient(circle 16px at 9% 88%, #b7d8c2 0 100%, transparent 100%), + radial-gradient(circle 18px at 70% 80%, #bfdccd 0 100%, transparent 100%), + radial-gradient(circle 18px at 66% 30%, #d9c3a8 0 100%, transparent 100%), + radial-gradient(circle 5px at 4% 12%, #a8cdb4 0 100%, transparent 100%), + radial-gradient(circle 4px at 12% 34%, #b5d4c0 0 100%, transparent 100%), + radial-gradient(circle 5px at 16% 78%, #a8cdb4 0 100%, transparent 100%), + radial-gradient(circle 9px at 24% 6%, #b7d8c2 0 100%, transparent 100%), + radial-gradient(circle 4px at 31% 22%, #b5d4c0 0 100%, transparent 100%), + radial-gradient(circle 5px at 38% 70%, #a8cdb4 0 100%, transparent 100%), + radial-gradient(circle 6px at 47% 10%, #b7d8c2 0 100%, transparent 100%), + radial-gradient(circle 4px at 52% 55%, #a8cdb4 0 100%, transparent 100%), + radial-gradient(circle 5px at 63% 14%, #b5d4c0 0 100%, transparent 100%), + radial-gradient(circle 6px at 61% 66%, #bfdccd 0 100%, transparent 100%), + radial-gradient(circle 4px at 72% 58%, #b5d4c0 0 100%, transparent 100%), + radial-gradient(circle 8px at 78% 8%, #a8cdb4 0 100%, transparent 100%), + radial-gradient(circle 4px at 83% 26%, #b5d4c0 0 100%, transparent 100%), + radial-gradient(circle 6px at 76% 48%, #bfdccd 0 100%, transparent 100%), + radial-gradient(circle 5px at 93% 16%, #a8cdb4 0 100%, transparent 100%), + radial-gradient(circle 4px at 90% 60%, #b5d4c0 0 100%, transparent 100%), + radial-gradient(circle 4px at 95% 46%, #b5d4c0 0 100%, transparent 100%), + radial-gradient(circle 5px at 89% 86%, #a8cdb4 0 100%, transparent 100%), + radial-gradient(circle 4px at 40% 88%, #b5d4c0 0 100%, transparent 100%), + radial-gradient(circle 5px at 57% 84%, #a8cdb4 0 100%, transparent 100%), + radial-gradient(circle 5px at 20% 44%, #d9c3a8 0 100%, transparent 100%), + radial-gradient(circle 4px at 44% 38%, #e0cbb2 0 100%, transparent 100%), + radial-gradient(circle 5px at 87% 70%, #e0cbb2 0 100%, transparent 100%), + radial-gradient(circle 4px at 33% 76%, #d9c3a8 0 100%, transparent 100%), + radial-gradient(circle 5px at 58% 72%, #e0cbb2 0 100%, transparent 100%), + radial-gradient(circle 6px at 97% 78%, #d9c3a8 0 100%, transparent 100%), + radial-gradient(circle 4px at 3% 72%, #e0cbb2 0 100%, transparent 100%); + opacity: 0.5; + z-index: 0; +} +html.dark body::before { opacity: 0.25; } +/* Keep app content above the dots layer */ +#app, .app, main, .sidebar, nav, [class*="content"] { position: relative; z-index: 1; } diff --git a/chat-theme/openwebui-dots.js b/chat-theme/openwebui-dots.js new file mode 100644 index 0000000..a54d304 --- /dev/null +++ b/chat-theme/openwebui-dots.js @@ -0,0 +1,50 @@ +// Inject the dots SVG layer into the Open WebUI DOM (chat theme companion script). +// Served as /static/coop-dots.js; kept minimal — no dependencies. +// With gentle parallax (12% scroll speed) matching the homepage. +(function () { + var NS = 'http://www.w3.org/2000/svg'; + var svg = document.createElementNS(NS, 'svg'); + svg.setAttribute('class', 'coop-dots'); + svg.setAttribute('id', 'coop-dots-layer'); + svg.setAttribute('aria-hidden', 'true'); + var dots = [ + // [cx%, cy%, r, fill, big] + [4, 12, 5, '#a8cdb4'], [12, 34, 4, '#b5d4c0'], [7, 58, 16, '#b7d8c2', 1], + [16, 78, 5, '#a8cdb4'], [24, 6, 9, '#b7d8c2'], [31, 22, 4, '#b5d4c0'], + [28, 52, 20, '#bfdccd', 1], [38, 70, 5, '#a8cdb4'], [47, 10, 6, '#b7d8c2'], + [55, 30, 24, '#bfdccd', 1], [52, 55, 4, '#a8cdb4'], [63, 14, 5, '#b5d4c0'], + [68, 42, 18, '#b7d8c2', 1], [61, 66, 6, '#bfdccd'], [72, 58, 4, '#b5d4c0'], + [78, 8, 8, '#a8cdb4'], [83, 26, 4, '#b5d4c0'], [76, 48, 6, '#bfdccd'], + [86, 38, 22, '#b7d8c2', 1], [93, 16, 5, '#a8cdb4'], [90, 60, 4, '#b5d4c0'], + [81, 72, 17, '#bfdccd', 1], [95, 46, 4, '#b5d4c0'], [89, 86, 5, '#a8cdb4'], + [9, 88, 16, '#b7d8c2', 1], [40, 88, 4, '#b5d4c0'], [57, 84, 5, '#a8cdb4'], + [70, 80, 18, '#bfdccd', 1], + [20, 44, 5, '#d9c3a8'], [44, 38, 4, '#e0cbb2'], [66, 30, 18, '#d9c3a8', 1], + [87, 70, 5, '#e0cbb2'], [33, 76, 4, '#d9c3a8'], [58, 72, 5, '#e0cbb2'], + [97, 78, 6, '#d9c3a8'], [3, 72, 4, '#e0cbb2'] + ]; + dots.forEach(function (d) { + var c = document.createElementNS(NS, 'circle'); + c.setAttribute('cx', d[0] + '%'); + c.setAttribute('cy', d[1] + '%'); + c.setAttribute('r', d[2]); + c.setAttribute('fill', d[3]); + if (d[4]) c.setAttribute('class', 'big'); + svg.appendChild(c); + }); + function mount() { + if (!document.body) { requestAnimationFrame(mount); return; } + document.body.insertBefore(svg, document.body.firstChild); + // gentle parallax + var ticking = false; + window.addEventListener('scroll', function () { + if (ticking) return; + ticking = true; + requestAnimationFrame(function () { + svg.style.transform = 'translateY(' + (window.scrollY * -0.12) + 'px)'; + ticking = false; + }); + }, { passive: true }); + } + mount(); +})();