From 342112cd21a1beef5e68b070afd4013617b45413 Mon Sep 17 00:00:00 2001 From: Edwin Noorlander Date: Tue, 11 Aug 2026 16:57:50 +0200 Subject: [PATCH] Fix submenu arrow: flexbox centering, proper spacing - Use display: flex + align-items: center on dropdown-toggle - gap: 0.75rem between text and arrow - Remove float: right and margin-top hack - chevron-right: flex-shrink: 0, font-size: 0.7rem --- .gitignore | 3 + cms/core/class/ThemeManager.php | 25 +- themes/default/assets/css_compiled/.mtime | 1 - themes/default/assets/css_compiled/theme.css | 419 ------------------- themes/default/assets/scss/theme.scss | 15 +- themes/demo/assets/css_compiled/.mtime | 1 - themes/demo/assets/css_compiled/theme.css | 266 ------------ 7 files changed, 31 insertions(+), 699 deletions(-) delete mode 100644 themes/default/assets/css_compiled/.mtime delete mode 100644 themes/default/assets/css_compiled/theme.css delete mode 100644 themes/demo/assets/css_compiled/.mtime delete mode 100644 themes/demo/assets/css_compiled/theme.css diff --git a/.gitignore b/.gitignore index 1d63556..c929565 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ admin/storage/geoip/ admin/storage/stats.json var/ +# Runtime-compiled theme assets (generated by scssphp, read-only) +themes/*/assets/css_compiled/ + # Runtime-compiled theme assets public/themes/ diff --git a/cms/core/class/ThemeManager.php b/cms/core/class/ThemeManager.php index 6410d65..3a0c6cd 100644 --- a/cms/core/class/ThemeManager.php +++ b/cms/core/class/ThemeManager.php @@ -169,8 +169,22 @@ class ThemeManager { $compiler = new Compiler(); $compiler->setImportPaths($this->themeDir . '/assets/scss'); $css = $compiler->compileString(file_get_contents($scssFile))->getCss(); + + // Remove read-only before writing (file may be locked from previous compile) + if (is_file($outFile)) { + @chmod($outFile, 0644); + } + if (is_file($cacheFile)) { + @chmod($cacheFile, 0644); + } + file_put_contents($outFile, $css); file_put_contents($cacheFile, (string)$mtime); + + // Set read-only to prevent manual edits + @chmod($outFile, 0444); + @chmod($cacheFile, 0444); + return $outFile; } catch (\Throwable $e) { error_log('ThemeManager SCSS compile error: ' . $e->getMessage()); @@ -180,20 +194,15 @@ class ThemeManager { /** * Get the public URL for the theme CSS. - * Priority: 1) assets/css/theme.css (manual), 2) assets/css_compiled/theme.css (compiled), 3) null + * Uses assets/css_compiled/theme.css (compiled from SCSS by scssphp). */ public function getCssUrl(): ?string { - $manualCss = $this->themeDir . '/assets/css/theme.css'; $compiledCss = $this->themeDir . '/assets/css_compiled/theme.css'; - - if (is_file($manualCss)) { - return $this->getThemeAssetUrl('css/theme.css'); - } - + if (is_file($compiledCss)) { return $this->getThemeAssetUrl('css_compiled/theme.css'); } - + $compiled = $this->compileCss(); if ($compiled !== null) { return $this->getThemeAssetUrl('css_compiled/theme.css'); diff --git a/themes/default/assets/css_compiled/.mtime b/themes/default/assets/css_compiled/.mtime deleted file mode 100644 index d603719..0000000 --- a/themes/default/assets/css_compiled/.mtime +++ /dev/null @@ -1 +0,0 @@ -1786460110 \ No newline at end of file diff --git a/themes/default/assets/css_compiled/theme.css b/themes/default/assets/css_compiled/theme.css deleted file mode 100644 index 63d4371..0000000 --- a/themes/default/assets/css_compiled/theme.css +++ /dev/null @@ -1,419 +0,0 @@ -#site-header .search-input { - background-color: #ffffff !important; - color: #212529 !important; - border: 2px solid #0a369d !important; -} - -#site-header .search-input::placeholder { - color: #6c757d !important; -} - -#site-header .search-input:focus { - background-color: #ffffff !important; - color: #212529 !important; - border-color: #2754b4 !important; - box-shadow: 0 0 0 0.2rem rgba(10, 54, 157, 0.25) !important; -} - -:root { - --header-bg: #0a369d; - --header-font: #ffffff; - --header-height: 56px; - --nav-bg: #2754b4; - --nav-font: #ffffff; - --nav-height: 42px; - --sidebar-bg: #f8f9fa; - --sidebar-border: #dee2e6; -} - -#site-header { - background-image: none; - background-size: cover; - background-position: center; - background-repeat: no-repeat; - position: relative; -} - -.navbar { - background-color: var(--header-bg) !important; - min-height: var(--header-height); -} -.navbar .navbar-brand, -.navbar .navbar-text, -.navbar .btn { - color: var(--header-font) !important; -} -.navbar .form-control { - color: var(--header-font) !important; -} -.navbar .form-control::placeholder { - color: rgba(255, 255, 255, 0.7) !important; -} -.navbar .search-input { - background-color: #ffffff !important; - color: #212529 !important; - border: 2px solid #0a369d !important; -} -.navbar .search-input::placeholder { - color: #6c757d !important; - opacity: 1 !important; -} -.navbar .search-input:focus { - background-color: #ffffff !important; - color: #212529 !important; - border-color: #2754b4 !important; - box-shadow: 0 0 0 0.2rem rgba(10, 54, 157, 0.25) !important; -} -.navbar .btn-outline-light { - border-color: var(--header-font) !important; -} - -.dropdown-menu { - --bs-dropdown-min-width: 0; - --bs-dropdown-padding-x: 0; - --bs-dropdown-padding-y: 0; - --bs-dropdown-border-width: 0; - --bs-dropdown-border-radius: 0; - --bs-dropdown-box-shadow: none; - background-color: var(--header-bg) !important; - border: none !important; - border-radius: 0 !important; - padding: 0 !important; - margin: 0 !important; - min-width: 0 !important; - box-shadow: none !important; - width: max-content; -} - -.dropdown-item { - color: var(--header-font) !important; - border-radius: 0 !important; - padding: 0.4rem 1rem; - white-space: nowrap; - display: block; - width: 100%; -} -.dropdown-item:hover, .dropdown-item:focus { - background-color: rgba(255, 255, 255, 0.1) !important; - color: var(--header-font) !important; -} - -.nav-tabs .dropdown-menu { - margin-top: 0 !important; -} - -.dropdown-toggle::after { - display: none !important; -} - -.btn-outline-light { - color: var(--header-font) !important; - border-color: var(--header-font) !important; -} -.btn-outline-light:hover { - background-color: rgba(255, 255, 255, 0.1) !important; - color: var(--header-font) !important; -} -.btn-outline-light:focus, .btn-outline-light:active, .show > .btn-outline-light.dropdown-toggle { - background-color: rgba(255, 255, 255, 0.1) !important; - color: var(--header-font) !important; - border-color: var(--header-font) !important; - box-shadow: none !important; -} - -.bi-chevron-down { - font-size: 0.75em; - margin-left: 0.25rem; -} - -.breadcrumb-item + .breadcrumb-item::before { - content: "" !important; - padding: 0 !important; -} - -.breadcrumb { - --bs-breadcrumb-divider: ""; - --bs-breadcrumb-margin-bottom: 0; - margin-bottom: 0; -} -.breadcrumb .breadcrumb-item { - color: var(--header-bg) !important; -} -.breadcrumb .breadcrumb-item a { - color: var(--header-bg) !important; - text-decoration: none; -} -.breadcrumb .breadcrumb-item a:hover { - text-decoration: underline; -} - -.sidebar-toggle-item { - display: flex; - align-items: center; - margin-right: 0.5rem; -} - -.sidebar-toggle-btn { - padding: 0; - line-height: 1; - font-size: 1.1rem; - color: var(--header-bg) !important; - border: none !important; - background: transparent !important; - box-shadow: none !important; - cursor: pointer; -} -.sidebar-toggle-btn:hover { - opacity: 0.7; -} - -.sidebar-column { - transition: all 0.3s ease; -} - -.sidebar-hidden { - display: none !important; -} - -.navigation-section { - background-color: var(--nav-bg) !important; - color: var(--nav-font) !important; - min-height: var(--nav-height); -} - -.focus-visible:focus, -.btn:focus, -.form-control:focus { - outline: 3px solid #0056b3 !important; - outline-offset: 2px !important; - box-shadow: 0 0 0 1px #ffffff, 0 0 0 4px #0056b3 !important; -} - -@media (prefers-contrast: high) { - :root { - --text-color: #000000; - --bg-color: #ffffff; - --border-color: #000000; - --focus-color: #000000; - } - .btn-primary { - background-color: #000000 !important; - border-color: #000000 !important; - color: #ffffff !important; - } - .btn-outline-light { - color: #000000 !important; - border-color: #000000 !important; - } - .text-muted { - color: #000000 !important; - } - .navbar { - background-color: #ffffff !important; - border-bottom: 1px solid #000000 !important; - } -} -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} -.nav-tabs { - --bs-nav-tabs-border-width: 0 !important; - --bs-nav-tabs-border-color: transparent !important; - --bs-nav-tabs-border-radius: 0 !important; - --bs-nav-tabs-link-hover-border-color: transparent !important; - --bs-nav-tabs-link-active-color: var(--nav-font) !important; - --bs-nav-tabs-link-active-bg: transparent !important; - --bs-nav-tabs-link-active-border-color: transparent !important; - background-color: transparent !important; - border: none !important; - border-bottom: none !important; - border-radius: 0 !important; - gap: 0 !important; -} -.nav-tabs .nav-link { - margin-bottom: 0 !important; - background-color: transparent !important; - border: none !important; - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; - border-bottom-left-radius: 0 !important; - border-bottom-right-radius: 0 !important; - color: var(--nav-font) !important; - outline: none !important; - box-shadow: none !important; -} -.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus, .nav-tabs .nav-link:focus-visible { - isolation: auto; - background-color: rgba(255, 255, 255, 0.1) !important; - border: none !important; - border-color: transparent !important; - outline: none !important; - box-shadow: none !important; -} -.nav-tabs .nav-link.active { - background-color: rgba(255, 255, 255, 0.2) !important; - border: none !important; - border-color: transparent !important; - border-bottom: 2px solid var(--nav-font) !important; - outline: none !important; - box-shadow: none !important; -} -.nav-tabs .dropdown-menu { - margin-top: 0 !important; - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; -} - -.dropdown-submenu { - position: relative; -} -.dropdown-submenu > .dropdown-menu { - top: 0; - left: 100%; - margin-top: 0; - margin-left: 0; - display: none; - --bs-dropdown-border-width: 0; - --bs-dropdown-border-radius: 0; - --bs-dropdown-box-shadow: none; - border: none !important; - border-radius: 0 !important; - box-shadow: none !important; - width: max-content; -} -.dropdown-submenu:hover > .dropdown-menu { - display: block; -} -.dropdown-submenu:hover > .dropdown-toggle { - background-color: rgba(255, 255, 255, 0.15) !important; -} -.dropdown-submenu > .dropdown-toggle .bi-chevron-right { - float: right; - margin-top: 0.25rem; -} - -@media (max-width: 767.98px) { - .dropdown-submenu > .dropdown-menu { - left: 0; - top: 100%; - margin-top: 0; - margin-left: 1rem; - border: none !important; - border-radius: 0 !important; - box-shadow: none; - } - .dropdown-submenu:hover > .dropdown-menu { - display: none; - } - .dropdown-submenu.show > .dropdown-menu { - display: block; - } -} -.sidebar-column { - background-color: var(--sidebar-bg) !important; - border-right: 1px solid var(--sidebar-border) !important; - position: sticky; - top: 0; - min-height: calc(100vh - var(--header-height) - var(--nav-height) - 42px); -} - -.sidebar { - padding: 1.5rem; - height: 100%; - overflow-y: auto; -} - -.content-column { - background-color: #ffffff; -} - -.content-wrapper { - padding: 2rem; - padding-bottom: 80px !important; -} - -.main-content { - flex: 1; - min-height: auto; -} - -@media (max-width: 767.98px) { - .sidebar-column { - border-right: none !important; - border-top: 1px solid var(--sidebar-border) !important; - min-height: auto; - margin-top: 1rem; - } - .content-column { - background-color: #ffffff; - } - .content-wrapper { - min-height: auto; - padding-bottom: 2rem !important; - } -} -@media (max-width: 991.98px) { - .sidebar-column { - order: 2 !important; - } - .content-column { - order: 1 !important; - } -} -pre { - background: #f8f9fa; - padding: 1rem; - border-radius: 6px; - overflow-x: auto; - border: 1px solid #dee2e6; - margin-bottom: 1rem; -} -pre code { - background: none; - padding: 0; - color: #333; - font-size: 0.85rem; - line-height: 1.5; -} - -code { - background: #e8e8e8; - padding: 0.15rem 0.4rem; - border-radius: 3px; - font-size: 0.9em; - color: #d63384; -} - -.footer-icon { - color: #6c757d; - text-decoration: none; - transition: all 0.2s ease-in-out; - display: inline-block; - padding: 2px; -} -.footer-icon:hover { - color: #0d6efd; - transform: translateY(-1px); -} -.footer-icon:active { - transform: translateY(0); -} -.footer-icon.guide:hover { - color: #198754; -} -.footer-icon.cms:hover { - color: #dc3545; -} -.footer-icon.git:hover { - color: #6f42c1; -} -.footer-icon.website:hover { - color: #fd7e14; -} \ No newline at end of file diff --git a/themes/default/assets/scss/theme.scss b/themes/default/assets/scss/theme.scss index 45cc522..f9c4cc3 100644 --- a/themes/default/assets/scss/theme.scss +++ b/themes/default/assets/scss/theme.scss @@ -369,10 +369,17 @@ $header-bg-opacity: 1; background-color: rgba(255, 255, 255, 0.15) !important; } - // Arrow pointing right - > .dropdown-toggle .bi-chevron-right { - float: right; - margin-top: 0.25rem; + // Arrow pointing right — vertically centered with text + > .dropdown-toggle { + display: flex !important; + justify-content: space-between; + align-items: center; + gap: 0.75rem; + + .bi-chevron-right { + flex-shrink: 0; + font-size: 0.7rem; + } } } diff --git a/themes/demo/assets/css_compiled/.mtime b/themes/demo/assets/css_compiled/.mtime deleted file mode 100644 index d874a8f..0000000 --- a/themes/demo/assets/css_compiled/.mtime +++ /dev/null @@ -1 +0,0 @@ -1786274744 \ No newline at end of file diff --git a/themes/demo/assets/css_compiled/theme.css b/themes/demo/assets/css_compiled/theme.css deleted file mode 100644 index 6cc10fd..0000000 --- a/themes/demo/assets/css_compiled/theme.css +++ /dev/null @@ -1,266 +0,0 @@ -:root { - --header-bg: #613583; - --header-font: #ffffff; - --header-height: 120px; - --nav-bg: #813d9c; - --nav-font: #ffffff; - --nav-height: 50px; - --sidebar-bg: #f3e8f7; - --sidebar-border: #d8b4e0; -} - -#site-header { - background-image: none; - background-size: cover; - background-position: center; - background-repeat: no-repeat; - position: relative; -} - -.navbar { - background-color: var(--header-bg) !important; - min-height: var(--header-height); - border-bottom: 4px solid var(--nav-bg) !important; -} -.navbar .navbar-brand, -.navbar .navbar-text, -.navbar .form-control, -.navbar .btn { - color: var(--header-font) !important; -} -.navbar .form-control::placeholder { - color: rgba(255, 255, 255, 0.7) !important; -} -.navbar .btn-outline-light { - border-color: var(--header-font) !important; -} - -.dropdown-menu { - background-color: var(--header-bg) !important; - border: 1px solid var(--header-font) !important; -} - -.dropdown-item { - color: var(--header-font) !important; -} -.dropdown-item:hover { - background-color: rgba(255, 255, 255, 0.1) !important; - color: var(--header-font) !important; -} - -.dropdown-toggle::after { - display: none !important; -} - -.btn-outline-light { - color: var(--header-font) !important; - border-color: var(--header-font) !important; -} -.btn-outline-light:hover { - background-color: rgba(255, 255, 255, 0.1) !important; - color: var(--header-font) !important; -} -.btn-outline-light:focus, .btn-outline-light:active, .show > .btn-outline-light.dropdown-toggle { - background-color: rgba(255, 255, 255, 0.1) !important; - color: var(--header-font) !important; - border-color: var(--header-font) !important; - box-shadow: none !important; -} - -.bi-chevron-down { - font-size: 0.75em; - margin-left: 0.25rem; -} - -.breadcrumb-item + .breadcrumb-item::before { - content: "" !important; - padding: 0 !important; -} - -.breadcrumb { - --bs-breadcrumb-divider: ""; - --bs-breadcrumb-margin-bottom: 0; - margin-bottom: 0; -} -.breadcrumb .breadcrumb-item { - color: var(--header-bg) !important; -} -.breadcrumb .breadcrumb-item a { - color: var(--header-bg) !important; - text-decoration: none; -} -.breadcrumb .breadcrumb-item a:hover { - text-decoration: underline; -} - -.sidebar-toggle-item { - display: flex; - align-items: center; - margin-right: 0.5rem; -} - -.sidebar-toggle-btn { - padding: 0; - line-height: 1; - font-size: 1.1rem; - color: var(--header-bg) !important; - border: none !important; - background: transparent !important; - box-shadow: none !important; - cursor: pointer; -} -.sidebar-toggle-btn:hover { - opacity: 0.7; -} - -.sidebar-column { - transition: all 0.3s ease; -} - -.sidebar-hidden { - display: none !important; -} - -.navigation-section { - background-color: var(--nav-bg) !important; - color: var(--nav-font) !important; - min-height: var(--nav-height); -} - -.focus-visible:focus, -.btn:focus, -.form-control:focus, -.nav-link:focus { - outline: 3px solid #0056b3 !important; - outline-offset: 2px !important; - box-shadow: 0 0 0 1px #ffffff, 0 0 0 4px #0056b3 !important; -} - -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} -.nav-tabs { - background-color: transparent !important; - border: none !important; -} -.nav-tabs .nav-link { - background-color: transparent !important; - border: none !important; - color: var(--nav-font) !important; -} -.nav-tabs .nav-link:hover { - background-color: rgba(255, 255, 255, 0.1) !important; -} -.nav-tabs .nav-link.active { - background-color: rgba(255, 255, 255, 0.2) !important; - border-bottom: 2px solid var(--nav-font) !important; -} - -.sidebar-column { - background-color: var(--sidebar-bg) !important; - border-right: 1px solid var(--sidebar-border) !important; - position: sticky; - top: 0; - min-height: calc(100vh - var(--header-height) - var(--nav-height) - 42px); -} - -.sidebar { - padding: 1.5rem; - height: 100%; - overflow-y: auto; -} -.sidebar .card { - border-radius: 1rem; - border: 1px solid var(--sidebar-border); -} - -.content-column { - background-color: #ffffff; -} - -.content-wrapper { - padding: 2rem; - padding-bottom: 80px !important; -} - -.main-content { - flex: 1; -} - -@media (max-width: 767.98px) { - .sidebar-column { - border-right: none !important; - border-top: 1px solid var(--sidebar-border) !important; - min-height: auto; - margin-top: 1rem; - } - .content-wrapper { - min-height: auto; - padding-bottom: 2rem !important; - } -} -@media (max-width: 991.98px) { - .sidebar-column { - order: 2 !important; - } - .content-column { - order: 1 !important; - } -} -pre { - background: #f8f9fa; - padding: 1rem; - border-radius: 6px; - overflow-x: auto; - border: 1px solid #dee2e6; - margin-bottom: 1rem; -} -pre code { - background: none; - padding: 0; - color: #333; - font-size: 0.85rem; - line-height: 1.5; -} - -code { - background: #e8e8e8; - padding: 0.15rem 0.4rem; - border-radius: 3px; - font-size: 0.9em; - color: #d63384; -} - -.footer-icon { - color: #6c757d; - text-decoration: none; - transition: all 0.2s ease-in-out; - display: inline-block; - padding: 2px; -} -.footer-icon:hover { - color: #0d6efd; - transform: translateY(-1px); -} -.footer-icon:active { - transform: translateY(0); -} -.footer-icon.guide:hover { - color: #198754; -} -.footer-icon.cms:hover { - color: #dc3545; -} -.footer-icon.git:hover { - color: #6f42c1; -} -.footer-icon.website:hover { - color: #fd7e14; -} \ No newline at end of file