diff --git a/chat-theme/README.md b/chat-theme/README.md new file mode 100644 index 0000000..25120c4 --- /dev/null +++ b/chat-theme/README.md @@ -0,0 +1,56 @@ +# Open WebUI chat theme + +The member chat (chat.inference.coop) is styled to the co-op's house palette +using Open WebUI's **native** `/static/custom.css` hook — no image patching, no +license issues (the branding surface is untouched; we only load styles). + +## Files + +- `openwebui-custom.css` — the theme (copied to the chat app as `/app/data/coop-theme.css`, served as `/static/custom.css`) +- `figtree-*.woff2` — the four Figtree weights (OFL), copied into the chat app's static dir and served same-origin from `/static/fonts/` (avoids cross-origin font CORS problems) + +## How it's deployed + +State on the chat app (`50ae6c97-…` on Cloudron): + +1. **Persistent copies** live in `/app/data/` (survives app updates): + - `/app/data/coop-theme.css` + - `/app/data/fonts/figtree-*.woff2` +2. **Startup hook** in `/app/data/env.sh` (appended, labeled "Co-op theme"): + ```bash + if [ -f /app/data/coop-theme.css ]; then + cp /app/data/coop-theme.css /run/openwebui/static/custom.css 2>/dev/null || true + mkdir -p /run/openwebui/static/fonts + cp /app/data/fonts/*.woff2 /run/openwebui/static/fonts/ 2>/dev/null || true + fi + ``` + This is needed because Open WebUI's start script recreates + `/run/openwebui/static/` from pristine image files on every boot. +3. **Live serving** — Open WebUI's built `index.html` already contains + ``, so no HTML changes. + +## To update the theme + +```bash +# 1. Push the new CSS to the app's persistent storage +cloudron push --app 50ae6c97-e851-4d17-b013-51ca7a4e5be0 \ + openwebui-custom.css /app/data/coop-theme.css + +# 2. Apply immediately (no restart needed) +cloudron exec --app 50ae6c97-e851-4d17-b013-51ca7a4e5be0 -- \ + cp /app/data/coop-theme.css /run/openwebui/static/custom.css +``` + +Verify at `https://chat.inference.coop/static/custom.css`. + +## Contrast notes (v2, 2026-09-29) + +v1 remapped the full Tailwind gray scale, which drifted body text and +placeholders toward olive and hurt readability. v2 rules: + +- **Tint surfaces, not text.** Backgrounds/borders (gray-50..300) get the + cream/olive treatment; text-side tokens (gray-600+) stay near Tailwind + defaults in light mode. +- **Explicit text contrast fixes** for message bodies (`.markdown-prose`), + placeholders, and secondary text, both modes. +- Links darkened to `#3f6e3e` on cream (AA); lightened in dark mode. diff --git a/chat-theme/figtree-400.woff2 b/chat-theme/figtree-400.woff2 new file mode 100644 index 0000000..ca6795e Binary files /dev/null and b/chat-theme/figtree-400.woff2 differ diff --git a/chat-theme/figtree-500.woff2 b/chat-theme/figtree-500.woff2 new file mode 100644 index 0000000..8601545 Binary files /dev/null and b/chat-theme/figtree-500.woff2 differ diff --git a/chat-theme/figtree-600.woff2 b/chat-theme/figtree-600.woff2 new file mode 100644 index 0000000..9b4ec79 Binary files /dev/null and b/chat-theme/figtree-600.woff2 differ diff --git a/chat-theme/figtree-700.woff2 b/chat-theme/figtree-700.woff2 new file mode 100644 index 0000000..3f75953 Binary files /dev/null and b/chat-theme/figtree-700.woff2 differ diff --git a/chat-theme/openwebui-custom.css b/chat-theme/openwebui-custom.css new file mode 100644 index 0000000..b906b8f --- /dev/null +++ b/chat-theme/openwebui-custom.css @@ -0,0 +1,247 @@ +/* Inference Cooperative theme for Open WebUI + House style: cream #faf8f5, ink #2d3327, greens #5b8c5a/#6ba86b/#7ab87a, + bronze #a97a4a, earthy red #b3543a. Figtree via co-op-hosted fonts. + Loaded via /static/custom.css (native Open WebUI hook). */ + +/* ---------- Font (served same-origin from the chat app, no CORS needed) ---------- */ +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('/static/fonts/figtree-400.woff2') format('woff2'); +} +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('/static/fonts/figtree-500.woff2') format('woff2'); +} +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('/static/fonts/figtree-600.woff2') format('woff2'); +} +@font-face { + font-family: 'Figtree'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('/static/fonts/figtree-700.woff2') format('woff2'); +} + +:root { + --coop-cream: #faf8f5; + --coop-cream-2: #f1ede5; + --coop-ink: #2d3327; + --coop-muted: #6b7a62; + --coop-green-deep: #5b8c5a; + --coop-green-mid: #6ba86b; + --coop-green-soft: #7ab87a; + --coop-bronze: #a97a4a; + --coop-earth: #b3543a; + --coop-border: #e4e0d8; +} + +/* Light mode base. + NOTE: base text colors stay near-Tailwind defaults for contrast; we tint + surfaces (backgrounds, borders) rather than pushing text colors around, + because remapping the whole gray scale made body text and placeholders + hard to read (gray-400/500 shifted too far toward olive). */ +html, body { + font-family: 'Figtree', -apple-system, 'Segoe UI', system-ui, sans-serif !important; +} + +/* ---------- Light theme: warm surfaces, readable text ---------- */ +@media (prefers-color-scheme: light), (prefers-color-scheme: no-preference) { + html:not(.dark) { + /* App background: cream. Text: unchanged (dark slate) for max contrast. */ + --color-gray-50: #faf8f5; + --color-gray-75: #f6f3ee; + --color-gray-100: #f1ede5; + --color-gray-200: #e4e0d8; + --color-gray-300: #d4cfc3; + --color-gray-400: #9c978b; /* placeholders — darker than before, still muted */ + --color-gray-500: #75705f; + /* gray-600 through 950 UNTOUCHED: body text keeps Tailwind's near-black + contrast instead of olive drift */ + } +} + +/* Dark mode: warm-tinted greens instead of pure gray. + Keep text-side tokens (600+) bright enough for contrast. */ +html.dark { + --color-gray-50: #262b21; + --color-gray-75: #2d3327; + --color-gray-100: #333a2d; + --color-gray-200: #3e4636; + --color-gray-300: #545e4a; + --color-gray-400: #7d8875; + --color-gray-500: #97a28e; + --color-gray-600: #b2bca9; + --color-gray-700: #ccd3c3; + --color-gray-800: #e3e7db; + --color-gray-850: #eef1e7; + --color-gray-900: #f5f7ef; + --color-gray-950: #faf8f5; +} + +/* ---------- Accent color: co-op green ---------- */ +:root, html.dark { + --color-green-300: #7ab87a; + --color-green-400: #6ba86b; + --color-green-500: #5b8c5a; + --color-green-600: #4e7a4d; +} + +/* Primary buttons */ +.btn-primary, button.bg-black, button.bg-emerald-600, .bg-emerald-600 { + background-color: #5b8c5a !important; +} +.btn-primary:hover, button.bg-emerald-600:hover { + background-color: #4e7a4d !important; +} + +/* Selected states, toggles, focus rings */ +.bg-emerald-500, .peer:checked ~ .bg-emerald-500 { + background-color: #6ba86b !important; +} +.ring-emerald-500, .focus\:ring-emerald-500:focus, .border-emerald-500 { + --tw-ring-color: #6ba86b; + border-color: #6ba86b; +} +.text-emerald-500, .text-emerald-400, .text-emerald-300 { + color: #5b8c5a !important; +} +html.dark .text-emerald-500, html.dark .text-emerald-400, html.dark .text-emerald-300 { + color: #7ab87a !important; +} + +/* ---------- Sidebar ---------- */ +#sidebar { + background: linear-gradient(180deg, #f6f3ee 0%, #f1ede5 100%) !important; + border-right: 1px solid #e4e0d8; +} +html.dark #sidebar { + background: linear-gradient(180deg, #262b21 0%, #1f241b 100%) !important; + border-right: 1px solid #3e4636; +} + +/* Sidebar: active conversation */ +#sidebar a[id^="chat-item"].active { + background-color: #eaf3ea !important; + border-left: 3px solid #5b8c5a; +} +html.dark #sidebar a[id^="chat-item"].active { + background-color: #333a2d !important; +} + +/* ---------- Chat area ---------- */ +/* User message bubble: soft green tint */ +.user-message .message, .chat-user .message, div[class*="user-message"] { + background-color: #eaf3ea !important; + border-radius: 14px; +} +html.dark .user-message .message, html.dark div[class*="user-message"] { + background-color: #333a2d !important; +} + +/* Assistant message: cream card */ +.assistant-message .message, div[class*="assistant-message"] { + border-radius: 14px; +} + +/* ---------- Composer ---------- */ +#chat-input, textarea[name="prompt"], .scrollbar-hidden.relative { + border-radius: 14px !important; +} +button[type="submit"].bg-black { + background-color: #5b8c5a !important; +} + +/* ---------- Model selector chip ---------- */ +button[aria-label="Select a model"], #model-selector { + border-color: #d4cfc3; +} +html.dark button[aria-label="Select a model"], html.dark #model-selector { + border-color: #545e4a; +} + +/* ---------- Code blocks ---------- */ +pre, code, .language-python, .hljs { + font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Menlo, monospace; +} +.codeblock { + border-radius: 12px !important; + border: 1px solid #e4e0d8; +} +html.dark .codeblock { + border-color: #3e4636; +} + +/* ---------- Scrollbars: thin, green ---------- */ +::-webkit-scrollbar { width: 8px; height: 8px; } +::-webkit-scrollbar-thumb { background: #c9d3c5; border-radius: 8px; } +::-webkit-scrollbar-thumb:hover { background: #6ba86b; } +html.dark ::-webkit-scrollbar-thumb { background: #3e4636; } +html.dark ::-webkit-scrollbar-thumb:hover { background: #6ba86b; } +::-webkit-scrollbar-track { background: transparent; } + +/* ---------- Selection ---------- */ +::selection { background: #cfe4cf; } +html.dark ::selection { background: #3e5a3d; } + +/* ---------- Links ---------- */ +a:not(.no-underline):not([class*="btn"]) { + color: #3f6e3e; /* darkened for AA contrast on cream */ +} +html.dark a:not(.no-underline):not([class*="btn"]) { + color: #8fc98f; +} + +/* ---------- Text contrast fixes ---------- */ +/* Assistant reply text: near-black in light mode regardless of remapped tokens */ +.text-gray-800, .text-gray-900, div[id^="message-"] .markdown-prose { + color: #262b21; +} +html.dark .text-gray-800, html.dark .text-gray-900, html.dark div[id^="message-"] .markdown-prose { + color: #eef1e7; +} + +/* Placeholder + secondary text: readable, not ghostly */ +::placeholder, .placeholder\:text-gray-400::placeholder { + color: #8a8578 !important; + opacity: 1 !important; +} +html.dark ::placeholder { + color: #7d8875 !important; +} +.text-gray-400 { color: #75705f !important; } +html.dark .text-gray-400 { color: #97a28e !important; } +.text-gray-500 { color: #66604f !important; } +html.dark .text-gray-500 { color: #a5b09a !important; } + +/* Input borders slightly stronger so fields are visible on cream */ +input, textarea, select { + border-color: #d4cfc3 !important; +} +html.dark input, html.dark textarea, html.dark select { + border-color: #545e4a !important; +} + +/* ---------- Modal / dropdown polish ---------- */ +.modal, .dropdown-menu, [role="menu"] { + border-radius: 14px !important; + border: 1px solid #e4e0d8 !important; +} +html.dark .modal, html.dark .dropdown-menu, html.dark [role="menu"] { + border-color: #3e4636 !important; +} + +/* ---------- Small serif-free caps for section labels (subtle) ---------- */ +.settings-tab, [data-sidebar-label] { + letter-spacing: 0.02em; +}