From 937ab538b1153feeacfeb1b499bd65125f24729d Mon Sep 17 00:00:00 2001 From: Paul Swenson Date: Wed, 13 May 2026 13:38:30 -0400 Subject: [PATCH 1/7] Add OpenCentauri Fluidd theme --- .../apply-opencentauri-fluidd-theme.py | 165 + .../logo_opencentauri.svg | 10638 ++++++++++++++++ .../opencentauri-icon.webp | Bin 0 -> 4486 bytes .../opencentauri-logo-small.png | Bin 0 -> 17102 bytes .../opencentauri-theme.css | 50 + .../recipes-apps/fluidd/fluidd_1.37.0.bb | 10 + 6 files changed, 10863 insertions(+) create mode 100644 meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py create mode 100644 meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/logo_opencentauri.svg create mode 100644 meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-icon.webp create mode 100644 meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-logo-small.png create mode 100644 meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py new file mode 100644 index 00000000..62a369a5 --- /dev/null +++ b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +import json +import re +import sys +from pathlib import Path + + +APP_NAME = "OpenCentauri" +DESCRIPTION = "OpenCentauri web interface for the Elegoo Centauri Carbon" +PRIMARY_COLOR = "#9EA2A6" +LOGO_SRC = "logo_opencentauri.svg" +ICON_SRC = "opencentauri-icon.webp" +WORDMARK_SRC = "opencentauri-logo-small.png" +THEME_CSS = "opencentauri-theme.css" + + +def replace_once(text, old, new, path): + if old not in text: + raise RuntimeError(f"{path}: expected text not found: {old[:80]!r}") + return text.replace(old, new, 1) + + +def patch_index_html(webroot): + path = webroot / "index.html" + text = path.read_text() + text = re.sub(r".*?", f"{APP_NAME}", text, count=1) + text = re.sub( + r'', + f'', + text, + count=1, + ) + text = re.sub( + r'', + f'', + text, + count=1, + ) + text = re.sub( + r'\s*\n', + "\n", + text, + count=1, + ) + text = re.sub( + r'', + f'', + text, + count=1, + ) + text = re.sub( + r'', + f'', + text, + count=1, + ) + text = re.sub( + r'', + f'', + text, + count=1, + ) + text = re.sub( + r'', + f'', + text, + count=1, + ) + if THEME_CSS not in text: + text = text.replace( + '', + f'\n ', + 1, + ) + path.write_text(text) + + +def patch_manifest(webroot): + path = webroot / "manifest.webmanifest" + manifest = json.loads(path.read_text()) + manifest.update( + { + "name": APP_NAME, + "short_name": APP_NAME, + "description": DESCRIPTION, + "theme_color": PRIMARY_COLOR, + "icons": [ + { + "src": ICON_SRC, + "sizes": "128x128", + "type": "image/webp", + "purpose": "any maskable", + } + ], + } + ) + path.write_text(json.dumps(manifest, separators=(",", ":"))) + + +def patch_config(webroot): + path = webroot / "config.json" + config = json.loads(path.read_text()) + presets = [p for p in config.get("themePresets", []) if p.get("name") != APP_NAME] + presets.insert( + 0, + { + "name": APP_NAME, + "color": PRIMARY_COLOR, + "isDark": True, + "logo": { + "src": LOGO_SRC, + "dark": "#232323", + "light": "#ffffff", + }, + }, + ) + config["themePresets"] = presets + path.write_text(json.dumps(config, indent=2) + "\n") + + +def patch_main_bundle(webroot): + bundles = sorted((webroot / "assets").glob("index-*.js")) + if len(bundles) != 1: + raise RuntimeError(f"{webroot}: expected exactly one assets/index-*.js bundle, found {len(bundles)}") + + path = bundles[0] + text = path.read_text() + text = replace_once( + text, + "get pageIcon(){let e=this.printInProgressIconDataUrl||this.defaultIconDataUrl;return[{rel:`icon`,type:`image/svg+xml`,sizes:`32x32`,href:e},{rel:`icon`,type:`image/svg+xml`,sizes:`16x16`,href:e}]}", + "get pageIcon(){let e=this.printInProgressIconDataUrl||this.defaultIconDataUrl,t=e.startsWith(`data:`)?`image/png`:`image/webp`;return[{rel:`icon`,type:t,sizes:`32x32`,href:e},{rel:`icon`,type:t,sizes:`16x16`,href:e}]}", + path, + ) + text, count = re.subn( + r"get defaultIconDataUrl\(\)\{let e=``;return`data:image/svg\+xml;base64,\$\{btoa\(e\)\}`\}", + f"get defaultIconDataUrl(){{return`./{ICON_SRC}`}}", + text, + count=1, + ) + if count != 1: + raise RuntimeError(f"{path}: expected defaultIconDataUrl implementation not found") + path.write_text(text) + + +def verify_assets(webroot): + for name in (ICON_SRC, WORDMARK_SRC, LOGO_SRC, THEME_CSS): + path = webroot / name + if not path.exists(): + raise RuntimeError(f"{path}: missing OpenCentauri theme asset") + + +def main(): + if len(sys.argv) != 2: + raise SystemExit(f"usage: {Path(sys.argv[0]).name} /path/to/fluidd-webroot") + + webroot = Path(sys.argv[1]) + verify_assets(webroot) + patch_index_html(webroot) + patch_manifest(webroot) + patch_config(webroot) + patch_main_bundle(webroot) + + +if __name__ == "__main__": + main() diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/logo_opencentauri.svg b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/logo_opencentauri.svg new file mode 100644 index 00000000..ce9afaa4 --- /dev/null +++ b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/logo_opencentauri.svg @@ -0,0 +1,10638 @@ + + + + + OpenCentauri Projection + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OpenCentauri Projection + + + + diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-icon.webp b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-icon.webp new file mode 100644 index 0000000000000000000000000000000000000000..0afad0c39d853e7f90d2afd6ec552c54f01a6c44 GIT binary patch literal 4486 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 a { + display: block; + width: min(240px, 42vw); + height: 30px; + overflow: hidden; + color: transparent; + font-size: 0; + line-height: 0; + background-image: url("./opencentauri-logo-small.png"); + background-repeat: no-repeat; + background-position: left center; + background-size: contain; +} + +@media only screen and (max-width: 599px) { + .toolbar-title { + max-width: 72%; + padding-left: 8px; + } + + .printer-title > a { + width: min(210px, 58vw); + height: 24px; + } +} diff --git a/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb b/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb index 81f3428f..a15dbfaf 100644 --- a/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb +++ b/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb @@ -6,10 +6,13 @@ HOMEPAGE = "https://github.com/fluidd-core/fluidd" LICENSE = "GPL-3.0-only" LIC_FILES_CHKSUM = "file://index.html;md5=e465c9484b3a1201b4cff1978e78b863" +inherit python3native + FILESEXTRAPATHS:prepend := "${THISDIR}/files:" SRC_URI = "https://github.com/fluidd-core/fluidd/releases/download/v${PV}/fluidd.zip;downloadfilename=fluidd-${PV}.zip;subdir=fluidd \ file://fluidd.cfg \ + file://opencentauri-fluidd-theme \ " SRC_URI[sha256sum] = "b9f003a82ea9061a77c5b2f47c5cdd15c58c8f5f5532960e811be2b01cf0a00b" @@ -31,6 +34,13 @@ do_install() { # Install static web files install -d ${D}/var/www/fluidd cp -r ${S}/* ${D}/var/www/fluidd/ + install -m 0644 \ + ${WORKDIR}/opencentauri-fluidd-theme/logo_opencentauri.svg \ + ${WORKDIR}/opencentauri-fluidd-theme/opencentauri-icon.webp \ + ${WORKDIR}/opencentauri-fluidd-theme/opencentauri-logo-small.png \ + ${WORKDIR}/opencentauri-fluidd-theme/opencentauri-theme.css \ + ${D}/var/www/fluidd/ + ${PYTHON} ${WORKDIR}/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py ${D}/var/www/fluidd # Install default fluidd config install -d ${D}${sysconfdir}/klipper From 9340c45f858f5fdfbc6c803b66930f84d6f750eb Mon Sep 17 00:00:00 2001 From: Paul Swenson Date: Wed, 13 May 2026 14:24:06 -0400 Subject: [PATCH 2/7] Refine OpenCentauri Fluidd dark theme branding CSS (opencentauri-theme.css): - Expand from logo-only styling to a complete dark OpenCentauri theme. - Define grayscale CSS variables (--oc-bg, --oc-surface, --oc-primary, etc.) on .theme--dark.v-application. - Drive app background to near-black (#050505) with dark gray cards/sheets/dialogs/menus/lists/tables. - Apply subtle white-alpha borders and muted text colors for secondary/disabled states. - Set primary/action tones to neutral gray/white (#D8DADC) with related muted variants. - Tone down semantic colors (success/info/warning/error) to fit the grayscale palette. - Override core Vuetify/Fluidd surfaces: app bar, toolbar, nav drawer, cards, dialogs, inputs, selects, sliders, progress bars, tabs, buttons, chips, alerts, snackbars, collapsible card headings, and file-system hover/action surfaces. - Leave filament/material-type colors untouched so they still represent real printer data. - Force the top-left toolbar logo to logo_opencentauri.svg via CSS background-image with grayscale(1) invert(1) brightness(0.82) filter for readability on black. - Add the wide OpenCentauri wordmark banner in the top header via opencentauri-logo-small.png. - Size and filter the bottom-right background watermark (.background-logo): width: min(38vw, 560px), opacity: 0.3, grayscale(1) invert(1) brightness(1.15) contrast(1.05). Patcher (apply-opencentauri-fluidd-theme.py): - Bump PRIMARY_COLOR from #9EA2A6 to #D8DADC for a brighter neutral primary. - Fix logoSrc getter patching to replace ALL occurrences in the minified bundle (not just the first). This ensures both the toolbar logo component and the app-level inline-svg.background-logo watermark render logo_opencentauri.svg instead of falling back to the Fluidd default logo. - Switch favicon/icon source from opencentauri-icon.webp to the new opencentauri-icon-gray.webp grayscale variant. New asset: - opencentauri-icon-gray.webp: grayscale + brightened version of the original colored icon, used as the favicon/apple-touch-icon/msapplication-TileImage. Recipe (fluidd_1.37.0.bb): - Add opencentauri-icon-gray.webp to the do_install file copy step so it is deployed alongside the other theme assets. Closes the TBD in PR #183 for deeper custom CSS branding. --- .../apply-opencentauri-fluidd-theme.py | 8 +- .../opencentauri-icon-gray.webp | Bin 0 -> 6396 bytes .../opencentauri-theme.css | 225 +++++++++++++++++- .../recipes-apps/fluidd/fluidd_1.37.0.bb | 1 + 4 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-icon-gray.webp diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py index 62a369a5..75e09b21 100644 --- a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py +++ b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py @@ -7,9 +7,9 @@ APP_NAME = "OpenCentauri" DESCRIPTION = "OpenCentauri web interface for the Elegoo Centauri Carbon" -PRIMARY_COLOR = "#9EA2A6" +PRIMARY_COLOR = "#D8DADC" LOGO_SRC = "logo_opencentauri.svg" -ICON_SRC = "opencentauri-icon.webp" +ICON_SRC = "opencentauri-icon-gray.webp" WORDMARK_SRC = "opencentauri-logo-small.png" THEME_CSS = "opencentauri-theme.css" @@ -139,6 +139,10 @@ def patch_main_bundle(webroot): ) if count != 1: raise RuntimeError(f"{path}: expected defaultIconDataUrl implementation not found") + old_logo_src_getter = "get logoSrc(){return`./${this.theme.logo.src}`}" + if old_logo_src_getter not in text: + raise RuntimeError(f"{path}: expected logoSrc getter not found") + text = text.replace(old_logo_src_getter, f"get logoSrc(){{return`./{LOGO_SRC}`}}") path.write_text(text) diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-icon-gray.webp b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-icon-gray.webp new file mode 100644 index 0000000000000000000000000000000000000000..0f78be7a03ad82474f5aab6145382641370ef4ed GIT binary patch literal 6396 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 literal 0 HcmV?d00001 diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css index c89e4bda..590cdbdd 100644 --- a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css +++ b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css @@ -1,15 +1,238 @@ +.theme--dark.v-application { + --oc-bg: #050505; + --oc-surface: #111111; + --oc-surface-raised: #171717; + --oc-surface-muted: #1f1f1f; + --oc-border: rgba(255, 255, 255, 0.12); + --oc-border-strong: rgba(255, 255, 255, 0.2); + --oc-text: rgba(255, 255, 255, 0.92); + --oc-text-muted: rgba(255, 255, 255, 0.62); + --oc-text-disabled: rgba(255, 255, 255, 0.38); + --oc-primary: #d8dadc; + --oc-primary-muted: #aeb2b6; + --oc-success: #8fa392; + --oc-info: #8d98a6; + --oc-warning: #b9a77a; + --oc-error: #b98282; + --v-primary-base: var(--oc-primary); + --v-primary-lighten1: #eeeeee; + --v-primary-darken1: #aeb2b6; + --v-primary-offset-base: #aeb2b6; + --v-secondary-base: #8d8d8d; + --v-card-heading-base: #1b1b1b; + --v-btncolor-base: #202020; + --v-btncolor-lighten1: #292929; + --v-btncolor-lighten2: #333333; + --v-btncolor-darken1: #161616; + --v-btncolor-darken2: #101010; + --v-btncolor-darken4: #050505; + --v-drawer-base: #080808; + --v-appbar-base: #050505; + --v-success-base: var(--oc-success); + --v-info-base: var(--oc-info); + --v-warning-base: var(--oc-warning); + --v-error-base: var(--oc-error); + color: var(--oc-text); + background: var(--oc-bg) !important; +} + +.theme--dark.v-application .v-main, +.theme--dark.v-application .v-main__wrap, +.theme--dark.v-application .v-application--wrap { + background: transparent !important; +} + +.theme--dark.v-application .v-app-bar, +.theme--dark.v-application .v-toolbar, +.theme--dark.v-application .v-toolbar__content, +.theme--dark.v-application .v-toolbar__extension { + background-color: var(--oc-bg) !important; + border-color: var(--oc-border) !important; +} + +.theme--dark.v-navigation-drawer, +.theme--dark.v-navigation-drawer .v-navigation-drawer__content, +.theme--dark.v-list { + background-color: #080808 !important; +} + +.theme--dark.v-card, +.theme--dark.v-sheet, +.theme--dark.v-menu__content, +.theme--dark.v-dialog, +.theme--dark.v-expansion-panels .v-expansion-panel, +.theme--dark.v-data-table, +.theme--dark.v-tabs-items, +.theme--dark.v-picker, +.theme--dark.v-stepper { + color: var(--oc-text) !important; + background-color: var(--oc-surface) !important; + border-color: var(--oc-border) !important; +} + +.theme--dark.v-card, +.theme--dark.v-sheet, +.theme--dark.v-menu__content, +.theme--dark.v-dialog, +.theme--dark.v-expansion-panels .v-expansion-panel { + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35) !important; +} + +.theme--dark .v-card .v-card__title.collapsable-card-title, +.theme--dark .card-heading, +.theme--dark .v-toolbar.card-heading, +.theme--dark.v-toolbar.card-heading { + color: var(--oc-text) !important; + background-color: var(--oc-surface-muted) !important; + border-bottom: thin solid var(--oc-border) !important; +} + +.theme--dark.v-application .v-card__subtitle, +.theme--dark.v-application .v-list-item__subtitle, +.theme--dark.v-application .dim--text, +.theme--dark.v-application .text--secondary { + color: var(--oc-text-muted) !important; +} + +.theme--dark.v-application .v-icon, +.theme--dark.v-application .v-label, +.theme--dark.v-application .v-input, +.theme--dark.v-application .v-select__selection, +.theme--dark.v-application input, +.theme--dark.v-application textarea { + color: var(--oc-text) !important; +} + +.theme--dark.v-application .v-list-item--disabled, +.theme--dark.v-application .v-btn--disabled, +.theme--dark.v-application .v-input--is-disabled, +.theme--dark.v-application .text--disabled { + color: var(--oc-text-disabled) !important; +} + +.theme--dark.v-application .v-list-item:hover:before, +.theme--dark.v-application .v-list-item--active:before, +.theme--dark.v-application .v-tab:before, +.theme--dark.v-application .v-btn:before { + background-color: #ffffff !important; +} + +.theme--dark.v-application .v-list-item--active, +.theme--dark.v-application .v-tab--active, +.theme--dark.v-application .v-btn.primary, +.theme--dark.v-application .v-btn.accent { + color: #ffffff !important; +} + +.theme--dark.v-application .v-btn.primary, +.theme--dark.v-application .v-btn.accent { + background-color: #2a2a2a !important; + border: thin solid var(--oc-border-strong) !important; +} + +.theme--dark.v-application .v-btn:not(.v-btn--text):not(.v-btn--icon):not(.v-btn--plain) { + box-shadow: none !important; +} + +.theme--dark.v-application .v-chip, +.theme--dark.v-application .v-alert, +.theme--dark.v-application .v-snack__wrapper { + color: var(--oc-text) !important; + background-color: var(--oc-surface-raised) !important; + border-color: var(--oc-border) !important; +} + +.theme--dark.v-application .v-alert.success, +.theme--dark.v-application .v-chip.success { + background-color: rgba(143, 163, 146, 0.18) !important; +} + +.theme--dark.v-application .v-alert.info, +.theme--dark.v-application .v-chip.info { + background-color: rgba(141, 152, 166, 0.18) !important; +} + +.theme--dark.v-application .v-alert.warning, +.theme--dark.v-application .v-chip.warning { + background-color: rgba(185, 167, 122, 0.2) !important; +} + +.theme--dark.v-application .v-alert.error, +.theme--dark.v-application .v-chip.error { + background-color: rgba(185, 130, 130, 0.2) !important; +} + +.theme--dark.v-application .v-data-table > .v-data-table__wrapper > table > thead > tr > th, +.theme--dark.v-application .v-data-table > .v-data-table__wrapper > table > tbody > tr > td { + color: var(--oc-text) !important; + border-bottom-color: var(--oc-border) !important; +} + +.theme--dark.v-application .v-data-table > .v-data-table__wrapper > table > tbody > tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper), +.theme--dark.v-application .file-system .theme--dark.v-data-table tr > td.actions div { + background-color: var(--oc-surface-raised) !important; +} + +.theme--dark.v-application .file-system .theme--dark.v-data-table tr > td.actions div:before { + background: linear-gradient(90deg, rgba(23, 23, 23, 0) 0%, var(--oc-surface-raised) 100%) !important; +} + +.theme--dark.v-application .v-text-field > .v-input__control > .v-input__slot, +.theme--dark.v-application .v-select > .v-input__control > .v-input__slot, +.theme--dark.v-application .v-input--selection-controls__input, +.theme--dark.v-application .v-slider__track-background { + background-color: #0d0d0d !important; + border-color: var(--oc-border) !important; +} + +.theme--dark.v-application .v-input__slot:before, +.theme--dark.v-application .v-input__slot:after, +.theme--dark.v-application .v-divider { + border-color: var(--oc-border) !important; +} + +.theme--dark.v-application .v-progress-linear__background, +.theme--dark.v-application .v-slider__track-background { + background-color: #2a2a2a !important; +} + +.theme--dark.v-application .v-progress-linear__determinate, +.theme--dark.v-application .v-slider__track-fill, +.theme--dark.v-application .v-slider__thumb, +.theme--dark.v-application .v-tabs-slider { + background-color: var(--oc-primary-muted) !important; + border-color: var(--oc-primary-muted) !important; +} + +.theme--dark.v-application .v-navigation-drawer__border, +.theme--dark.v-application .v-toolbar--extended .v-toolbar__content, +.theme--dark.v-application .v-card:not(.collapsed) .v-card__title.collapsable-card-title { + box-shadow: none !important; +} + +.theme--dark.v-application .background-logo { + width: min(38vw, 560px) !important; + height: auto !important; + opacity: 0.3 !important; + filter: grayscale(1) invert(1) brightness(1.15) contrast(1.05) !important; +} .toolbar-logo .logo-wrapper, .toolbar-logo .logo-wrapper svg { width: 56px; height: 56px; } +.theme--dark .toolbar-logo { + background-color: var(--oc-bg) !important; + border-right-color: var(--oc-border) !important; +} + .toolbar-logo .logo-wrapper { background-image: url("./logo_opencentauri.svg"); background-repeat: no-repeat; background-position: center; background-size: 48px 48px; - filter: invert(1) brightness(0.75); + filter: grayscale(1) invert(1) brightness(0.82); } .toolbar-logo .logo-wrapper svg { diff --git a/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb b/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb index a15dbfaf..8979fdad 100644 --- a/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb +++ b/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb @@ -37,6 +37,7 @@ do_install() { install -m 0644 \ ${WORKDIR}/opencentauri-fluidd-theme/logo_opencentauri.svg \ ${WORKDIR}/opencentauri-fluidd-theme/opencentauri-icon.webp \ + ${WORKDIR}/opencentauri-fluidd-theme/opencentauri-icon-gray.webp \ ${WORKDIR}/opencentauri-fluidd-theme/opencentauri-logo-small.png \ ${WORKDIR}/opencentauri-fluidd-theme/opencentauri-theme.css \ ${D}/var/www/fluidd/ From ad3a29ad1b86fb43f00c6a6fa967c114feab251a Mon Sep 17 00:00:00 2001 From: Paul Swenson Date: Wed, 13 May 2026 14:36:50 -0400 Subject: [PATCH 3/7] Style notification badge: dark gray bubble with white border and text Adds .v-badge__badge styling to opencentauri-theme.css so the notification count bubble on the bell icon uses a dark gray (#333333) background with white text and a white circular border, making it readable against the dark theme instead of the default light/saturated Vuetify badge colors. --- .../opencentauri-fluidd-theme/opencentauri-theme.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css index 590cdbdd..e8024dc0 100644 --- a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css +++ b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css @@ -162,6 +162,14 @@ background-color: rgba(185, 130, 130, 0.2) !important; } +.theme--dark.v-application .v-badge__badge { + background-color: #333333 !important; + color: #ffffff !important; + border: 2px solid #ffffff !important; + border-radius: 50% !important; + box-sizing: border-box; +} + .theme--dark.v-application .v-data-table > .v-data-table__wrapper > table > thead > tr > th, .theme--dark.v-application .v-data-table > .v-data-table__wrapper > table > tbody > tr > td { color: var(--oc-text) !important; From 543b503a59765d1bc269d5d977a71d33a8351d4a Mon Sep 17 00:00:00 2001 From: Paul Swenson Date: Wed, 13 May 2026 22:25:21 -0400 Subject: [PATCH 4/7] Encapsulate OpenCentauri as a selectable theme preset Previously the CSS globally overrode all dark themes. Now it is properly scoped so it only applies when the OpenCentauri theme is active. CSS changes: - All selectors scoped under [data-fluidd-theme="OpenCentauri"] - The theme only applies when selected, allowing users to switch Patcher changes: - Add data-fluidd-theme="OpenCentauri" to tag (prevents FOUC) - Patch onThemeChange() to update data-fluidd-theme when theme changes - Patch initial Vuex state to default to OpenCentauri theme - Set config.json default theme to OpenCentauri Users can now select any theme from the dropdown. OpenCentauri is the default, but Fluidd, Annex, and other presets work normally. --- .../apply-opencentauri-fluidd-theme.py | 37 ++++ .../opencentauri-theme.css | 194 +++++++++--------- 2 files changed, 134 insertions(+), 97 deletions(-) diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py index 75e09b21..8dc8dd58 100644 --- a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py +++ b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py @@ -24,6 +24,8 @@ def patch_index_html(webroot): path = webroot / "index.html" text = path.read_text() text = re.sub(r".*?", f"{APP_NAME}", text, count=1) + # Add data-fluidd-theme to html tag for immediate CSS scoping (prevents FOUC) + text = text.replace("", f'') text = re.sub( r'', f'', @@ -115,6 +117,17 @@ def patch_config(webroot): }, ) config["themePresets"] = presets + # Set OpenCentauri as the default active theme for new users + config["theme"] = { + "name": APP_NAME, + "color": PRIMARY_COLOR, + "isDark": True, + "logo": { + "src": LOGO_SRC, + "dark": "#232323", + "light": "#ffffff", + }, + } path.write_text(json.dumps(config, indent=2) + "\n") @@ -143,9 +156,32 @@ def patch_main_bundle(webroot): if old_logo_src_getter not in text: raise RuntimeError(f"{path}: expected logoSrc getter not found") text = text.replace(old_logo_src_getter, f"get logoSrc(){{return`./{LOGO_SRC}`}}") + # Patch onThemeChange to toggle a data attribute for CSS scoping + old_on_theme_change = "async onThemeChange(e,t){let n=Io.framework.theme;n.dark=t.isDark,n.currentTheme.primary=t.color,n.currentTheme[`primary-offset`]=new ft(t.color).desaturate(5).darken(10).toHexString(),n.themes.dark.logo=t.logo.light,n.themes.light.logo=t.logo.dark}" + new_on_theme_change = "async onThemeChange(e,t){let n=Io.framework.theme;n.dark=t.isDark,n.currentTheme.primary=t.color,n.currentTheme[`primary-offset`]=new ft(t.color).desaturate(5).darken(10).toHexString(),n.themes.dark.logo=t.logo.light,n.themes.light.logo=t.logo.dark;document.documentElement.dataset.fluiddTheme=t.name||\"\"}" + text = replace_once(text, old_on_theme_change, new_on_theme_change, path) path.write_text(text) +def patch_state_chunk(webroot): + """Patch the initial Vuex state so OpenCentauri is the default theme.""" + chunks = sorted((webroot / "assets").glob("state-*.js")) + if not chunks: + return # Not all builds have this chunk + + old_theme = "theme:{isDark:!0,logo:{src:`logo_fluidd.svg`},color:`#2196F3`,backgroundLogo:!0}" + new_theme = f"theme:{{name:`{APP_NAME}`,isDark:!0,logo:{{src:`{LOGO_SRC}`}},color:`{PRIMARY_COLOR}`,backgroundLogo:!0}}" + + for path in chunks: + text = path.read_text() + if old_theme in text: + text = text.replace(old_theme, new_theme) + path.write_text(text) + return # Patched successfully + + raise RuntimeError(f"{webroot}/assets: expected hardcoded default theme not found in any state chunk") + + def verify_assets(webroot): for name in (ICON_SRC, WORDMARK_SRC, LOGO_SRC, THEME_CSS): path = webroot / name @@ -163,6 +199,7 @@ def main(): patch_manifest(webroot) patch_config(webroot) patch_main_bundle(webroot) + patch_state_chunk(webroot) if __name__ == "__main__": diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css index e8024dc0..bd2a73a9 100644 --- a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css +++ b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/opencentauri-theme.css @@ -1,4 +1,4 @@ -.theme--dark.v-application { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application { --oc-bg: #050505; --oc-surface: #111111; --oc-surface-raised: #171717; @@ -36,133 +36,133 @@ background: var(--oc-bg) !important; } -.theme--dark.v-application .v-main, -.theme--dark.v-application .v-main__wrap, -.theme--dark.v-application .v-application--wrap { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-main, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-main__wrap, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-application--wrap { background: transparent !important; } -.theme--dark.v-application .v-app-bar, -.theme--dark.v-application .v-toolbar, -.theme--dark.v-application .v-toolbar__content, -.theme--dark.v-application .v-toolbar__extension { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-app-bar, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-toolbar, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-toolbar__content, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-toolbar__extension { background-color: var(--oc-bg) !important; border-color: var(--oc-border) !important; } -.theme--dark.v-navigation-drawer, -.theme--dark.v-navigation-drawer .v-navigation-drawer__content, -.theme--dark.v-list { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-navigation-drawer, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-navigation-drawer .v-navigation-drawer__content, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-list { background-color: #080808 !important; } -.theme--dark.v-card, -.theme--dark.v-sheet, -.theme--dark.v-menu__content, -.theme--dark.v-dialog, -.theme--dark.v-expansion-panels .v-expansion-panel, -.theme--dark.v-data-table, -.theme--dark.v-tabs-items, -.theme--dark.v-picker, -.theme--dark.v-stepper { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-card, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-sheet, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-menu__content, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-dialog, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-expansion-panels .v-expansion-panel, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-data-table, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-tabs-items, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-picker, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-stepper { color: var(--oc-text) !important; background-color: var(--oc-surface) !important; border-color: var(--oc-border) !important; } -.theme--dark.v-card, -.theme--dark.v-sheet, -.theme--dark.v-menu__content, -.theme--dark.v-dialog, -.theme--dark.v-expansion-panels .v-expansion-panel { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-card, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-sheet, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-menu__content, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-dialog, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-expansion-panels .v-expansion-panel { box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35) !important; } -.theme--dark .v-card .v-card__title.collapsable-card-title, -.theme--dark .card-heading, -.theme--dark .v-toolbar.card-heading, -.theme--dark.v-toolbar.card-heading { +[data-fluidd-theme="OpenCentauri"] .theme--dark .v-card .v-card__title.collapsable-card-title, +[data-fluidd-theme="OpenCentauri"] .theme--dark .card-heading, +[data-fluidd-theme="OpenCentauri"] .theme--dark .v-toolbar.card-heading, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-toolbar.card-heading { color: var(--oc-text) !important; background-color: var(--oc-surface-muted) !important; border-bottom: thin solid var(--oc-border) !important; } -.theme--dark.v-application .v-card__subtitle, -.theme--dark.v-application .v-list-item__subtitle, -.theme--dark.v-application .dim--text, -.theme--dark.v-application .text--secondary { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-card__subtitle, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-list-item__subtitle, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .dim--text, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .text--secondary { color: var(--oc-text-muted) !important; } -.theme--dark.v-application .v-icon, -.theme--dark.v-application .v-label, -.theme--dark.v-application .v-input, -.theme--dark.v-application .v-select__selection, -.theme--dark.v-application input, -.theme--dark.v-application textarea { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-icon, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-label, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-input, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-select__selection, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application input, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application textarea { color: var(--oc-text) !important; } -.theme--dark.v-application .v-list-item--disabled, -.theme--dark.v-application .v-btn--disabled, -.theme--dark.v-application .v-input--is-disabled, -.theme--dark.v-application .text--disabled { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-list-item--disabled, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-btn--disabled, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-input--is-disabled, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .text--disabled { color: var(--oc-text-disabled) !important; } -.theme--dark.v-application .v-list-item:hover:before, -.theme--dark.v-application .v-list-item--active:before, -.theme--dark.v-application .v-tab:before, -.theme--dark.v-application .v-btn:before { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-list-item:hover:before, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-list-item--active:before, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-tab:before, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-btn:before { background-color: #ffffff !important; } -.theme--dark.v-application .v-list-item--active, -.theme--dark.v-application .v-tab--active, -.theme--dark.v-application .v-btn.primary, -.theme--dark.v-application .v-btn.accent { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-list-item--active, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-tab--active, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-btn.primary, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-btn.accent { color: #ffffff !important; } -.theme--dark.v-application .v-btn.primary, -.theme--dark.v-application .v-btn.accent { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-btn.primary, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-btn.accent { background-color: #2a2a2a !important; border: thin solid var(--oc-border-strong) !important; } -.theme--dark.v-application .v-btn:not(.v-btn--text):not(.v-btn--icon):not(.v-btn--plain) { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-btn:not(.v-btn--text):not(.v-btn--icon):not(.v-btn--plain) { box-shadow: none !important; } -.theme--dark.v-application .v-chip, -.theme--dark.v-application .v-alert, -.theme--dark.v-application .v-snack__wrapper { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-chip, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-alert, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-snack__wrapper { color: var(--oc-text) !important; background-color: var(--oc-surface-raised) !important; border-color: var(--oc-border) !important; } -.theme--dark.v-application .v-alert.success, -.theme--dark.v-application .v-chip.success { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-alert.success, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-chip.success { background-color: rgba(143, 163, 146, 0.18) !important; } -.theme--dark.v-application .v-alert.info, -.theme--dark.v-application .v-chip.info { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-alert.info, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-chip.info { background-color: rgba(141, 152, 166, 0.18) !important; } -.theme--dark.v-application .v-alert.warning, -.theme--dark.v-application .v-chip.warning { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-alert.warning, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-chip.warning { background-color: rgba(185, 167, 122, 0.2) !important; } -.theme--dark.v-application .v-alert.error, -.theme--dark.v-application .v-chip.error { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-alert.error, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-chip.error { background-color: rgba(185, 130, 130, 0.2) !important; } -.theme--dark.v-application .v-badge__badge { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-badge__badge { background-color: #333333 !important; color: #ffffff !important; border: 2px solid #ffffff !important; @@ -170,72 +170,72 @@ box-sizing: border-box; } -.theme--dark.v-application .v-data-table > .v-data-table__wrapper > table > thead > tr > th, -.theme--dark.v-application .v-data-table > .v-data-table__wrapper > table > tbody > tr > td { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-data-table > .v-data-table__wrapper > table > thead > tr > th, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-data-table > .v-data-table__wrapper > table > tbody > tr > td { color: var(--oc-text) !important; border-bottom-color: var(--oc-border) !important; } -.theme--dark.v-application .v-data-table > .v-data-table__wrapper > table > tbody > tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper), -.theme--dark.v-application .file-system .theme--dark.v-data-table tr > td.actions div { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-data-table > .v-data-table__wrapper > table > tbody > tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper), +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .file-system .theme--dark.v-data-table tr > td.actions div { background-color: var(--oc-surface-raised) !important; } -.theme--dark.v-application .file-system .theme--dark.v-data-table tr > td.actions div:before { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .file-system .theme--dark.v-data-table tr > td.actions div:before { background: linear-gradient(90deg, rgba(23, 23, 23, 0) 0%, var(--oc-surface-raised) 100%) !important; } -.theme--dark.v-application .v-text-field > .v-input__control > .v-input__slot, -.theme--dark.v-application .v-select > .v-input__control > .v-input__slot, -.theme--dark.v-application .v-input--selection-controls__input, -.theme--dark.v-application .v-slider__track-background { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-text-field > .v-input__control > .v-input__slot, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-select > .v-input__control > .v-input__slot, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-input--selection-controls__input, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-slider__track-background { background-color: #0d0d0d !important; border-color: var(--oc-border) !important; } -.theme--dark.v-application .v-input__slot:before, -.theme--dark.v-application .v-input__slot:after, -.theme--dark.v-application .v-divider { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-input__slot:before, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-input__slot:after, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-divider { border-color: var(--oc-border) !important; } -.theme--dark.v-application .v-progress-linear__background, -.theme--dark.v-application .v-slider__track-background { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-progress-linear__background, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-slider__track-background { background-color: #2a2a2a !important; } -.theme--dark.v-application .v-progress-linear__determinate, -.theme--dark.v-application .v-slider__track-fill, -.theme--dark.v-application .v-slider__thumb, -.theme--dark.v-application .v-tabs-slider { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-progress-linear__determinate, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-slider__track-fill, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-slider__thumb, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-tabs-slider { background-color: var(--oc-primary-muted) !important; border-color: var(--oc-primary-muted) !important; } -.theme--dark.v-application .v-navigation-drawer__border, -.theme--dark.v-application .v-toolbar--extended .v-toolbar__content, -.theme--dark.v-application .v-card:not(.collapsed) .v-card__title.collapsable-card-title { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-navigation-drawer__border, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-toolbar--extended .v-toolbar__content, +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .v-card:not(.collapsed) .v-card__title.collapsable-card-title { box-shadow: none !important; } -.theme--dark.v-application .background-logo { +[data-fluidd-theme="OpenCentauri"] .theme--dark.v-application .background-logo { width: min(38vw, 560px) !important; height: auto !important; opacity: 0.3 !important; filter: grayscale(1) invert(1) brightness(1.15) contrast(1.05) !important; } -.toolbar-logo .logo-wrapper, -.toolbar-logo .logo-wrapper svg { +[data-fluidd-theme="OpenCentauri"] .toolbar-logo .logo-wrapper, +[data-fluidd-theme="OpenCentauri"] .toolbar-logo .logo-wrapper svg { width: 56px; height: 56px; } -.theme--dark .toolbar-logo { +[data-fluidd-theme="OpenCentauri"] .theme--dark .toolbar-logo { background-color: var(--oc-bg) !important; border-right-color: var(--oc-border) !important; } -.toolbar-logo .logo-wrapper { +[data-fluidd-theme="OpenCentauri"] .toolbar-logo .logo-wrapper { background-image: url("./logo_opencentauri.svg"); background-repeat: no-repeat; background-position: center; @@ -243,18 +243,18 @@ filter: grayscale(1) invert(1) brightness(0.82); } -.toolbar-logo .logo-wrapper svg { +[data-fluidd-theme="OpenCentauri"] .toolbar-logo .logo-wrapper svg { display: block; opacity: 0; } -.printer-title { +[data-fluidd-theme="OpenCentauri"] .printer-title { display: flex; align-items: center; min-width: 128px; } -.printer-title > a { +[data-fluidd-theme="OpenCentauri"] .printer-title > a { display: block; width: min(240px, 42vw); height: 30px; @@ -269,12 +269,12 @@ } @media only screen and (max-width: 599px) { - .toolbar-title { + [data-fluidd-theme="OpenCentauri"] .toolbar-title { max-width: 72%; padding-left: 8px; } - .printer-title > a { + [data-fluidd-theme="OpenCentauri"] .printer-title > a { width: min(210px, 58vw); height: 24px; } From c077c10b567c3f66fb9009ce4c87515a5eafda09 Mon Sep 17 00:00:00 2001 From: Paul Swenson Date: Wed, 13 May 2026 22:52:02 -0400 Subject: [PATCH 5/7] Refine patcher: add favicon override, remove stale logo/icon patches, use logo-src theme detection --- .../apply-opencentauri-fluidd-theme.py | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py index 8dc8dd58..797fde1f 100644 --- a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py +++ b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py @@ -119,7 +119,6 @@ def patch_config(webroot): config["themePresets"] = presets # Set OpenCentauri as the default active theme for new users config["theme"] = { - "name": APP_NAME, "color": PRIMARY_COLOR, "isDark": True, "logo": { @@ -132,34 +131,26 @@ def patch_config(webroot): def patch_main_bundle(webroot): + """Patch the main JS bundle to add theme-aware dataset toggling.""" bundles = sorted((webroot / "assets").glob("index-*.js")) if len(bundles) != 1: raise RuntimeError(f"{webroot}: expected exactly one assets/index-*.js bundle, found {len(bundles)}") path = bundles[0] text = path.read_text() - text = replace_once( - text, - "get pageIcon(){let e=this.printInProgressIconDataUrl||this.defaultIconDataUrl;return[{rel:`icon`,type:`image/svg+xml`,sizes:`32x32`,href:e},{rel:`icon`,type:`image/svg+xml`,sizes:`16x16`,href:e}]}", - "get pageIcon(){let e=this.printInProgressIconDataUrl||this.defaultIconDataUrl,t=e.startsWith(`data:`)?`image/png`:`image/webp`;return[{rel:`icon`,type:t,sizes:`32x32`,href:e},{rel:`icon`,type:t,sizes:`16x16`,href:e}]}", - path, - ) - text, count = re.subn( - r"get defaultIconDataUrl\(\)\{let e=``;return`data:image/svg\+xml;base64,\$\{btoa\(e\)\}`\}", - f"get defaultIconDataUrl(){{return`./{ICON_SRC}`}}", - text, - count=1, - ) - if count != 1: - raise RuntimeError(f"{path}: expected defaultIconDataUrl implementation not found") - old_logo_src_getter = "get logoSrc(){return`./${this.theme.logo.src}`}" - if old_logo_src_getter not in text: - raise RuntimeError(f"{path}: expected logoSrc getter not found") - text = text.replace(old_logo_src_getter, f"get logoSrc(){{return`./{LOGO_SRC}`}}") - # Patch onThemeChange to toggle a data attribute for CSS scoping + + # Patch onThemeChange to toggle a data attribute for CSS scoping. + # We detect OpenCentauri by logo src (not name) because the name field + # persists across theme switches via Fluidd's object-merge in updateTheme. old_on_theme_change = "async onThemeChange(e,t){let n=Io.framework.theme;n.dark=t.isDark,n.currentTheme.primary=t.color,n.currentTheme[`primary-offset`]=new ft(t.color).desaturate(5).darken(10).toHexString(),n.themes.dark.logo=t.logo.light,n.themes.light.logo=t.logo.dark}" - new_on_theme_change = "async onThemeChange(e,t){let n=Io.framework.theme;n.dark=t.isDark,n.currentTheme.primary=t.color,n.currentTheme[`primary-offset`]=new ft(t.color).desaturate(5).darken(10).toHexString(),n.themes.dark.logo=t.logo.light,n.themes.light.logo=t.logo.dark;document.documentElement.dataset.fluiddTheme=t.name||\"\"}" + new_on_theme_change = 'async onThemeChange(e,t){let n=Io.framework.theme;n.dark=t.isDark,n.currentTheme.primary=t.color,n.currentTheme[`primary-offset`]=new ft(t.color).desaturate(5).darken(10).toHexString(),n.themes.dark.logo=t.logo.light,n.themes.light.logo=t.logo.dark;document.documentElement.dataset.fluiddTheme=(t.logo?.src===`logo_opencentauri.svg`)?`OpenCentauri`:``}' text = replace_once(text, old_on_theme_change, new_on_theme_change, path) + + # Override the default favicon globally (all themes show OpenCentauri favicon) + old_icon = 'get defaultIconDataUrl(){let e=``;return`data:image/svg+xml;base64,${btoa(e)}`}' + new_icon = f'get defaultIconDataUrl(){{return`./{ICON_SRC}`}}' + text = replace_once(text, old_icon, new_icon, path) + path.write_text(text) @@ -170,7 +161,7 @@ def patch_state_chunk(webroot): return # Not all builds have this chunk old_theme = "theme:{isDark:!0,logo:{src:`logo_fluidd.svg`},color:`#2196F3`,backgroundLogo:!0}" - new_theme = f"theme:{{name:`{APP_NAME}`,isDark:!0,logo:{{src:`{LOGO_SRC}`}},color:`{PRIMARY_COLOR}`,backgroundLogo:!0}}" + new_theme = f"theme:{{isDark:!0,logo:{{src:`{LOGO_SRC}`}},color:`{PRIMARY_COLOR}`,backgroundLogo:!0}}" for path in chunks: text = path.read_text() From 9a78a3f1d51afdb9b4947f7a9f8c566647dbfbe6 Mon Sep 17 00:00:00 2001 From: Paul Swenson Date: Wed, 13 May 2026 23:48:35 -0400 Subject: [PATCH 6/7] Switch favicon to carbon-logo-red.webp --- .../apply-opencentauri-fluidd-theme.py | 2 +- .../carbon-logo-red.webp | Bin 0 -> 1146 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100755 meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/carbon-logo-red.webp diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py index 797fde1f..04543ef4 100644 --- a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py +++ b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/apply-opencentauri-fluidd-theme.py @@ -9,7 +9,7 @@ DESCRIPTION = "OpenCentauri web interface for the Elegoo Centauri Carbon" PRIMARY_COLOR = "#D8DADC" LOGO_SRC = "logo_opencentauri.svg" -ICON_SRC = "opencentauri-icon-gray.webp" +ICON_SRC = "carbon-logo-red.webp" WORDMARK_SRC = "opencentauri-logo-small.png" THEME_CSS = "opencentauri-theme.css" diff --git a/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/carbon-logo-red.webp b/meta-opencentauri/recipes-apps/fluidd/files/opencentauri-fluidd-theme/carbon-logo-red.webp new file mode 100755 index 0000000000000000000000000000000000000000..68194f9a8a335f311c6ae6b225504e8d7aff899e GIT binary patch literal 1146 zcmV-=1cm!jNk&F;1ONb6MM6+kP&il$0000G0000#002J#06|PpNYMcR00EFJ$+l@* zvvF-c+qP}nwr$(CZQHhurfpwyJ?**X8tZ*PL`(qv|C41aSFT;VcEwyE0@ATkr7Kph zUb}8#>fcdk$5rx?3)p(~*|RvM)XZ@!*B(53^VusU8+UbPy#SfBO1jxMjep)<+ANvg zNzSldEP5vK_oKD#GVvC2+Lhy6rFK$(zuVf5(U0*hlgm;@HF7bbl1f8 zGPKhv6XPUNE5^I6VTQ&zQJj<7yEKYnz8>Ql13MdIuN_guoN32RPA5Coa!T3Jj}u}? zH4c5VB7}9*hEK%aXv0a~C>!ST8rjf^m(PYmJRci;c=QVI5ow;p+pDt>Z=y~=yhb{; z@N(&7f{P;UIJCQZ57E}@?L`};HxsRzUMI9_=q?FA!r3Y{5oeH8DV$nT^a>`G z*h-XdBwh{WqMSz<$U-^V3Nq6`E0Bu@0^%^DJmlykhAL*D7swz3sSLad7DH4L;oHY^>9!!(ggrkP&goZ0ssK; z4FH`1DnI~006uLhkwv5;p_u!Pv_J;LvH)F~%m^&SCAt-M>fOWo74)>Dg-JHH$E1e! zB_@K`nry>Kn0N84v?2FxBro}K6n$f5LcEQI*M;!CIOl^ioV0`z!Q0m#v^BGKVE$t+ zm?$OEI$&=XuX1elc{%_9{`nC8F9VNd=n01)R$*D!?WJPX*et)mDW;Dn&!ua#eWy&U zPXA@+zx)4>MOn+k42sjbHvM~ZAgRK3CExm1s=JBsQy31dlm>JE4TC4HH+ZDcR>xMT zufNa&bADOqYyyYSjyFkt^YugYRz>USuCtsys^RuM=^U?|d&erpQgg_PY5zRM#7Q9N?3br0Gp1%Q^fKo@X4U4M$|zj;>z5urLG#bow%6)%-r#HZ&YJw*|_-GA{Sv z#*KfWzLBS|%%MS&;9K!$G9D~l5ksayQ$4U+_84*=}9yQU!HwLJS= z;EDD$gT!~()Ftaydkp1v>948lwdBbJ3rN&k#Kvw*vMxA_<|!lD4fuNw-o-ml>d?fn zC|nrm*k;3-m5e@IxU1a`n}htm@fuT{U`jqjyCcL$hwowNlrMk)sR{#G;xQy5w#nXh z6%j6jFf^xfxN6nns#yuM_*u1TGtL(>ifUGVj|0xKkDNBsU)(DG;FRluO7bG!QyIly z`bk@=yn%-Bu7xOp*vihOuQ3f&)I9%VI(TeV5;`z@5v(Jb@b*?=%)tB&yvk|&b>!wC%w6Es?XWwa MJdoY Date: Thu, 14 May 2026 00:01:46 -0400 Subject: [PATCH 7/7] Fix recipe: install carbon-logo-red.webp instead of old gray icon --- meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb b/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb index 8979fdad..d0ee54c4 100644 --- a/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb +++ b/meta-opencentauri/recipes-apps/fluidd/fluidd_1.37.0.bb @@ -36,8 +36,7 @@ do_install() { cp -r ${S}/* ${D}/var/www/fluidd/ install -m 0644 \ ${WORKDIR}/opencentauri-fluidd-theme/logo_opencentauri.svg \ - ${WORKDIR}/opencentauri-fluidd-theme/opencentauri-icon.webp \ - ${WORKDIR}/opencentauri-fluidd-theme/opencentauri-icon-gray.webp \ + ${WORKDIR}/opencentauri-fluidd-theme/carbon-logo-red.webp \ ${WORKDIR}/opencentauri-fluidd-theme/opencentauri-logo-small.png \ ${WORKDIR}/opencentauri-fluidd-theme/opencentauri-theme.css \ ${D}/var/www/fluidd/