From 9daf2559ec863cfced4c9469401ee2d137d5f11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ervin=20R=C3=A1cz?= Date: Tue, 21 Jul 2026 15:35:48 +0300 Subject: [PATCH 01/10] feat(theme): add dark mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adds a dark/light toggle that remembers your choice, plus dark styles for most of the site. - theme switcher js + partial, wired into head/baseof - dark scss palette + dark syntax highlighting - dark styles for navbar, footer, search, blog, docs, product, careers, releases and 404 - a11y: focus/selected colors, contrast, mobile toggle focus - dropped an unused static/js/common.js and fixed a dropdown null check Signed-off-by: Ervin Rácz --- config.yaml | 6 +- content/blog/_index.md | 7 + static/js/common.js | 199 ------------------ themes/flatcar/assets/js/common.js | 17 +- themes/flatcar/assets/js/theme-switcher.js | 144 +++++++++++++ themes/flatcar/assets/scss/_custom.scss | 36 ++++ themes/flatcar/assets/scss/_styles.scss | 35 ++- themes/flatcar/assets/scss/_syntax-dark.scss | 83 ++++++++ .../assets/scss/_syntax-dark.scss.parked | 99 --------- themes/flatcar/assets/scss/_syntax.scss | 9 +- themes/flatcar/assets/scss/buttons.scss | 33 +-- themes/flatcar/assets/scss/layouts.scss | 8 +- themes/flatcar/assets/scss/main.scss | 2 + themes/flatcar/assets/scss/pages/article.scss | 24 ++- .../flatcar/assets/scss/pages/blog-page.scss | 58 ++--- .../flatcar/assets/scss/partials/footer.scss | 12 ++ .../assets/scss/partials/grid-statements.scss | 4 +- .../flatcar/assets/scss/partials/header.scss | 4 + themes/flatcar/assets/scss/partials/hero.scss | 9 + .../flatcar/assets/scss/partials/navbar.scss | 140 +++++++++++- themes/flatcar/assets/scss/search.scss | 60 +++--- .../scss/sections/section-contact-us.scss | 12 +- .../assets/scss/sections/section-docs.scss | 89 +++++--- .../assets/scss/sections/section-product.scss | 123 ++++++----- .../scss/sections/section-related-post.scss | 8 +- themes/flatcar/layouts/404.html | 36 ++-- themes/flatcar/layouts/_default/baseof.html | 30 +-- themes/flatcar/layouts/blog/baseof.html | 20 ++ themes/flatcar/layouts/blog/single.html | 8 + themes/flatcar/layouts/careers/single.html | 36 ++-- themes/flatcar/layouts/docs/baseof.html | 7 +- .../flatcar/layouts/docs/docsportal_home.html | 124 +++++------ .../layouts/partials/announcement_banner.html | 2 +- themes/flatcar/layouts/partials/article.html | 11 +- themes/flatcar/layouts/partials/footer.html | 5 +- themes/flatcar/layouts/partials/head.html | 35 ++- themes/flatcar/layouts/partials/hero.html | 5 +- themes/flatcar/layouts/partials/navbar.html | 35 +++ .../flatcar/layouts/partials/theme-mode.html | 44 ++++ themes/flatcar/layouts/section/blog.html | 105 +++++---- themes/flatcar/layouts/section/releases.html | 8 +- themes/flatcar/layouts/taxonomy/tag.html | 47 ++--- 42 files changed, 1067 insertions(+), 712 deletions(-) create mode 100644 content/blog/_index.md delete mode 100644 static/js/common.js create mode 100644 themes/flatcar/assets/js/theme-switcher.js create mode 100644 themes/flatcar/assets/scss/_syntax-dark.scss delete mode 100644 themes/flatcar/assets/scss/_syntax-dark.scss.parked create mode 100644 themes/flatcar/layouts/blog/baseof.html create mode 100644 themes/flatcar/layouts/partials/theme-mode.html diff --git a/config.yaml b/config.yaml index 219624feb..1bc13f268 100644 --- a/config.yaml +++ b/config.yaml @@ -61,8 +61,8 @@ markup: # noClasses is false — Hugo/Chroma only emits semantic CSS classes # (.chroma .k, .s, .c, ...) and the actual colors come from the # hand-written Flatcar light theme in - # themes/flatcar/assets/scss/_syntax.scss. A dark variant is - # parked in _syntax-dark.scss.parked for future dark-mode work. + # themes/flatcar/assets/scss/_syntax.scss, with the dark variant in + # _syntax-dark.scss (scoped under [data-bs-theme="dark"]). style: github guessSyntax: true noClasses: false @@ -98,7 +98,7 @@ menu: identifier: releases weight: -400 - name: Docs - url: /docs + url: /docs/latest identifier: docs weight: -200 - name: GitHub diff --git a/content/blog/_index.md b/content/blog/_index.md new file mode 100644 index 000000000..6bce1a760 --- /dev/null +++ b/content/blog/_index.md @@ -0,0 +1,7 @@ +--- +title: Blog +hero: + merge: true + description: "" + buttons: [] +--- diff --git a/static/js/common.js b/static/js/common.js deleted file mode 100644 index 3aa50390a..000000000 --- a/static/js/common.js +++ /dev/null @@ -1,199 +0,0 @@ -// Mobile menu toggle -document.querySelector('.mobile-menu')?.addEventListener('click', function(e) { - document.body.classList.toggle('mobile-menu_open'); -}); - -// Docs menu toggle -document.querySelector('.docs-menu')?.addEventListener('click', function(e) { - document.body.classList.toggle('show'); -}); - -function closeMenu(elem) { - if(document.body.classList.contains('mobile-menu_open')) { - if(!elem.classList.contains('dropdown-toggle')) { - document.querySelector('.mobile-menu')?.click(); - } - } -} - -// Dropdown navigation for mobile -document.querySelectorAll(".nav-item.dropdown .nav-link").forEach(function(item) { - item.addEventListener("click", function(e) { - e.preventDefault(); - if(document.body.classList.contains('mobile-menu_open')) { - if(!this.classList.contains('nav-link_selected')) { - document.querySelectorAll('.nav-link').forEach(function(elem) { - elem.classList.remove('nav-link_selected'); - }); - this.classList.add('nav-link_selected'); - } else { - this.classList.remove('nav-link_selected'); - } - } - }); -}); - -// Dropdown focus handling for desktop -document.querySelectorAll(".nav-item.dropdown .nav-link").forEach(function(item) { - if(!document.body.classList.contains('mobile-menu_open')) { - item.addEventListener("focus", function(e) { - item.parentElement.classList.add("focus"); - }); - - // This logic is based on https://www.a11ywithlindsey.com/blog/create-accessible-dropdown-navigation - item.parentElement.querySelectorAll(".dropdown-menu").forEach(function(dropdown) { - const subMenuLinks = dropdown.querySelectorAll('a') - const lastLink = subMenuLinks[subMenuLinks.length - 1] - - lastLink.addEventListener('blur', function() { - item.parentElement.classList.remove('focus') - }) - }); - } -}); - -// Close menu on nav/dropdown click -[".nav-link", ".dropdown-item"].forEach(className => - document.querySelectorAll(className).forEach(function(item) { - item.addEventListener("click", function(e) { - closeMenu(this); - }) - }) -); - - -// Cookies - -function getCookie(cname) { - var name = cname + "="; - var decodedCookie = decodeURIComponent(document.cookie); - var ca = decodedCookie.split(';'); - for(var i = 0; i - var codeTd = block.querySelector('.lntd:last-child code'); - var code = codeTd || block.querySelector('code'); - if (!code) return; - navigator.clipboard.writeText(code.textContent).then(function() { - btn.innerHTML = ''; - setTimeout(function() { - btn.innerHTML = ''; - }, 2000); - }); - }); -}); - - -// TOC scroll tracking - highlight active section in "On This Page" -(function() { - var toc = document.getElementById('TableOfContents'); - if (!toc) return; - - var tocLinks = toc.querySelectorAll('a'); - if (!tocLinks.length) return; - - var sections = []; - var linkMap = {}; - var clickLockUntil = 0; - - tocLinks.forEach(function(link) { - var href = link.getAttribute('href'); - if (!href || href.charAt(0) !== '#') return; - var id = decodeURIComponent(href.substring(1)); - var target = document.getElementById(id); - if (target) { - sections.push({ el: target, li: link.parentElement, id: id }); - linkMap[id] = link.parentElement; - } - }); - - function clearActive() { - sections.forEach(function(s) { - s.li.classList.remove('active'); - }); - } - - function setActiveById(id) { - clearActive(); - if (linkMap[id]) { - linkMap[id].classList.add('active'); - } - } - - function updateActiveByScroll() { - // Skip scroll updates briefly after a click to prevent override - if (Date.now() < clickLockUntil) return; - - var scrollPos = window.scrollY + 100; - var activeLi = null; - - for (var i = 0; i < sections.length; i++) { - if (sections[i].el.offsetTop <= scrollPos) { - activeLi = sections[i].li; - } - } - - clearActive(); - if (activeLi) { - activeLi.classList.add('active'); - } - } - - function updateFromHash() { - var hash = window.location.hash; - if (hash) { - var id = decodeURIComponent(hash.substring(1)); - if (linkMap[id]) { - setActiveById(id); - return true; - } - } - return false; - } - - // Click on TOC links: highlight immediately, suppress scroll override - tocLinks.forEach(function(link) { - link.addEventListener('click', function() { - var href = link.getAttribute('href'); - if (!href || href.charAt(0) !== '#') return; - var id = decodeURIComponent(href.substring(1)); - clickLockUntil = Date.now() + 300; - setActiveById(id); - }); - }); - - // Hash change (back/forward navigation) - window.addEventListener('hashchange', function() { - updateFromHash(); - }); - - // Scroll tracking - window.addEventListener('scroll', updateActiveByScroll, { passive: true }); - - // Initial state: use hash if present, otherwise use scroll position - if (!updateFromHash()) { - updateActiveByScroll(); - } -})(); - -document.querySelectorAll(".contact-cookies-consent-notice").forEach( - function (item) { - if (getCookie("cookieconsent_status") !== "allow") { - item.classList.remove("d-none"); - } - } -); \ No newline at end of file diff --git a/themes/flatcar/assets/js/common.js b/themes/flatcar/assets/js/common.js index 874728e6e..5cc239cff 100644 --- a/themes/flatcar/assets/js/common.js +++ b/themes/flatcar/assets/js/common.js @@ -19,6 +19,10 @@ function closeMenu(elem) { // Dropdown navigation for mobile document.querySelectorAll(".nav-item.dropdown .nav-link").forEach(function(item) { item.addEventListener("click", function(e) { + // Skip theme dropdown - it has its own handling + if (this.classList.contains('theme-switcher__toggle')) { + return; + } e.preventDefault(); if(document.body.classList.contains('mobile-menu_open')) { if(!this.classList.contains('nav-link_selected')) { @@ -52,12 +56,13 @@ document.querySelectorAll(".nav-item.dropdown .nav-link").forEach(function(item) } }); -// Close menu on link clicks +// Close menu on nav/dropdown click [".nav-link", ".dropdown-item"].forEach(className => document.querySelectorAll(className).forEach(function(item) { item.addEventListener("click", function(e) { + if (this.closest('.theme-switcher__menu')) return; closeMenu(this); - }); + }) }) ); @@ -68,7 +73,7 @@ function getCookie(cname) { var name = cname + "="; var decodedCookie = decodeURIComponent(document.cookie); var ca = decodedCookie.split(';'); - for(var i = 0; i < ca.length; i++) { + for(var i = 0; i + var codeTd = block.querySelector('.lntd:last-child code'); + var code = codeTd || block.querySelector('code'); if (!code) return; navigator.clipboard.writeText(code.textContent).then(function() { btn.innerHTML = ''; @@ -94,6 +102,7 @@ document.querySelectorAll('.code-block .btn-copy').forEach(function(btn) { }); }); + // TOC scroll tracking - highlight active section in "On This Page" (function() { var toc = document.getElementById('TableOfContents'); diff --git a/themes/flatcar/assets/js/theme-switcher.js b/themes/flatcar/assets/js/theme-switcher.js new file mode 100644 index 000000000..df6c3b1d4 --- /dev/null +++ b/themes/flatcar/assets/js/theme-switcher.js @@ -0,0 +1,144 @@ +// Dark mode theme switcher – three modes: auto · light · dark. +// The localStorage key and mode values must match the inline FOUC-prevention +// script emitted by theme-mode.html (called with "head" part). + +(function () { + var KEY = "theme-mode"; + var AUTO = "auto", + LIGHT = "light", + DARK = "dark"; + + var ICONS = { auto: "auto-icon", light: "sun-icon", dark: "moon-icon" }; + + function stored() { + try { + return localStorage.getItem(KEY) || AUTO; + } catch (e) { + return AUTO; + } + } + function save(mode) { + try { + localStorage.setItem(KEY, mode); + } catch (e) { + /* storage unavailable */ + } + } + function system() { + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? DARK + : LIGHT; + } + function resolve(m) { + return m === AUTO ? system() : m; + } + + function forcedTheme() { + var f = document.body && document.body.dataset.themeForce; + return f || null; + } + + function updateUI(mode) { + var theme = resolve(mode); + + // On pages with data-theme-force (e.g. landing page), don't touch the + // data-bs-theme attribute on at all — leave it exactly as the + // server rendered it. The icon and checkmark still reflect the user's + // actual choice so the preference is visible and saved for other pages. + if (!forcedTheme()) { + document.documentElement.setAttribute("data-bs-theme", theme); + } + + var btn = document.querySelector(".theme-switcher__toggle"); + if (btn) { + var use = btn.querySelector(".theme-switcher__current-icon use"); + if (use) { + var iconRef = "#" + ICONS[mode]; + use.setAttributeNS( + "http://www.w3.org/1999/xlink", + "xlink:href", + iconRef, + ); + use.setAttribute("href", iconRef); + } + } + + document.querySelectorAll(".theme-switcher__option").forEach(function (el) { + var isActive = el.getAttribute("data-theme-mode") === mode; + var cm = el.querySelector(".theme-switcher__checkmark"); + if (cm) cm.style.visibility = isActive ? "visible" : "hidden"; + if (isActive) { + el.setAttribute("aria-current", "true"); + } else { + el.removeAttribute("aria-current"); + } + }); + } + + function apply(mode) { + save(mode); + updateUI(mode); + } + + function select(mode) { + apply(mode); + var t = document.querySelector(".theme-switcher__toggle"); + if (t && window.bootstrap) { + var d = bootstrap.Dropdown.getInstance(t); + if (d) d.hide(); + } + } + + function init() { + var menu = document.querySelector(".theme-switcher__menu"); + if (!menu) return; + + updateUI(stored()); + + menu.addEventListener("click", function (e) { + var opt = e.target.closest(".theme-switcher__option"); + if (opt) { + e.preventDefault(); + select(opt.getAttribute("data-theme-mode")); + } + }); + + // Accessibility: on narrow viewports the toggle button is hidden with + // display:none while the dropdown is open (see navbar.scss). Hiding the + // focused element drops focus to , breaking arrow-key roving and + // Escape-to-close. Move focus into the menu when it opens while the + // toggle is hidden, and restore it to the toggle once it closes. + var toggle = document.querySelector(".theme-switcher__toggle"); + if (toggle) { + var toggleHidden = function () { + return getComputedStyle(toggle).display === "none"; + }; + toggle.addEventListener("shown.bs.dropdown", function () { + if (!toggleHidden()) return; + var target = + menu.querySelector('.theme-switcher__option[aria-current="true"]') || + menu.querySelector(".theme-switcher__option"); + if (target) target.focus(); + }); + toggle.addEventListener("hidden.bs.dropdown", function () { + if (toggleHidden()) return; + var active = document.activeElement; + if (!active || active === document.body || menu.contains(active)) { + toggle.focus(); + } + }); + } + + window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", function () { + if (stored() === AUTO) updateUI(AUTO); + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/themes/flatcar/assets/scss/_custom.scss b/themes/flatcar/assets/scss/_custom.scss index 13eb59386..2eed5859a 100644 --- a/themes/flatcar/assets/scss/_custom.scss +++ b/themes/flatcar/assets/scss/_custom.scss @@ -280,6 +280,42 @@ $pagination-active-color: $white; $pagination-active-bg: $green; $pagination-disabled-bg: $gray !important; +// Dark surface scale — Flatcar brand navy (from PR #640 / John15321). +// Built on $flatcar-navy (#12172b, the brand's "primary dark navy" for +// site backgrounds/headers) and $flatcar-navy-light (#1a2d3e), with the +// intermediate #1c2238 from the branded dark syntax theme. Keeping the +// whole dark UI on this one navy family (rather than a separate blue-black +// scale) so surfaces, cards and code blocks share the brand navy. +$flatcar-dark-900: #12172b; // brand navy — site/body background & code +$flatcar-dark-800: #1c2238; // card / elevated surface — deeper navy +$flatcar-dark-700: #1a2d3e; // navy-light — dropdowns / raised surfaces +$flatcar-dark-500: #2d3a4d; // borders, dividers — desaturated navy slate + +// Bootstrap dark-mode overrides (must precede @import "bootstrap") +// +// Design rationale: +// • Body text is a cool off-white (#e2e8f0) — avoids the warm cast of +// pure #e5e5e5 which fights the blue surfaces. +// • Secondary / tertiary colors use the tinted scale so cards, sidebars, +// and code blocks all breathe the same brand-navy atmosphere. +// • Border colors use the slate tones with translucency so they glow +// subtly rather than cutting hard lines. +// • Link accent stays $flatcar-blue but hover shifts to a brighter, +// slightly warmer cyan (#22d3ee) for a satisfying "pop" on interaction. + +$body-bg-dark: $flatcar-dark-900; +$body-color-dark: #e2e8f0; // cool off-white (Tailwind slate-200) +$body-secondary-color-dark: rgba($body-color-dark, .70); +$body-secondary-bg-dark: $flatcar-dark-700; +$body-tertiary-color-dark: rgba($body-color-dark, .45); +$body-tertiary-bg-dark: $flatcar-dark-800; +$body-emphasis-color-dark: #f1f5f9; // near-white with blue tint (slate-100) +$border-color-dark: $flatcar-dark-500; +$border-color-translucent-dark: rgba(148, 180, 214, .12); // blue-tinted translucency +$headings-color-dark: #f1f5f9; +$link-color-dark: $flatcar-blue; +$link-hover-color-dark: #22d3ee; // bright cyan — Tailwind cyan-400 + // Let's start by bring in all of bootstrap // Later we can bring in only what we need: // https://getbootstrap.com/docs/5.3/customize/sass/ diff --git a/themes/flatcar/assets/scss/_styles.scss b/themes/flatcar/assets/scss/_styles.scss index 744f35418..467028a34 100644 --- a/themes/flatcar/assets/scss/_styles.scss +++ b/themes/flatcar/assets/scss/_styles.scss @@ -1,3 +1,27 @@ +// Flatcar custom properties + +:root, +[data-bs-theme="light"] { + --flatcar-accent: #{$flatcar-darker-blue}; + --flatcar-surface-inverse: #{$black}; + --flatcar-surface-inverse-color: #{$white}; +} + +[data-bs-theme="dark"] { + --flatcar-accent: #{$flatcar-blue}; + --flatcar-surface-inverse: #{$flatcar-dark-700}; + --flatcar-surface-inverse-color: #f1f5f9; +} + +.announcement-banner { + background-color: var(--announcement-bg); + + [data-bs-theme="dark"] & { + background-color: rgba($yellow, 0.08); + border-left: 3px solid $yellow; + } +} + // Headings h2 { @@ -350,7 +374,7 @@ h6 { .page-item:nth-child(2):not(.disabled):hover, .page-item:nth-last-child(2):not(.disabled):hover { .page-link { - color: $black; + color: var(--bs-emphasis-color); background: none; &:before { @@ -367,3 +391,12 @@ a:hover { background-color: $flatcar-blue; border-color: $flatcar-blue; } + +[data-bs-theme="dark"] .pagination { + --bs-pagination-color: #{$body-color-dark}; + --bs-pagination-bg: #{$flatcar-dark-800}; + --bs-pagination-hover-color: #{$body-emphasis-color-dark}; + --bs-pagination-hover-bg: #{$flatcar-dark-700}; + --bs-pagination-disabled-bg: #{$flatcar-dark-800}; + --bs-pagination-disabled-color: #{rgba($body-color-dark, .35)}; +} diff --git a/themes/flatcar/assets/scss/_syntax-dark.scss b/themes/flatcar/assets/scss/_syntax-dark.scss new file mode 100644 index 000000000..04199568b --- /dev/null +++ b/themes/flatcar/assets/scss/_syntax-dark.scss @@ -0,0 +1,83 @@ +// Flatcar-branded Chroma syntax theme — DARK. +// +// Companion to the light theme in _syntax.scss. The branded rules are +// scoped under [data-bs-theme="dark"] so they override the light theme +// on dark pages; imported right after _syntax.scss in main.scss. + +[data-bs-theme="dark"] { + /* Background */ .bg { color:#C9D1E3; background-color:$flatcar-dark-900; } + /* PreWrapper */ .chroma { color:#C9D1E3; background-color:$flatcar-dark-900; -webkit-text-size-adjust:none; } + /* Error */ .chroma .err { color:#FF5C7A } + /* LineLink */ .chroma .lnlinks { outline:none; text-decoration:none; color:inherit } + /* LineTableTD */ .chroma .lntd { vertical-align:top; padding:0; margin:0; border:0; } + /* LineTable */ .chroma .lntable { border-spacing:0; padding:0; margin:0; border:0; } + /* LineHighlight */ .chroma .hl { background-color:$flatcar-dark-800 } + /* LineNumbersTable */ .chroma .lnt { white-space:pre; -webkit-user-select:none; user-select:none; margin-right:0.4em; padding:0 0.4em 0 0.4em; color:#56607A } + /* LineNumbers */ .chroma .ln { white-space:pre; -webkit-user-select:none; user-select:none; margin-right:0.4em; padding:0 0.4em 0 0.4em; color:#56607A } + /* Line */ .chroma .line { display:flex; } + /* Keyword */ .chroma .k { color:#09BAC8 } + /* KeywordConstant */ .chroma .kc { color:#09BAC8 } + /* KeywordDeclaration */ .chroma .kd { color:#09BAC8 } + /* KeywordNamespace */ .chroma .kn { color:#6FE0EA } + /* KeywordPseudo */ .chroma .kp { color:#09BAC8 } + /* KeywordReserved */ .chroma .kr { color:#09BAC8 } + /* KeywordType */ .chroma .kt { color:#4FE3EF } + /* Name */ .chroma .n { color:#C9D1E3 } + /* NameAttribute */ .chroma .na { color:#8FA6C8 } + /* NameClass */ .chroma .nc { color:#4FE3EF } + /* NameConstant */ .chroma .no { color:#FFB84D } + /* NameDecorator */ .chroma .nd { color:#7A87A0 } + /* NameEntity */ .chroma .ni { color:#8FA6C8 } + /* NameException */ .chroma .ne { color:#FF5C7A } + /* NameLabel */ .chroma .nl { color:#8FA6C8 } + /* NameNamespace */ .chroma .nn { color:#8FA6C8 } + /* NameProperty */ .chroma .py { color:#8FA6C8 } + /* NameTag */ .chroma .nt { color:#09BAC8 } + /* NameBuiltin */ .chroma .nb { color:#4FE3EF } + /* NameBuiltinPseudo */ .chroma .bp { color:#4FE3EF } + /* NameVariable */ .chroma .nv { color:#C9D1E3 } + /* NameVariableClass */ .chroma .vc { color:#C9D1E3 } + /* NameVariableGlobal */ .chroma .vg { color:#C9D1E3 } + /* NameVariableInstance */ .chroma .vi { color:#C9D1E3 } + /* NameVariableMagic */ .chroma .vm { color:#C9D1E3 } + /* NameFunction */ .chroma .nf { color:#4FE3EF } + /* NameFunctionMagic */ .chroma .fm { color:#4FE3EF } + /* Literal */ .chroma .l { color:#FFB84D } + /* LiteralDate */ .chroma .ld { color:#FFB84D } + /* LiteralString */ .chroma .s { color:#5FD9B4 } + /* LiteralStringAffix */ .chroma .sa { color:#5FD9B4 } + /* LiteralStringBacktick */ .chroma .sb { color:#5FD9B4 } + /* LiteralStringChar */ .chroma .sc { color:#5FD9B4 } + /* LiteralStringDelimiter */ .chroma .dl { color:#5FD9B4 } + /* LiteralStringDoc */ .chroma .sd { color:#5FD9B4 } + /* LiteralStringDouble */ .chroma .s2 { color:#5FD9B4 } + /* LiteralStringEscape */ .chroma .se { color:#09BAC8 } + /* LiteralStringHeredoc */ .chroma .sh { color:#5FD9B4 } + /* LiteralStringInterpol */ .chroma .si { color:#5FD9B4 } + /* LiteralStringOther */ .chroma .sx { color:#5FD9B4 } + /* LiteralStringRegex */ .chroma .sr { color:#5FD9B4 } + /* LiteralStringSingle */ .chroma .s1 { color:#5FD9B4 } + /* LiteralStringSymbol */ .chroma .ss { color:#5FD9B4 } + /* LiteralNumber */ .chroma .m { color:#FFB84D } + /* LiteralNumberBin */ .chroma .mb { color:#FFB84D } + /* LiteralNumberFloat */ .chroma .mf { color:#FFB84D } + /* LiteralNumberHex */ .chroma .mh { color:#FFB84D } + /* LiteralNumberInteger */ .chroma .mi { color:#FFB84D } + /* LiteralNumberIntegerLong */ .chroma .il { color:#FFB84D } + /* LiteralNumberOct */ .chroma .mo { color:#FFB84D } + /* Operator */ .chroma .o { color:#8B96AE } + /* OperatorWord */ .chroma .ow { color:#09BAC8 } + /* Punctuation */ .chroma .p { color:#8B96AE } + /* Comment */ .chroma .c { color:#8291AE; font-style:italic } + /* CommentHashbang */ .chroma .ch { color:#8291AE; font-style:italic } + /* CommentMultiline */ .chroma .cm { color:#8291AE; font-style:italic } + /* CommentSingle */ .chroma .c1 { color:#8291AE; font-style:italic } + /* CommentSpecial */ .chroma .cs { color:#8291AE; font-style:italic } + /* CommentPreproc */ .chroma .cp { color:#8291AE } + /* CommentPreprocFile */ .chroma .cpf { color:#8291AE } + /* GenericDeleted */ .chroma .gd { color:#FF5C7A } + /* GenericEmph */ .chroma .ge { font-style:italic } + /* GenericInserted */ .chroma .gi { color:#3FBF7A } + /* GenericStrong */ .chroma .gs { font-weight:bold } + /* GenericSubheading */ .chroma .gu { color:#6FE0EA } +} diff --git a/themes/flatcar/assets/scss/_syntax-dark.scss.parked b/themes/flatcar/assets/scss/_syntax-dark.scss.parked deleted file mode 100644 index 8a0a17e36..000000000 --- a/themes/flatcar/assets/scss/_syntax-dark.scss.parked +++ /dev/null @@ -1,99 +0,0 @@ -/* Flatcar-branded Chroma syntax theme — DARK (parked). - * - * Hand-written (not `hugo gen chromastyles`, since "flatcar" isn't a - * built-in Chroma style). Selector list mirrors the class names Chroma - * itself emits, so this can replace the previous generic `rose-pine-moon` - * style — only the palette changes. - * - * This file is intentionally NOT imported today. It can be revived in a - * future dark-mode PR by scoping it under `[data-bs-theme="dark"]` and - * importing it alongside the light theme in `_syntax.scss` once the site - * wires up Bootstrap color modes (see bootstrap/mixins/_color-mode.scss - * and bootstrap/_variables-dark.scss). - * - * Palette source: themes/flatcar/base.css & dark.css in the - * cookiecutter-marp-flatcar repo (same brand colors used on slides). - * #12172B bg-navy editor background - * #09BAC8 accent-cyan keywords / primary accent - * #7A87A0 muted base for comments / line numbers - * #FFB84D amber numbers / constants ("staged" brand amber) - * #5FD9B4 teal (light) strings (lightened brand teal, for contrast on navy) - * #FF5C7A red (light) errors / deletions (lightened brand red) - * #3FBF7A green (light) insertions (lightened brand green) - */ - -/* Background */ .bg { color:#C9D1E3; background-color:#12172B; } -/* PreWrapper */ .chroma { color:#C9D1E3; background-color:#12172B; -webkit-text-size-adjust:none; } -/* Error */ .chroma .err { color:#FF5C7A } -/* LineLink */ .chroma .lnlinks { outline:none; text-decoration:none; color:inherit } -/* LineTableTD */ .chroma .lntd { vertical-align:top; padding:0; margin:0; border:0; } -/* LineTable */ .chroma .lntable { border-spacing:0; padding:0; margin:0; border:0; } -/* LineHighlight */ .chroma .hl { background-color:#1C2238 } -/* LineNumbersTable */ .chroma .lnt { white-space:pre; -webkit-user-select:none; user-select:none; margin-right:0.4em; padding:0 0.4em 0 0.4em; color:#56607A } -/* LineNumbers */ .chroma .ln { white-space:pre; -webkit-user-select:none; user-select:none; margin-right:0.4em; padding:0 0.4em 0 0.4em; color:#56607A } -/* Line */ .chroma .line { display:flex; } -/* Keyword */ .chroma .k { color:#09BAC8 } -/* KeywordConstant */ .chroma .kc { color:#09BAC8 } -/* KeywordDeclaration */ .chroma .kd { color:#09BAC8 } -/* KeywordNamespace */ .chroma .kn { color:#6FE0EA } -/* KeywordPseudo */ .chroma .kp { color:#09BAC8 } -/* KeywordReserved */ .chroma .kr { color:#09BAC8 } -/* KeywordType */ .chroma .kt { color:#4FE3EF } -/* Name */ .chroma .n { color:#C9D1E3 } -/* NameAttribute */ .chroma .na { color:#8FA6C8 } -/* NameClass */ .chroma .nc { color:#4FE3EF } -/* NameConstant */ .chroma .no { color:#FFB84D } -/* NameDecorator */ .chroma .nd { color:#7A87A0 } -/* NameEntity */ .chroma .ni { color:#8FA6C8 } -/* NameException */ .chroma .ne { color:#FF5C7A } -/* NameLabel */ .chroma .nl { color:#8FA6C8 } -/* NameNamespace */ .chroma .nn { color:#8FA6C8 } -/* NameProperty */ .chroma .py { color:#8FA6C8 } -/* NameTag */ .chroma .nt { color:#09BAC8 } -/* NameBuiltin */ .chroma .nb { color:#4FE3EF } -/* NameBuiltinPseudo */ .chroma .bp { color:#4FE3EF } -/* NameVariable */ .chroma .nv { color:#C9D1E3 } -/* NameVariableClass */ .chroma .vc { color:#C9D1E3 } -/* NameVariableGlobal */ .chroma .vg { color:#C9D1E3 } -/* NameVariableInstance */ .chroma .vi { color:#C9D1E3 } -/* NameVariableMagic */ .chroma .vm { color:#C9D1E3 } -/* NameFunction */ .chroma .nf { color:#4FE3EF } -/* NameFunctionMagic */ .chroma .fm { color:#4FE3EF } -/* Literal */ .chroma .l { color:#FFB84D } -/* LiteralDate */ .chroma .ld { color:#FFB84D } -/* LiteralString */ .chroma .s { color:#5FD9B4 } -/* LiteralStringAffix */ .chroma .sa { color:#5FD9B4 } -/* LiteralStringBacktick */ .chroma .sb { color:#5FD9B4 } -/* LiteralStringChar */ .chroma .sc { color:#5FD9B4 } -/* LiteralStringDelimiter */ .chroma .dl { color:#5FD9B4 } -/* LiteralStringDoc */ .chroma .sd { color:#5FD9B4 } -/* LiteralStringDouble */ .chroma .s2 { color:#5FD9B4 } -/* LiteralStringEscape */ .chroma .se { color:#09BAC8 } -/* LiteralStringHeredoc */ .chroma .sh { color:#5FD9B4 } -/* LiteralStringInterpol */ .chroma .si { color:#5FD9B4 } -/* LiteralStringOther */ .chroma .sx { color:#5FD9B4 } -/* LiteralStringRegex */ .chroma .sr { color:#5FD9B4 } -/* LiteralStringSingle */ .chroma .s1 { color:#5FD9B4 } -/* LiteralStringSymbol */ .chroma .ss { color:#5FD9B4 } -/* LiteralNumber */ .chroma .m { color:#FFB84D } -/* LiteralNumberBin */ .chroma .mb { color:#FFB84D } -/* LiteralNumberFloat */ .chroma .mf { color:#FFB84D } -/* LiteralNumberHex */ .chroma .mh { color:#FFB84D } -/* LiteralNumberInteger */ .chroma .mi { color:#FFB84D } -/* LiteralNumberIntegerLong */ .chroma .il { color:#FFB84D } -/* LiteralNumberOct */ .chroma .mo { color:#FFB84D } -/* Operator */ .chroma .o { color:#8B96AE } -/* OperatorWord */ .chroma .ow { color:#09BAC8 } -/* Punctuation */ .chroma .p { color:#8B96AE } -/* Comment */ .chroma .c { color:#6B7A99; font-style:italic } -/* CommentHashbang */ .chroma .ch { color:#6B7A99; font-style:italic } -/* CommentMultiline */ .chroma .cm { color:#6B7A99; font-style:italic } -/* CommentSingle */ .chroma .c1 { color:#6B7A99; font-style:italic } -/* CommentSpecial */ .chroma .cs { color:#6B7A99; font-style:italic } -/* CommentPreproc */ .chroma .cp { color:#6B7A99 } -/* CommentPreprocFile */ .chroma .cpf { color:#6B7A99 } -/* GenericDeleted */ .chroma .gd { color:#FF5C7A } -/* GenericEmph */ .chroma .ge { font-style:italic } -/* GenericInserted */ .chroma .gi { color:#3FBF7A } -/* GenericStrong */ .chroma .gs { font-weight:bold } -/* GenericSubheading */ .chroma .gu { color:#6FE0EA } diff --git a/themes/flatcar/assets/scss/_syntax.scss b/themes/flatcar/assets/scss/_syntax.scss index ec0ff05ff..2e08fe6dc 100644 --- a/themes/flatcar/assets/scss/_syntax.scss +++ b/themes/flatcar/assets/scss/_syntax.scss @@ -5,11 +5,10 @@ * Chroma itself emits, so this is a drop-in swap for the previous * generic `rose-pine-moon` style. * - * The site is light-mode only for now (a dark-mode variant is parked - * in `_syntax-dark.scss.parked`, ready to be revived and scoped under - * `[data-bs-theme="dark"]` once the site adds a real mode toggle - * via Bootstrap's color-mode support (see bootstrap/mixins/_color-mode.scss - * and bootstrap/_variables-dark.scss). + * The matching dark-mode variant lives in `_syntax-dark.scss`, scoped + * under `[data-bs-theme="dark"]` and imported right after this file in + * `main.scss`, using Bootstrap's color-mode support (see + * bootstrap/mixins/_color-mode.scss and bootstrap/_variables-dark.scss). * * DESIGN NOTE — hue variety, not just "navy + one blue": * A first pass at this theme leaned on `$flatcar-link-blue` for almost diff --git a/themes/flatcar/assets/scss/buttons.scss b/themes/flatcar/assets/scss/buttons.scss index 49512b621..b6afce0ad 100644 --- a/themes/flatcar/assets/scss/buttons.scss +++ b/themes/flatcar/assets/scss/buttons.scss @@ -12,10 +12,18 @@ } .btn-black { + --_btn-bg: #{$black}; + --_btn-fg: #{$white}; + + [data-bs-theme="dark"] & { + --_btn-bg: #{$white}; + --_btn-fg: #{$black}; + } + position: relative; - background: $black; - color: $white !important; - box-shadow: inset 0 0 0 3px $black; + background: var(--_btn-bg); + color: var(--_btn-fg) !important; + box-shadow: inset 0 0 0 3px var(--_btn-bg); z-index: 1; @include media-breakpoint-down(md) { @@ -30,7 +38,7 @@ left: 0; right: 0; bottom: 0; - background: $black; + background: var(--_btn-bg); z-index: -1; transform: scaleY(0); transition: transform 0.3s; @@ -39,21 +47,16 @@ } .btn-black:hover, .btn-black:focus { - background: white; - @include media-breakpoint-up(md) { - color: $black !important; - background: white; - box-shadow: inset 0 0 0 3px $black; - } - - @include media-breakpoint-down(md) { - color: $white !important; + @include media-breakpoint-up(md) { + color: var(--_btn-bg) !important; + background: var(--_btn-fg); + box-shadow: inset 0 0 0 3px var(--_btn-bg); } } .btn-black:active { - color: $white !important; - background: rgba($black, 1); + color: var(--_btn-fg) !important; + background: var(--_btn-bg); } @mixin btn-colored($bg-color, $text-color, $border-color: false) { diff --git a/themes/flatcar/assets/scss/layouts.scss b/themes/flatcar/assets/scss/layouts.scss index 3fac0ec7a..449ba5d26 100644 --- a/themes/flatcar/assets/scss/layouts.scss +++ b/themes/flatcar/assets/scss/layouts.scss @@ -341,8 +341,8 @@ section { .card { padding: 22px; - background-color: $gray-100; - border: 1px solid $gray-200; + background-color: var(--bs-tertiary-bg); + border: 1px solid var(--bs-border-color); height: 100%; margin-left: auto; margin-right: auto; @@ -355,7 +355,7 @@ section { .card-body { font-size: 1rem; - color: $gray-800; + color: var(--bs-body-color); padding-top: 6px; line-break: pre; } @@ -537,7 +537,7 @@ main.notfound { margin-right: 50px; a { - color: $gray-700; + color: var(--bs-secondary-color); padding-bottom: 5px; font-size: 1rem; text-transform: none; diff --git a/themes/flatcar/assets/scss/main.scss b/themes/flatcar/assets/scss/main.scss index c7575eccc..c2250d323 100644 --- a/themes/flatcar/assets/scss/main.scss +++ b/themes/flatcar/assets/scss/main.scss @@ -34,7 +34,9 @@ @import "./sections/section-contact-us.scss"; @import "./sections/section-docs.scss"; @import "./syntax"; +@import "./syntax-dark"; @import "./pages/events-page.scss"; @import "./pages/blog-page.scss"; @import "./pages/article.scss"; + diff --git a/themes/flatcar/assets/scss/pages/article.scss b/themes/flatcar/assets/scss/pages/article.scss index e9659a986..fb5f7621a 100644 --- a/themes/flatcar/assets/scss/pages/article.scss +++ b/themes/flatcar/assets/scss/pages/article.scss @@ -23,7 +23,7 @@ a { text-decoration:none; - color: $flatcar-darker-blue; + color: var(--bs-link-color); font-weight: bold; transition: background 0.5s cubic-bezier(.33,.66,.66,1);//font @@ -35,7 +35,11 @@ ::selection { background: $flatcar-dark-blue; - color: white; + color: $white; + } + + [data-bs-theme="dark"] & ::selection { + background: lighten($flatcar-dark-blue, 30%); } .article__product-link { @@ -103,7 +107,7 @@ } .article__author { - color: $gray-600; + color: var(--bs-secondary-color); p { font-size: .9rem; @@ -111,7 +115,7 @@ } .section-title { - margin-bottom: 30px; + margin-bottom: 10px; } .authors { @@ -120,11 +124,11 @@ } .article__related { - background-color: $gray-100; + background-color: var(--bs-tertiary-bg); padding: 4rem 0 8rem 0; @include media-breakpoint-down(md) { - margin-top: 80px; + padding-top: 80px; } h2 { @@ -133,15 +137,15 @@ .card { padding: 0; - background: $white; - border: 1px solid $gray-200; + background: var(--bs-body-bg); + border: 1px solid var(--bs-border-color); font-size: 1rem; max-width: 320px; min-height: 340px; height: 100%; a { - color: $black; + color: var(--bs-body-color); } a:hover { @@ -167,7 +171,7 @@ .card-date { font-size: .9rem; - color: $gray-600; + color: var(--bs-secondary-color); } } } diff --git a/themes/flatcar/assets/scss/pages/blog-page.scss b/themes/flatcar/assets/scss/pages/blog-page.scss index 28397040f..6423ff873 100644 --- a/themes/flatcar/assets/scss/pages/blog-page.scss +++ b/themes/flatcar/assets/scss/pages/blog-page.scss @@ -1,5 +1,4 @@ .blog-page { - padding-top: 50px; .post-hero { margin-bottom: 106px; @@ -52,7 +51,7 @@ background-size: auto 100%; background-position: center center; background-repeat: no-repeat; - background-color: black; + background-color: var(--flatcar-surface-inverse); img { position: absolute; @@ -65,7 +64,7 @@ } .post-hero__content { - background-color: #f6f6f7; + background-color: var(--bs-tertiary-bg); } } @@ -96,13 +95,13 @@ .card-body { .post__date { - color: $gray-700; + color: var(--bs-secondary-color); font-weight: normal; } .authors { font-weight: bold; - color: $gray-800; + color: var(--bs-body-color); } p { @@ -120,27 +119,38 @@ } } -.blog-page, .blog { - .header-bg-stripes { - background: none !important; - color: $black !important; +.blog-page { + --blog-hero-overlap: 250px; - &:after { - content: ""; - background-color: $flatcar-bg-blue; - position: absolute; - top: 0; - height: 420px; - width: 100%; - left: 0; - z-index: -1; + .pagination-wrap { + margin-bottom: 5rem; + } - @include media-breakpoint-up(md) { - height: 520px; - } + .banner { + padding-bottom: var(--blog-hero-overlap); + } + + .post-hero { + margin-top: calc(-1 * var(--blog-hero-overlap)); + position: relative; + z-index: 1; + } +} + +.blog-page, .blog { + .banner { + h1 { + font-size: calc(1.325rem + .9vw); + font-family: inherit; + font-weight: $font-weight-semi-bold; } } + .authors { + color: var(--bs-body-color); + margin-top: 1.5rem; + } + .article { a { font-weight: 600; @@ -153,14 +163,14 @@ table { thead { th { - background-color: $black; - color: $white; + background-color: var(--flatcar-surface-inverse); + color: var(--flatcar-surface-inverse-color); } } tbody { td { - border-bottom: 1px solid $gray-200; + border-bottom: 1px solid var(--bs-border-color); } } diff --git a/themes/flatcar/assets/scss/partials/footer.scss b/themes/flatcar/assets/scss/partials/footer.scss index 62acef008..8631dcde8 100644 --- a/themes/flatcar/assets/scss/partials/footer.scss +++ b/themes/flatcar/assets/scss/partials/footer.scss @@ -100,6 +100,14 @@ color: $gray; } + [data-bs-theme="dark"] & { + color: $body-secondary-color-dark; + + a { + color: $body-secondary-color-dark; + } + } + @include media-breakpoint-up(xl) { margin-bottom: 1.67em; } @@ -120,6 +128,10 @@ text-transform: capitalize; transition: color 0.3s; + [data-bs-theme="dark"] & { + color: rgba($body-color-dark, .70); + } + &:hover { @include media-breakpoint-up(md) { color: $flatcar-blue; diff --git a/themes/flatcar/assets/scss/partials/grid-statements.scss b/themes/flatcar/assets/scss/partials/grid-statements.scss index fae81e420..0a239b8cc 100644 --- a/themes/flatcar/assets/scss/partials/grid-statements.scss +++ b/themes/flatcar/assets/scss/partials/grid-statements.scss @@ -1,6 +1,6 @@ .grid-statements { padding-top: 120px; - background: $gray-100; + background: var(--bs-tertiary-bg); .container { @include media-breakpoint-up(md) { @@ -21,7 +21,7 @@ } .card { - background: $white; + background: var(--bs-body-bg); padding: 44px 22px; height: 100%; text-align: center; diff --git a/themes/flatcar/assets/scss/partials/header.scss b/themes/flatcar/assets/scss/partials/header.scss index 84a809f4d..78983841f 100644 --- a/themes/flatcar/assets/scss/partials/header.scss +++ b/themes/flatcar/assets/scss/partials/header.scss @@ -45,6 +45,10 @@ &.scrolled { background-color: $flatcar-dark-blue; + + [data-bs-theme="dark"] & { + background-color: $flatcar-dark-900; + } .news_stripe { max-height: 0; padding: 0; diff --git a/themes/flatcar/assets/scss/partials/hero.scss b/themes/flatcar/assets/scss/partials/hero.scss index e6ba74349..34532e195 100644 --- a/themes/flatcar/assets/scss/partials/hero.scss +++ b/themes/flatcar/assets/scss/partials/hero.scss @@ -8,6 +8,10 @@ flex-direction: column; text-align: center; + [data-bs-theme="dark"] & { + border-bottom: 1px solid rgba(148, 180, 214, 0.12); + } + .banner-bg { overflow: hidden; position: absolute; @@ -47,6 +51,11 @@ .animated-svg:nth-child(9) { animation-delay: 4s; } } + > .container { + position: relative; + z-index: 1; + } + h1 { font-size: 3.6rem; font-family: "Overpass Mono", sans-serif; diff --git a/themes/flatcar/assets/scss/partials/navbar.scss b/themes/flatcar/assets/scss/partials/navbar.scss index e8f410d87..1b1d5987f 100644 --- a/themes/flatcar/assets/scss/partials/navbar.scss +++ b/themes/flatcar/assets/scss/partials/navbar.scss @@ -14,6 +14,8 @@ .navbar-brand { margin-right: 0; + view-transition-name: site-logo; + width: fit-content; img { max-height: 85px; @@ -35,6 +37,11 @@ } .navbar-nav { + view-transition-name: site-nav; + + @include media-breakpoint-up(md) { + width: fit-content; + } > .nav-item { position: relative; @@ -93,7 +100,7 @@ } } -.nav-item.dropdown { +.nav-item.dropdown:not(:has(.theme-switcher__toggle)) { &:hover,&.focus { @include media-breakpoint-up(md) { > .dropdown-menu { @@ -143,7 +150,8 @@ @include media-breakpoint-down(md) { margin-left: 0; - margin-top: 26px; + margin-top: 0.75em; + padding-bottom: 0.25em; } } @@ -210,3 +218,131 @@ body:not(.mobile-menu_open) { } } } + +// Theme switcher dropdown + +$_theme-switcher-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.25); +$_theme-switcher-shadow-dark: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.5); + +%icon-box { + flex-shrink: 0; + display: flex; + align-items: center; + svg { width: 100%; height: 100%; } +} + +.navbar-nav .theme-switcher__toggle { + &, &:hover, &:focus, &.show { + color: $white; + } +} + +.theme-switcher__toggle { + &::after { + display: none; + } + + @include media-breakpoint-down(md) { + &.show { + display: none; + } + } + + .theme-switcher__current-icon { + display: inline-flex; + align-items: center; + justify-content: center; + vertical-align: middle; + padding-bottom: 0.125rem; + } + + .theme-switcher__icon { + display: block; + } +} + +.theme-switcher__menu { + --bs-dropdown-bg: #{$black}; + --bs-dropdown-color: #{$white}; + --bs-dropdown-link-color: #{$white}; + --bs-dropdown-link-hover-bg: #{rgba($white, 0.15)}; + --bs-dropdown-link-hover-color: #{$white}; + + min-width: auto; + width: max-content; + padding: 0.25rem 0; + font-size: 0.875rem; + border-radius: 0; + border: 1px solid transparent; + box-shadow: $_theme-switcher-shadow; + + @include media-breakpoint-down(md) { + --bs-dropdown-bg: transparent; + border: none; + box-shadow: none; + width: 100%; + padding: 0; + } + + [data-bs-theme="dark"] & { + --bs-dropdown-bg: #{$flatcar-dark-800}; + box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(8, 162, 175, 0.06); + border-color: #{$flatcar-dark-500}; + + @include media-breakpoint-down(md) { + --bs-dropdown-bg: transparent; + box-shadow: none; + border-color: transparent; + } + } + + .dropdown-item { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.375rem 0.75rem; + white-space: nowrap; + font-weight: normal; + line-height: 1.4; + text-align: left; + + @include media-breakpoint-down(md) { + justify-content: center; + width: auto; + color: #{$white}; + } + } + + .dropdown-item__icon { + @extend %icon-box; + width: 1rem; + height: 1rem; + } + + .dropdown-item__description { + flex: none; + } + + .dropdown-item__title { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + text-transform: none; + margin-bottom: 0; + line-height: 1; + color: inherit; + } + + .theme-switcher__checkmark { + @extend %icon-box; + width: 0.75rem; + height: 0.75rem; + visibility: hidden; + color: $yellow; + } + + .dropdown-item__text { + display: none; + } +} diff --git a/themes/flatcar/assets/scss/search.scss b/themes/flatcar/assets/scss/search.scss index 57fa34883..55f98954f 100644 --- a/themes/flatcar/assets/scss/search.scss +++ b/themes/flatcar/assets/scss/search.scss @@ -9,18 +9,18 @@ align-items: center; gap: 6px; padding: 5px 10px; - background: $white; - border: 1px solid $gray-400; + background: var(--bs-body-bg); + border: 1px solid var(--bs-border-color); border-radius: 5px; - color: $gray-600; + color: var(--bs-secondary-color); cursor: pointer; font-size: 0.8rem; width: 100%; transition: border-color 0.15s; &:hover { - border-color: $gray-600; - color: $gray-800; + border-color: var(--bs-secondary-color); + color: var(--bs-emphasis-color); } svg { @@ -36,13 +36,13 @@ } .search-button-kbd { - background: $gray-200; - border: 1px solid $gray-300; + background: var(--bs-secondary-bg); + border: 1px solid var(--bs-border-color); border-radius: 3px; padding: 1px 5px; font-size: 0.7rem; font-family: inherit; - color: $gray-600; + color: var(--bs-secondary-color); } // Mobile: icon-only compact button @@ -86,7 +86,7 @@ width: 90%; max-width: 640px; max-height: 70vh; - background: $white; + background: var(--bs-body-bg); border-radius: 10px; box-shadow: 0 16px 70px rgba(0, 0, 0, 0.2); display: flex; @@ -100,11 +100,11 @@ align-items: center; gap: 10px; padding: 12px 16px; - border-bottom: 1px solid $gray-300; + border-bottom: 1px solid var(--bs-border-color); svg { flex-shrink: 0; - color: $gray-500; + color: var(--bs-secondary-color); } input { @@ -113,21 +113,21 @@ outline: none; font-size: 1rem; background: transparent; - color: $black; + color: var(--bs-body-color); &::placeholder { - color: $gray-500; + color: var(--bs-secondary-color); } } kbd { - background: $gray-200; - border: 1px solid $gray-300; + background: var(--bs-secondary-bg); + border: 1px solid var(--bs-border-color); border-radius: 4px; padding: 2px 8px; font-size: 0.7rem; font-family: inherit; - color: $gray-500; + color: var(--bs-secondary-color); flex-shrink: 0; } } @@ -136,7 +136,7 @@ .searchresults-header { padding: 8px 16px; font-size: 0.8rem; - color: $gray-600; + color: var(--bs-secondary-color); &:empty { display: none; @@ -152,7 +152,7 @@ max-height: calc(70vh - 60px); .search-result { - border-bottom: 1px solid $gray-200; + border-bottom: 1px solid var(--bs-border-color); &:last-child { border-bottom: none; @@ -162,11 +162,11 @@ display: block; padding: 12px 16px; text-decoration: none; - color: $black; + color: var(--bs-body-color); transition: background-color 0.1s; &:hover, &:focus { - background-color: $gray-100; + background-color: var(--bs-tertiary-bg); outline: none; } @@ -181,7 +181,7 @@ font-weight: 600; font-size: 0.95rem; margin-bottom: 4px; - color: $black; + color: var(--bs-body-color); a:hover &, a:focus & { color: $flatcar-blue; @@ -191,7 +191,7 @@ .search-result-excerpt { display: block; font-size: 0.85rem; - color: $gray-700; + color: var(--bs-secondary-color); line-height: 1.4; mark { @@ -220,25 +220,25 @@ mark.search-highlight { gap: 12px; padding: 8px 14px; margin-bottom: 12px; - background: $gray-100; - border: 1px solid $gray-300; + background: var(--bs-tertiary-bg); + border: 1px solid var(--bs-border-color); border-radius: 6px; font-size: 0.85rem; - color: $gray-700; + color: var(--bs-secondary-color); button { background: none; - border: 1px solid $gray-400; + border: 1px solid var(--bs-border-color); border-radius: 4px; padding: 2px 10px; font-size: 0.8rem; - color: $gray-600; + color: var(--bs-secondary-color); cursor: pointer; &:hover { - background: $white; - border-color: $gray-600; - color: $black; + background: var(--bs-body-bg); + border-color: var(--bs-secondary-color); + color: var(--bs-emphasis-color); } } } diff --git a/themes/flatcar/assets/scss/sections/section-contact-us.scss b/themes/flatcar/assets/scss/sections/section-contact-us.scss index 9b1108b9b..34fcd2fd0 100644 --- a/themes/flatcar/assets/scss/sections/section-contact-us.scss +++ b/themes/flatcar/assets/scss/sections/section-contact-us.scss @@ -9,20 +9,20 @@ .message { text-align: center; - color: $gray-500; + color: var(--bs-secondary-color); h1 { - color: $black; + color: var(--bs-body-color); font-size: 2rem; } h2 { - color: $gray-800; + color: var(--bs-body-color); font-size: 1.8rem; } h3 { - color: $gray-600; + color: var(--bs-secondary-color); font-size: 1.6rem; } @@ -37,7 +37,7 @@ input, textarea { &::placeholder { - color: $gray-700; + color: var(--bs-secondary-color); } } @@ -46,7 +46,7 @@ text-align: right; p { - color: $gray-600; + color: var(--bs-secondary-color); padding: 0; margin: 0; font-size: .9rem; diff --git a/themes/flatcar/assets/scss/sections/section-docs.scss b/themes/flatcar/assets/scss/sections/section-docs.scss index 59ceed77d..3295e9953 100644 --- a/themes/flatcar/assets/scss/sections/section-docs.scss +++ b/themes/flatcar/assets/scss/sections/section-docs.scss @@ -1,9 +1,38 @@ $paddingLeft: 1.5rem; $topElemsVPadding: 1.4rem; +$docs-container-max-width: 2048px; +$sideBarWidth: 350px; .docs { .header { background: $black !important; + + // Match the header container width to the docs content container + // so the logo aligns flush with the sidebar's left edge. + .container { + max-width: $docs-container-max-width; + + @include media-breakpoint-down(md) { + max-width: 100%; + width: 100%; + } + } + + // Give the logo the same left margin as the sidebar nav items + // so it visually aligns with the menu below. + .navbar-brand { + @include media-breakpoint-up(md) { + margin-left: $paddingLeft; + } + } + + // Keep the last nav item (theme switcher) from hugging the right + // edge of the viewport / scrollbar on docs pages. + .navbar-nav { + @include media-breakpoint-up(md) { + margin-right: $paddingLeft; + } + } } } @@ -24,11 +53,11 @@ $topElemsVPadding: 1.4rem; & > li.td-sidebar-nav__section-title { padding-left: $paddingLeft; padding-top: 2.5rem; - border-bottom: 1px solid #e0dfdf; + border-bottom: 1px solid var(--bs-border-color); a { font-weight: bold; - color: $black; + color: var(--bs-emphasis-color); font-size: 1rem; } } @@ -60,8 +89,8 @@ $topElemsVPadding: 1.4rem; &:after { width: 0.5em; height: 0.5em; - border-right: 2px solid #717171; - border-bottom: 2px solid #717171; + border-right: 2px solid var(--bs-secondary-color); + border-bottom: 2px solid var(--bs-secondary-color); border-top: none; border-left: none; margin-left: 0.83em; @@ -80,17 +109,17 @@ $topElemsVPadding: 1.4rem; #docs { & > li > ul { - border-bottom: 1px solid #e0dfdf; + border-bottom: 1px solid var(--bs-border-color); & > li { padding-left: $paddingLeft; & > a { - color: #474747; + color: var(--bs-body-color); font-size: 1rem; &.active { - color: $flatcar-darker-blue; + color: var(--flatcar-accent); } } } @@ -105,11 +134,11 @@ $topElemsVPadding: 1.4rem; li { a { - color: #717171; + color: var(--bs-secondary-color); font-size: 0.9rem; &.active { - color: $flatcar-darker-blue; + color: var(--flatcar-accent); font-weight: bold; } @@ -127,9 +156,8 @@ $topElemsVPadding: 1.4rem; } } -$sideBarWidth: 350px; .td-sidebar { - background: #f9f9f9; + background: var(--bs-tertiary-bg); padding: 0; @include media-breakpoint-up(xl) { @@ -175,12 +203,12 @@ $sideBarWidth: 350px; td, th { height: 45px; - border-bottom: 1px solid #e0dfdf; + border-bottom: 1px solid var(--bs-border-color); padding: 15px 25px 10px 25px; } thead { - background-color: #f6f6f7; + background-color: var(--bs-tertiary-bg); } pre { @@ -199,7 +227,7 @@ $sideBarWidth: 350px; .btn-copy { background: none; border: none !important; - color: #717171; + color: var(--bs-secondary-color); line-height: 1; padding: 0.25rem 0.5rem; z-index: 1; @@ -228,7 +256,7 @@ $sideBarWidth: 350px; margin-right: 50px; a { - color: #717171; + color: var(--bs-secondary-color); &:hover { text-decoration: none; @@ -249,7 +277,7 @@ $sideBarWidth: 350px; } .btn { - border: 2px solid #717171; + border: 2px solid var(--bs-secondary-color); } a.heading-anchor { @@ -257,7 +285,7 @@ $sideBarWidth: 350px; &:hover { text-decoration: none; - color: $black; + color: var(--bs-emphasis-color); } } @@ -284,14 +312,14 @@ $sideBarWidth: 350px; .breadcrumb-item { font-size: 1rem; & > a { - color: $gray-600; + color: var(--bs-secondary-color); text-decoration: none; } } } .container { - max-width: 2048px; + max-width: $docs-container-max-width; @include media-breakpoint-down(md) { max-width: 100%; @@ -311,6 +339,11 @@ $sideBarWidth: 350px; padding-bottom: 50px; } + .github-footer .btn { + border: 2px solid var(--bs-border-color); + padding: 4px 2px; + font-weight: inherit; + } } .header-with-news-stripe + .docs-section { @@ -335,14 +368,14 @@ $sideBarWidth: 350px; font-size: 0.75rem; font-weight: 600; letter-spacing: 0.05em; - color: #474747; + color: var(--bs-secondary-color); padding-left: 1rem; margin-bottom: 0.5rem; } #TableOfContents { font-size: 0.85rem; - border-left: 1px solid #e0e0e0; + border-left: 1px solid var(--bs-border-color); ul, ol { @@ -359,7 +392,7 @@ $sideBarWidth: 350px; & > a { display: block; padding: 0.2rem 0 0.2rem 0.875rem; - color: #616161; + color: var(--bs-secondary-color); text-decoration: none; transition: color 0.15s ease, border-color 0.15s ease; @@ -413,8 +446,8 @@ $sideBarWidth: 350px; } .card { - border: 1px solid rgba(0, 0, 0, 0.1); - background-color: $white; + border: 1px solid var(--bs-border-color-translucent); + background-color: var(--bs-body-bg); height: 100%; .card-header { @@ -477,7 +510,7 @@ $sideBarWidth: 350px; text-align: center; p { - color: #717171; + color: var(--bs-secondary-color); font-size: 0.9rem; } } @@ -500,7 +533,7 @@ $sideBarWidth: 350px; h4, h5 { & > a { - color: $black; + color: var(--bs-emphasis-color); &:hover { color: $flatcar-blue; @@ -510,8 +543,8 @@ $sideBarWidth: 350px; } .note { - background-color: $gray-100; - border: 1px solid $gray-200; + background-color: var(--bs-tertiary-bg); + border: 1px solid var(--bs-border-color); border-left: 4px solid $flatcar-blue; padding: 0.5rem; } diff --git a/themes/flatcar/assets/scss/sections/section-product.scss b/themes/flatcar/assets/scss/sections/section-product.scss index a70e0a7a8..46a78ca23 100644 --- a/themes/flatcar/assets/scss/sections/section-product.scss +++ b/themes/flatcar/assets/scss/sections/section-product.scss @@ -145,7 +145,7 @@ .companies { .title { width: 100%; - color: $black; + color: var(--bs-emphasis-color); font-size: 2rem; } } @@ -331,9 +331,9 @@ } .pricing-table { - background-color: #f6f6f7; + background-color: var(--bs-tertiary-bg); padding-bottom: 64px; - color: $black; + color: var(--bs-body-color); font-size: smaller; text-align: center; @@ -538,7 +538,7 @@ .resources-description { font-size: 1.2rem; - color: $gray-700; + color: var(--bs-secondary-color); margin-left: auto; margin-right: auto; } @@ -563,7 +563,7 @@ } .card { - background: $gray-100; + background: var(--bs-tertiary-bg); padding: 22px; height: 100%; text-align: center; @@ -590,7 +590,7 @@ } .card { - background-color: $white; + background-color: var(--bs-body-bg); text-align: left; } @@ -605,7 +605,7 @@ } .page-menus { - background-color: $gray-100; + background-color: var(--bs-tertiary-bg); margin-top: 0; padding: 0.5rem 1rem !important; @@ -613,7 +613,7 @@ font-size: x-large; &:hover { - color: $gray-700; + color: var(--bs-secondary-color); } } @@ -636,7 +636,7 @@ } .nav-link { - color: $black; + color: var(--bs-body-color); text-transform: none; font-weight: 700; } @@ -664,13 +664,20 @@ .releases { padding-bottom: 0 !important; + background-color: var(--bs-body-bg); + + // Accent for focus outlines and selected (.active) controls. It must + // contrast the surface; in dark mode the brand navy ($flatcar-bg-blue) + // equals the body background, so switch to the brighter link blue. + --release-accent: #{$flatcar-bg-blue}; - $gray-text: #717171; - $dark-text: #474747; + [data-bs-theme="dark"] & { + --release-accent: #{$flatcar-link-blue}; + } .article { a { - color: $flatcar-darker-blue; + color: var(--flatcar-accent); } } @@ -689,7 +696,7 @@ margin: 0 auto; font-size: 1rem; line-height: 1.7; - color: $dark-text; + color: var(--bs-body-color); p { margin-bottom: 12px; @@ -722,14 +729,14 @@ position: relative; display: inline-flex; align-items: center; - border: 2px solid $border-color; + border: 2px solid var(--bs-border-color); border-radius: 0; - color: $dark-text; + color: var(--bs-body-color); font-weight: 600; font-size: 0.95rem; padding: 8px 24px; cursor: pointer; - background: $white; + background: var(--bs-body-bg); transition: all 0.15s ease; user-select: none; margin-left: -2px; @@ -751,21 +758,21 @@ } &:has(input:focus-visible) { - outline: 2px solid $flatcar-bg-blue; + outline: 2px solid var(--release-accent); outline-offset: 2px; z-index: 3; } &.active { - background-color: $flatcar-bg-blue; - border-color: $flatcar-bg-blue; + background-color: var(--release-accent); + border-color: var(--release-accent); color: $white; z-index: 1; } &:not(.active):hover { background-color: rgba($flatcar-blue, 0.08); - border-color: $flatcar-bg-blue; + border-color: var(--release-accent); } &__label { @@ -786,11 +793,11 @@ display: inline-flex; flex-direction: column; align-items: center; - border: 2px solid $border-color; + border: 2px solid var(--bs-border-color); border-radius: 0; padding: 8px 20px; cursor: pointer; - background: $white; + background: var(--bs-body-bg); transition: all 0.15s ease; user-select: none; margin-left: -2px; @@ -814,14 +821,14 @@ } &:has(input:focus-visible) { - outline: 2px solid $flatcar-bg-blue; + outline: 2px solid var(--release-accent); outline-offset: 2px; z-index: 3; } &.active { - background-color: $flatcar-bg-blue; - border-color: $flatcar-bg-blue; + background-color: var(--release-accent); + border-color: var(--release-accent); color: $white; z-index: 2; @@ -832,7 +839,7 @@ &:not(.active):hover { background-color: rgba($flatcar-blue, 0.08); - border-color: $flatcar-bg-blue; + border-color: var(--release-accent); z-index: 1; } @@ -843,7 +850,7 @@ &__version { font-size: 0.7rem; - color: $gray-text; + color: var(--bs-secondary-color); margin-top: 2px; } } @@ -854,8 +861,8 @@ font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; - color: $gray-text; - border-bottom: 1px solid $border-color; + color: var(--bs-secondary-color); + border-bottom: 1px solid var(--bs-border-color); padding-bottom: 8px; margin-bottom: 16px; margin-top: 8px; @@ -867,9 +874,9 @@ flex-direction: column; align-items: center; justify-content: center; - border: 1px solid $border-color; + border: 1px solid var(--bs-border-color); border-radius: 8px; - background: $white; + background: var(--bs-body-bg); transition: all 0.15s ease; height: 100%; text-align: center; @@ -887,7 +894,7 @@ justify-content: center; padding: 20px 8px 16px; text-decoration: none; - color: $dark-text; + color: var(--bs-body-color); width: 100%; height: 100%; @@ -897,7 +904,7 @@ } &:focus-visible { - outline: 2px solid $flatcar-bg-blue; + outline: 2px solid var(--release-accent); outline-offset: -2px; border-radius: 8px; } @@ -914,7 +921,7 @@ height: 24px; border-radius: 4px; font-size: 0.7rem; - color: $gray-text; + color: var(--bs-secondary-color); text-decoration: none; transition: background-color 0.15s ease, @@ -926,7 +933,7 @@ } &:focus-visible { - outline: 2px solid $flatcar-bg-blue; + outline: 2px solid var(--release-accent); outline-offset: 1px; } } @@ -962,7 +969,7 @@ // ── Channel cards (original style) ── .releases-channels { - background-color: #f6f6f7; + background-color: var(--bs-tertiary-bg); padding: 56px 0 48px; .text-muted { @@ -973,11 +980,11 @@ } .card { - border: 1px solid $border-color; - background: $white; + background-color: var(--bs-body-bg); + border: 1px solid var(--bs-border-color); .card-header { - background-color: #f6f6f7; + background-color: var(--bs-tertiary-bg); h5 { font-size: 1.5em; @@ -986,7 +993,7 @@ .version { font-size: 0.9em; font-weight: bolder; - color: $gray-text; + color: var(--bs-secondary-color); } a.arch { @@ -1001,7 +1008,7 @@ .release-date { font-size: 0.8rem; font-weight: bolder; - color: $gray-text; + color: var(--bs-secondary-color); } } @@ -1010,12 +1017,12 @@ p { font-size: 0.8em; - color: #474747; + color: var(--bs-body-color); } } .card-footer { - border-top: 1px solid $border-color; + border-top: 1px solid var(--bs-border-color); font-size: 0.7em; } } @@ -1026,12 +1033,12 @@ // ── Release notes ── .notes-area { - background-color: #f6f6f7; + background-color: var(--bs-tertiary-bg); padding-left: auto; padding-right: auto; ul.nav-tabs { - border-bottom: 1px solid #dee2e6; + border-bottom: 1px solid var(--bs-border-color); padding-left: auto; padding-right: auto; max-width: 1200px; @@ -1040,7 +1047,7 @@ margin-right: 50px; a { - color: $dark-text; + color: var(--bs-body-color); &:hover { text-decoration: none; @@ -1056,7 +1063,7 @@ &.active { font-weight: bold; - color: $flatcar-darker-blue; + color: var(--flatcar-accent); border-bottom: 2px solid $flatcar-blue; } @@ -1098,7 +1105,7 @@ a.arch { text-decoration: underline; - color: $black; + color: var(--bs-body-color); &:hover { color: $flatcar-blue; @@ -1106,11 +1113,11 @@ } p.date { - color: $gray-text; + color: var(--bs-secondary-color); } .packages { - color: $dark-text; + color: var(--bs-body-color); } .release-list { @@ -1119,7 +1126,7 @@ } .release-notes { - color: $dark-text; + color: var(--bs-body-color); overflow-wrap: anywhere; } @@ -1129,17 +1136,17 @@ hr { width: 80%; - border-top: 1px solid $gray-400; + border-top: 1px solid var(--bs-border-color); } } } // ── Collapsible release entries ── .release-entry { - border: 1px solid $border-color; + border: 1px solid var(--bs-border-color); border-radius: 6px; margin-bottom: 8px; - background: $white; + background: var(--bs-body-bg); overflow: hidden; &__header { @@ -1172,7 +1179,7 @@ &__toggle-icon { font-size: 0.7rem; - color: $gray-text; + color: var(--bs-secondary-color); transition: transform 0.2s ease; flex-shrink: 0; } @@ -1180,7 +1187,7 @@ &__version { font-weight: 700; font-size: 1.3rem; - color: $black; + color: var(--bs-body-color); text-decoration: none; &:hover { @@ -1190,7 +1197,7 @@ &__date { font-size: 1rem; - color: $gray-text; + color: var(--bs-secondary-color); } &__archs { @@ -1204,7 +1211,7 @@ &__body { padding: 0 16px 16px; - border-top: 1px solid $border-color; + border-top: 1px solid var(--bs-border-color); padding-top: 16px; } } diff --git a/themes/flatcar/assets/scss/sections/section-related-post.scss b/themes/flatcar/assets/scss/sections/section-related-post.scss index db45c1355..271e8d9a0 100644 --- a/themes/flatcar/assets/scss/sections/section-related-post.scss +++ b/themes/flatcar/assets/scss/sections/section-related-post.scss @@ -1,12 +1,12 @@ .related-post { - color: $black; + color: var(--bs-body-color); padding-top: 1rem; padding-bottom: 1rem; .container { padding: 2rem; - border-top: 2px solid $gray; - border-bottom: 2px solid $gray; + border-top: 2px solid var(--bs-border-color); + border-bottom: 2px solid var(--bs-border-color); } .related-post-message { @@ -15,7 +15,7 @@ align-items: center; font-weight: $font-weight-light; font-size: 1.1rem; - color: $black; + color: var(--bs-body-color); min-width: 19.17em; flex-shrink: 0; // padding-left: 95px !important; diff --git a/themes/flatcar/layouts/404.html b/themes/flatcar/layouts/404.html index 3fdd3bdcf..7744656d1 100644 --- a/themes/flatcar/layouts/404.html +++ b/themes/flatcar/layouts/404.html @@ -1,22 +1,16 @@ - - - {{- partial "head.html" . -}} - -
-
- 404 - -

Whoops! Something's missing!

-

Not to worry, head back home!

-
-
- - {{ block "scripts" . }} - {{ $common := resources.Get "js/common.js" }} - - {{ $js := slice $common | resources.Concat "js/bundle.js" }} - - - {{ end }} - + + + + {{- partial "head.html" . -}} {{ partial "theme-mode.html" (dict "part" + "head" "page" .) }} + + +
+
+

Whoops! Something's missing!

+

Not to worry, head back home!

+
+
+ {{ partial "theme-mode.html" (dict "part" "body" "page" .) }} + diff --git a/themes/flatcar/layouts/_default/baseof.html b/themes/flatcar/layouts/_default/baseof.html index 0016f4784..2c8989b94 100644 --- a/themes/flatcar/layouts/_default/baseof.html +++ b/themes/flatcar/layouts/_default/baseof.html @@ -1,16 +1,20 @@ - - - {{- partial "head.html" . -}} - - Skip to content - {{- partial "header.html" . -}} + + + + {{ partial "head.html" . }} + {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} + + + Skip to content + {{ partial "header.html" . }} -
- {{ block "main" . }}{{ .Render "common-page" }}{{ end }} - {{ block "unique-content" . }}{{ end }} - {{ block "bottom" . }}{{ end }} -
+
+ {{ block "main" . }}{{ .Render "common-page" }}{{ end }} + {{ block "unique-content" . }}{{ end }} + {{ block "bottom" . }}{{ end }} +
- {{ partial "footer.html" . }} - + {{ partial "footer.html" . }} + {{ partial "theme-mode.html" (dict "part" "body" "page" .) }} + diff --git a/themes/flatcar/layouts/blog/baseof.html b/themes/flatcar/layouts/blog/baseof.html new file mode 100644 index 000000000..d88e2b6a8 --- /dev/null +++ b/themes/flatcar/layouts/blog/baseof.html @@ -0,0 +1,20 @@ + + + + {{ partial "head.html" . }} + {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} + + + Skip to content + {{ partial "header.html" . }} + +
+ {{ block "main" . }}{{ .Render "common-page" }}{{ end }} + {{ block "unique-content" . }}{{ end }} + {{ block "bottom" . }}{{ end }} +
+ + {{ partial "footer.html" . }} + {{ partial "theme-mode.html" (dict "part" "body" "page" .) }} + + diff --git a/themes/flatcar/layouts/blog/single.html b/themes/flatcar/layouts/blog/single.html index ba6f7be52..58af594e3 100644 --- a/themes/flatcar/layouts/blog/single.html +++ b/themes/flatcar/layouts/blog/single.html @@ -1,3 +1,11 @@ {{ define "main" }} + {{ partial "hero" . }} +
+
+
+ {{ partial "authors.html" (dict "message" "By" "context" . "with_date" true ) }} +
+
+
{{- partial "article.html" . -}} {{ end }} diff --git a/themes/flatcar/layouts/careers/single.html b/themes/flatcar/layouts/careers/single.html index de4239782..2a5fd8c56 100644 --- a/themes/flatcar/layouts/careers/single.html +++ b/themes/flatcar/layouts/careers/single.html @@ -1,19 +1,23 @@ - - + + + {{- partial "head.html" . -}} - - {{- partial "header.html" . -}} -
-
-
-
-
- {{ .Content }} -
+ {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} + + + {{- partial "header.html" . -}} +
+
+
+
+
+ {{ .Content }}
-
-
- {{- partial "footer.html" . -}} - - \ No newline at end of file +
+
+
+ {{- partial "footer.html" . -}} + {{ partial "theme-mode.html" (dict "part" "body" "page" .) }} + + diff --git a/themes/flatcar/layouts/docs/baseof.html b/themes/flatcar/layouts/docs/baseof.html index 7779ccdc1..31a001a2d 100644 --- a/themes/flatcar/layouts/docs/baseof.html +++ b/themes/flatcar/layouts/docs/baseof.html @@ -2,9 +2,9 @@ {{ partial "head.html" . }} - {{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }} + {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} - + {{ partial "header.html" . }}
@@ -59,7 +59,6 @@ Edit this page @@ -71,7 +70,6 @@ File documentation issue @@ -92,6 +90,7 @@ {{ partial "search-modal" . }} {{ partial "footer.html" . }} + {{ partial "theme-mode.html" (dict "part" "body" "page" .) }} {{ block "scripts" . }} {{- $searchJs := resources.GetMatch "js/fastsearch.js" | resources.Minify -}} diff --git a/themes/flatcar/layouts/docs/docsportal_home.html b/themes/flatcar/layouts/docs/docsportal_home.html index c0b894956..670d5f351 100644 --- a/themes/flatcar/layouts/docs/docsportal_home.html +++ b/themes/flatcar/layouts/docs/docsportal_home.html @@ -1,8 +1,11 @@ - - + + + {{- partial "head.html" . -}} - - {{- partial "header.html" . -}} + {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} + + + {{- partial "header.html" . -}}
{{/* partial "hero" . */}} @@ -20,72 +23,73 @@

{{ .Params.hero.title }}

- {{ $homeParams := .Params }} -
-
- {{ range $homeParams.highlighted_cards }} - {{ $main_url := .main_url}} -
-
-
- - {{ .title }} - -
-
-
    - {{ range .links }} -
  • - {{ $url := .url }} - {{ if (not ((hasPrefix $url "/") | or (hasPrefix $url "http://") | or (hasPrefix $url "https://"))) }} - {{ $url = path.Join $main_url .url}} - {{ end }} - {{ .name }} -
  • + {{ $homeParams := .Params }} +
    +
    + {{ range $homeParams.highlighted_cards }} + {{ $main_url := .main_url}} +
    +
    +
    + + {{ .title }} + +
    +
    +
      + {{ range .links }} +
    • + {{ $url := .url }} + {{ if (not ((hasPrefix $url "/") | or (hasPrefix $url "http://") | or (hasPrefix $url "https://"))) }} + {{ $url = path.Join $main_url .url}} {{ end }} -
    -
    -
    + {{ .name }} + + {{ end }} +
- {{ end }} +
-
- {{ if $homeParams.cards }} -
-
- {{ range $homeParams.cards }} -
-
-
- - {{ if (not .icon) | and .name }} -

{{ .name }}

- {{ end }} -
-
- {{ if .description }} -

- {{ .description }} -

- {{ end }} - Read more - - - - -
-
+ {{ end }} +
+
+ {{ if $homeParams.cards }} +
+
+ {{ range $homeParams.cards }} +
+
+
+ + {{ if (not .icon) | and .name }} +

{{ .name }}

+ {{ end }} +
+
+ {{ if .description }} +

+ {{ .description }} +

+ {{ end }} + Read more + + + +
- {{ end }}
- {{ end }} + {{ end }}
- -
+ + {{ end }} + + +
{{ partial "footer.html" . }} {{- $searchJs := resources.GetMatch "js/fastsearch.js" | resources.Minify -}} + {{ partial "theme-mode.html" (dict "part" "body" "page" .) }} diff --git a/themes/flatcar/layouts/partials/announcement_banner.html b/themes/flatcar/layouts/partials/announcement_banner.html index 6efe72480..2b28c7e96 100644 --- a/themes/flatcar/layouts/partials/announcement_banner.html +++ b/themes/flatcar/layouts/partials/announcement_banner.html @@ -1,5 +1,5 @@ {{ if .Page.Param "announcement" }} -
+
diff --git a/themes/flatcar/layouts/partials/article.html b/themes/flatcar/layouts/partials/article.html index 2cea980e2..67a2a1133 100644 --- a/themes/flatcar/layouts/partials/article.html +++ b/themes/flatcar/layouts/partials/article.html @@ -1,13 +1,6 @@ -
+
- - {{- $path := print "**/" .Params.postImage -}} {{- $image := resources.GetMatch $path -}} {{- $imagePath := path.Join "/media/splash" .Params.postImage -}} @@ -27,7 +20,7 @@

{{ .Title }}

-
+
{{- if .Params.moved_to_flatcar -}} {{ .Summary }}

Continue reading »

diff --git a/themes/flatcar/layouts/partials/footer.html b/themes/flatcar/layouts/partials/footer.html index 22f2d325e..03178a416 100644 --- a/themes/flatcar/layouts/partials/footer.html +++ b/themes/flatcar/layouts/partials/footer.html @@ -133,6 +133,5 @@ - {{- $common := resources.Get "js/common.js" -}} - {{- $js := slice $common | resources.Concat "js/common.js" -}} - + {{- $common := resources.Get "js/common.js" | fingerprint -}} + diff --git a/themes/flatcar/layouts/partials/head.html b/themes/flatcar/layouts/partials/head.html index 83e0741f3..05362a9ea 100644 --- a/themes/flatcar/layouts/partials/head.html +++ b/themes/flatcar/layouts/partials/head.html @@ -1,7 +1,3 @@ - - - - @@ -89,8 +85,33 @@ {{ $style := resources.Get "scss/main.scss" | css.Sass $options | fingerprint}} + + + - - - diff --git a/themes/flatcar/layouts/partials/hero.html b/themes/flatcar/layouts/partials/hero.html index f4ebfb961..2021e2daf 100644 --- a/themes/flatcar/layouts/partials/hero.html +++ b/themes/flatcar/layouts/partials/hero.html @@ -13,7 +13,10 @@ {{ if eq $title nil }} {{ $title = default .Title $hero.title }} {{ end }} -{{ $description := default .Description $hero.description }} +{{ $description := index $hero "description" }} +{{ if eq $description nil }} + {{ $description = .Description }} +{{ end }}
+ {{ partial "paginator.html" (.Paginate (after 1 (where .Data.Pages "Type" "blog").ByDate.Reverse)) }} +
+ {{ template "_internal/pagination.html" . }} +
+
+ {{- partial "footer.html" . -}} - + {{ partial "theme-mode.html" (dict "part" "body" "page" .) }} + diff --git a/themes/flatcar/layouts/section/releases.html b/themes/flatcar/layouts/section/releases.html index e9b1cd620..5e8d7bc27 100644 --- a/themes/flatcar/layouts/section/releases.html +++ b/themes/flatcar/layouts/section/releases.html @@ -265,20 +265,20 @@

Release Notes

aria-labelledby="{{ $chan.current.channel }}-release-tab"> {{ if eq $chan.current.channel "lts" }} {{ range slice "2024" "2023" }} {{ end }} diff --git a/themes/flatcar/layouts/taxonomy/tag.html b/themes/flatcar/layouts/taxonomy/tag.html index 0f6e99130..f03ecb06f 100644 --- a/themes/flatcar/layouts/taxonomy/tag.html +++ b/themes/flatcar/layouts/taxonomy/tag.html @@ -1,27 +1,24 @@ - - + + + {{ partial "head.html" . }} - - {{ partial "header.html" . }} -
-
-
- -
-
-
- -
- {{ template "_internal/pagination.html" . }} -
-
-
- {{ partial "footer.html" . }} - + {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} + + + Skip to content + {{ partial "header.html" . }} +
+ {{ partial "hero" . }} +
+ +
+ {{ template "_internal/pagination.html" . }} +
+
+
+ {{ partial "footer.html" . }} + {{ partial "theme-mode.html" (dict "part" "body" "page" .) }} + From aa98b8504f82ef5f36f9b6ded89485482ec77fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ervin=20R=C3=A1cz?= Date: Wed, 22 Jul 2026 15:18:23 +0300 Subject: [PATCH 02/10] fix(theme): dark-mode accessibility and contrast fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - move the theme script into head.html before the stylesheets so the right theme paints first, and drop the duplicate call from each layout head - set $link-color so body links aren't black in light mode, and remove the global a:hover override so the per-theme hover colors apply - use the theme link color for the docs toc and code-copy hover, which were low-contrast on the dark code background - add accessible light syntax-token variants that clear wcag aa - give navbar controls a visible focus ring in both themes - keep product cta text readable on hover - honor prefers-reduced-motion - drop an unused variable Signed-off-by: Ervin Rácz --- themes/flatcar/assets/scss/_custom.scss | 6 ++- themes/flatcar/assets/scss/_styles.scss | 3 -- themes/flatcar/assets/scss/_syntax.scss | 44 +++++++++---------- themes/flatcar/assets/scss/main.scss | 10 +++++ .../flatcar/assets/scss/partials/navbar.scss | 11 ++++- .../assets/scss/sections/section-docs.scss | 8 ++-- .../assets/scss/sections/section-product.scss | 3 ++ themes/flatcar/layouts/404.html | 3 +- themes/flatcar/layouts/_default/baseof.html | 1 - themes/flatcar/layouts/blog/baseof.html | 1 - themes/flatcar/layouts/careers/single.html | 1 - themes/flatcar/layouts/docs/baseof.html | 1 - .../flatcar/layouts/docs/docsportal_home.html | 1 - themes/flatcar/layouts/partials/head.html | 3 ++ themes/flatcar/layouts/section/blog.html | 1 - themes/flatcar/layouts/taxonomy/tag.html | 1 - 16 files changed, 58 insertions(+), 40 deletions(-) diff --git a/themes/flatcar/assets/scss/_custom.scss b/themes/flatcar/assets/scss/_custom.scss index 2eed5859a..489204a02 100644 --- a/themes/flatcar/assets/scss/_custom.scss +++ b/themes/flatcar/assets/scss/_custom.scss @@ -53,8 +53,10 @@ $flatcar-yellow: #fff200; // brand yellow accent $flatcar-red: #eb1a45; $flatcar-red-a11y: #ce163c; // darkened ~12% — $flatcar-red fails AA for small text on light backgrounds (3.9:1); this passes (4.9:1) $flatcar-pink: #de1040; +$flatcar-pink-a11y: #c11054; // darkened + more magenta: 4.45:1 on #F1F2F6 -> clears AA, distinct from red-a11y $flatcar-green: #198806; $flatcar-green-dark: #168203; +$flatcar-green-dark-a11y: #157b03; // darkened: 4.37:1 on #F1F2F6 -> clears AA (code strings) $flatcar-gold: #be8900; $flatcar-amber: #9a3f00; // text-safe derivative of $flatcar-gold, which fails AA on light backgrounds (2.8:1); used for code numerics @@ -75,6 +77,7 @@ $gray: #c4c4c4; $gold: $flatcar-gold; $flatcar-darker-blue: #007d88; +$flatcar-darker-blue-a11y: #007680; // darkened: 4.30:1 on #F1F2F6 -> clears AA (code functions) $flatcar-dark-blue: $flatcar-navy; $flatcar-bg-blue: $flatcar-dark-blue; $flatcar-blue: #08a2af; @@ -91,7 +94,8 @@ $body-color: $black; // Links -$link-hover-color: $green; +$link-color: $flatcar-link-blue; // was unset -> $black (links looked like body text) +$link-hover-color: $flatcar-blue; // was $green (a now-removed global a:hover forced this) $link-hover-decoration: none; // Paragraphs diff --git a/themes/flatcar/assets/scss/_styles.scss b/themes/flatcar/assets/scss/_styles.scss index 467028a34..baa967c75 100644 --- a/themes/flatcar/assets/scss/_styles.scss +++ b/themes/flatcar/assets/scss/_styles.scss @@ -383,9 +383,6 @@ h6 { } } -a:hover { - color: $flatcar-blue; -} .page-item.active .page-link, .page-link:hover { background-color: $flatcar-blue; diff --git a/themes/flatcar/assets/scss/_syntax.scss b/themes/flatcar/assets/scss/_syntax.scss index 2e08fe6dc..a179f5181 100644 --- a/themes/flatcar/assets/scss/_syntax.scss +++ b/themes/flatcar/assets/scss/_syntax.scss @@ -73,11 +73,11 @@ .chroma .nt { color:$flatcar-link-blue; font-weight:600 } /* Namespaces / decorators / exceptions — brand pink, distinct from error red */ -.chroma .kn { color:$flatcar-pink } -.chroma .nd { color:$flatcar-pink; font-style:italic } -.chroma .ne { color:$flatcar-pink; font-weight:600 } -.chroma .nn { color:$flatcar-pink } -.chroma .vm { color:$flatcar-pink } +.chroma .kn { color:$flatcar-pink-a11y } +.chroma .nd { color:$flatcar-pink-a11y; font-style:italic } +.chroma .ne { color:$flatcar-pink-a11y; font-weight:600 } +.chroma .nn { color:$flatcar-pink-a11y } +.chroma .vm { color:$flatcar-pink-a11y } /* Types / classes / builtins — navy, bold-for-emphasis rather than a new hue */ .chroma .kt { color:$flatcar-navy; font-weight:700 } @@ -86,8 +86,8 @@ .chroma .bp { color:$flatcar-navy; font-weight:700 } /* Functions — brand teal-blue, its own category instead of bolded navy */ -.chroma .nf { color:$flatcar-darker-blue; font-weight:600 } -.chroma .fm { color:$flatcar-darker-blue; font-weight:600 } +.chroma .nf { color:$flatcar-darker-blue-a11y; font-weight:600 } +.chroma .fm { color:$flatcar-darker-blue-a11y; font-weight:600 } /* Names / attributes / labels / tags-in-prose — quiet, muted */ .chroma .n { color:$flatcar-navy } @@ -106,20 +106,20 @@ .chroma .no { color:$flatcar-amber } /* Strings — brand green, the conventional "string" color */ -.chroma .s { color:$flatcar-green-dark } -.chroma .sa { color:$flatcar-green-dark } -.chroma .sb { color:$flatcar-green-dark } -.chroma .sc { color:$flatcar-green-dark } -.chroma .dl { color:$flatcar-green-dark } -.chroma .sd { color:$flatcar-green-dark } -.chroma .s2 { color:$flatcar-green-dark } +.chroma .s { color:$flatcar-green-dark-a11y } +.chroma .sa { color:$flatcar-green-dark-a11y } +.chroma .sb { color:$flatcar-green-dark-a11y } +.chroma .sc { color:$flatcar-green-dark-a11y } +.chroma .dl { color:$flatcar-green-dark-a11y } +.chroma .sd { color:$flatcar-green-dark-a11y } +.chroma .s2 { color:$flatcar-green-dark-a11y } .chroma .se { color:$flatcar-link-blue } /* escape sequence — pop it in keyword-blue against the green string */ -.chroma .sh { color:$flatcar-green-dark } -.chroma .si { color:$flatcar-green-dark } -.chroma .sx { color:$flatcar-green-dark } -.chroma .sr { color:$flatcar-green-dark } -.chroma .s1 { color:$flatcar-green-dark } -.chroma .ss { color:$flatcar-green-dark } +.chroma .sh { color:$flatcar-green-dark-a11y } +.chroma .si { color:$flatcar-green-dark-a11y } +.chroma .sx { color:$flatcar-green-dark-a11y } +.chroma .sr { color:$flatcar-green-dark-a11y } +.chroma .s1 { color:$flatcar-green-dark-a11y } +.chroma .ss { color:$flatcar-green-dark-a11y } /* Numbers — amber (accessible gold) */ .chroma .l { color:$flatcar-amber } @@ -148,6 +148,6 @@ /* Diff / generic */ .chroma .gd { color:$flatcar-red-a11y } .chroma .ge { font-style:italic } -.chroma .gi { color:$flatcar-green-dark } +.chroma .gi { color:$flatcar-green-dark-a11y } .chroma .gs { font-weight:bold } -.chroma .gu { color:$flatcar-pink } +.chroma .gu { color:$flatcar-pink-a11y } diff --git a/themes/flatcar/assets/scss/main.scss b/themes/flatcar/assets/scss/main.scss index c2250d323..14f273001 100644 --- a/themes/flatcar/assets/scss/main.scss +++ b/themes/flatcar/assets/scss/main.scss @@ -40,3 +40,13 @@ @import "./pages/blog-page.scss"; @import "./pages/article.scss"; + +// Respect prefers-reduced-motion (also removes the theme-toggle color cross-fade). +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + transition-duration: 0.01ms !important; + } +} diff --git a/themes/flatcar/assets/scss/partials/navbar.scss b/themes/flatcar/assets/scss/partials/navbar.scss index 1b1d5987f..2ef86a4da 100644 --- a/themes/flatcar/assets/scss/partials/navbar.scss +++ b/themes/flatcar/assets/scss/partials/navbar.scss @@ -221,8 +221,17 @@ body:not(.mobile-menu_open) { // Theme switcher dropdown +// Navbar sits on a dark background in both themes, so the default focus ring +// ($primary is $black) is invisible; use a light one. Skip nav.dark (light bg). +.navbar:not(.dark) .nav-link:focus-visible, +.navbar:not(.dark) .theme-switcher__toggle:focus-visible, +.theme-switcher__menu .dropdown-item:focus-visible { + outline: 2px solid rgba($white, 0.85); + outline-offset: 2px; + box-shadow: none; +} + $_theme-switcher-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.25); -$_theme-switcher-shadow-dark: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.5); %icon-box { flex-shrink: 0; diff --git a/themes/flatcar/assets/scss/sections/section-docs.scss b/themes/flatcar/assets/scss/sections/section-docs.scss index 3295e9953..3dd74684d 100644 --- a/themes/flatcar/assets/scss/sections/section-docs.scss +++ b/themes/flatcar/assets/scss/sections/section-docs.scss @@ -235,7 +235,7 @@ $sideBarWidth: 350px; cursor: pointer; &:hover { - color: $flatcar-link-blue; + color: var(--bs-link-color); } } } @@ -397,15 +397,15 @@ $sideBarWidth: 350px; transition: color 0.15s ease, border-color 0.15s ease; &:hover { - color: $flatcar-link-blue; + color: var(--bs-link-color); } } &.active { - border-left-color: $flatcar-link-blue; + border-left-color: var(--bs-link-color); & > a { - color: $flatcar-link-blue; + color: var(--bs-link-color); font-weight: 500; } } diff --git a/themes/flatcar/assets/scss/sections/section-product.scss b/themes/flatcar/assets/scss/sections/section-product.scss index 46a78ca23..a9b7748c2 100644 --- a/themes/flatcar/assets/scss/sections/section-product.scss +++ b/themes/flatcar/assets/scss/sections/section-product.scss @@ -29,6 +29,9 @@ .btn:hover { background-color: #fff; + // Keep the label readable on the white hover bg (dark mode would otherwise + // resolve .btn text to near-white). + color: $flatcar-navy; } .card-title { diff --git a/themes/flatcar/layouts/404.html b/themes/flatcar/layouts/404.html index 7744656d1..71cc375f6 100644 --- a/themes/flatcar/layouts/404.html +++ b/themes/flatcar/layouts/404.html @@ -1,8 +1,7 @@ - {{- partial "head.html" . -}} {{ partial "theme-mode.html" (dict "part" - "head" "page" .) }} + {{- partial "head.html" . -}}
diff --git a/themes/flatcar/layouts/_default/baseof.html b/themes/flatcar/layouts/_default/baseof.html index 2c8989b94..a766ce9c8 100644 --- a/themes/flatcar/layouts/_default/baseof.html +++ b/themes/flatcar/layouts/_default/baseof.html @@ -2,7 +2,6 @@ {{ partial "head.html" . }} - {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} Skip to content diff --git a/themes/flatcar/layouts/blog/baseof.html b/themes/flatcar/layouts/blog/baseof.html index d88e2b6a8..449006585 100644 --- a/themes/flatcar/layouts/blog/baseof.html +++ b/themes/flatcar/layouts/blog/baseof.html @@ -2,7 +2,6 @@ {{ partial "head.html" . }} - {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} Skip to content diff --git a/themes/flatcar/layouts/careers/single.html b/themes/flatcar/layouts/careers/single.html index 2a5fd8c56..997a44765 100644 --- a/themes/flatcar/layouts/careers/single.html +++ b/themes/flatcar/layouts/careers/single.html @@ -2,7 +2,6 @@ {{- partial "head.html" . -}} - {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} {{- partial "header.html" . -}} diff --git a/themes/flatcar/layouts/docs/baseof.html b/themes/flatcar/layouts/docs/baseof.html index 31a001a2d..96c31861c 100644 --- a/themes/flatcar/layouts/docs/baseof.html +++ b/themes/flatcar/layouts/docs/baseof.html @@ -2,7 +2,6 @@ {{ partial "head.html" . }} - {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} {{ partial "header.html" . }} diff --git a/themes/flatcar/layouts/docs/docsportal_home.html b/themes/flatcar/layouts/docs/docsportal_home.html index 670d5f351..0071f0aef 100644 --- a/themes/flatcar/layouts/docs/docsportal_home.html +++ b/themes/flatcar/layouts/docs/docsportal_home.html @@ -2,7 +2,6 @@ {{- partial "head.html" . -}} - {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} {{- partial "header.html" . -}} diff --git a/themes/flatcar/layouts/partials/head.html b/themes/flatcar/layouts/partials/head.html index 05362a9ea..28f7c4cc6 100644 --- a/themes/flatcar/layouts/partials/head.html +++ b/themes/flatcar/layouts/partials/head.html @@ -1,6 +1,9 @@ + + + {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} {{ if eq .Kind "term" }}{{ .Data.Singular }} {{ .Title }}{{ else }}{{ .Title }} | {{ $.Site.Title }}{{ end }} diff --git a/themes/flatcar/layouts/section/blog.html b/themes/flatcar/layouts/section/blog.html index 02d4c6814..25b9f190b 100644 --- a/themes/flatcar/layouts/section/blog.html +++ b/themes/flatcar/layouts/section/blog.html @@ -2,7 +2,6 @@ {{- partial "head.html" . -}} - {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} Skip to content diff --git a/themes/flatcar/layouts/taxonomy/tag.html b/themes/flatcar/layouts/taxonomy/tag.html index f03ecb06f..e863d9519 100644 --- a/themes/flatcar/layouts/taxonomy/tag.html +++ b/themes/flatcar/layouts/taxonomy/tag.html @@ -2,7 +2,6 @@ {{ partial "head.html" . }} - {{ partial "theme-mode.html" (dict "part" "head" "page" .) }} Skip to content From 6493df304299e1853b3ee19105fe9987f6188235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ervin=20R=C3=A1cz?= Date: Wed, 22 Jul 2026 17:33:16 +0300 Subject: [PATCH 03/10] fix(theme): dark-mode contrast, theme-init robustness, and switcher a11y MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - releases: use --flatcar-accent for badge/hover/version colours so they meet contrast on the dark cards - search: dark-mode highlight now uses near-solid brand yellow with dark text instead of low-contrast translucent yellow - theme init: ignore invalid stored modes and keep following the system theme on pages without a switcher (e.g. 404) - reduced-motion: also cap animation iterations - switcher: options are radio buttons (role=menuitemradio, aria-checked), the toggle announces the active theme, and selection is delegated so extra switcher instances stay in sync - restore the "Posts Tagged #" heading on tag pages Signed-off-by: Ervin Rácz --- themes/flatcar/assets/js/theme-switcher.js | 70 +++++++++---------- themes/flatcar/assets/scss/main.scss | 1 + themes/flatcar/assets/scss/search.scss | 8 +++ .../assets/scss/sections/section-product.scss | 8 +-- themes/flatcar/layouts/partials/hero.html | 3 + themes/flatcar/layouts/partials/navbar.html | 6 +- .../flatcar/layouts/partials/theme-mode.html | 2 +- 7 files changed, 53 insertions(+), 45 deletions(-) diff --git a/themes/flatcar/assets/js/theme-switcher.js b/themes/flatcar/assets/js/theme-switcher.js index df6c3b1d4..71d6b8b59 100644 --- a/themes/flatcar/assets/js/theme-switcher.js +++ b/themes/flatcar/assets/js/theme-switcher.js @@ -9,10 +9,12 @@ DARK = "dark"; var ICONS = { auto: "auto-icon", light: "sun-icon", dark: "moon-icon" }; + var LABELS = { auto: "Auto", light: "Light", dark: "Dark" }; function stored() { try { - return localStorage.getItem(KEY) || AUTO; + var m = localStorage.getItem(KEY); + return m === LIGHT || m === DARK ? m : AUTO; } catch (e) { return AUTO; } @@ -49,29 +51,21 @@ document.documentElement.setAttribute("data-bs-theme", theme); } - var btn = document.querySelector(".theme-switcher__toggle"); - if (btn) { + var iconRef = "#" + ICONS[mode]; + document.querySelectorAll(".theme-switcher__toggle").forEach(function (btn) { var use = btn.querySelector(".theme-switcher__current-icon use"); if (use) { - var iconRef = "#" + ICONS[mode]; - use.setAttributeNS( - "http://www.w3.org/1999/xlink", - "xlink:href", - iconRef, - ); + use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", iconRef); use.setAttribute("href", iconRef); } - } + btn.setAttribute("aria-label", "Theme: " + LABELS[mode]); + }); document.querySelectorAll(".theme-switcher__option").forEach(function (el) { var isActive = el.getAttribute("data-theme-mode") === mode; var cm = el.querySelector(".theme-switcher__checkmark"); if (cm) cm.style.visibility = isActive ? "visible" : "hidden"; - if (isActive) { - el.setAttribute("aria-current", "true"); - } else { - el.removeAttribute("aria-current"); - } + el.setAttribute("aria-checked", isActive ? "true" : "false"); }); } @@ -80,9 +74,10 @@ updateUI(mode); } - function select(mode) { + function select(mode, opt) { apply(mode); - var t = document.querySelector(".theme-switcher__toggle"); + var scope = (opt && opt.closest(".dropdown")) || document; + var t = scope.querySelector(".theme-switcher__toggle"); if (t && window.bootstrap) { var d = bootstrap.Dropdown.getInstance(t); if (d) d.hide(); @@ -90,33 +85,40 @@ } function init() { - var menu = document.querySelector(".theme-switcher__menu"); - if (!menu) return; - updateUI(stored()); - menu.addEventListener("click", function (e) { + window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", function () { + if (stored() === AUTO) updateUI(AUTO); + }); + + // Delegated so any number of switcher instances (e.g. a footer one) work. + document.addEventListener("click", function (e) { var opt = e.target.closest(".theme-switcher__option"); if (opt) { e.preventDefault(); - select(opt.getAttribute("data-theme-mode")); + select(opt.getAttribute("data-theme-mode"), opt); } }); - // Accessibility: on narrow viewports the toggle button is hidden with - // display:none while the dropdown is open (see navbar.scss). Hiding the - // focused element drops focus to , breaking arrow-key roving and - // Escape-to-close. Move focus into the menu when it opens while the - // toggle is hidden, and restore it to the toggle once it closes. - var toggle = document.querySelector(".theme-switcher__toggle"); - if (toggle) { + // Accessibility: on narrow viewports the toggle is hidden with display:none + // while the dropdown is open (see navbar.scss). Hiding the focused element + // drops focus to , breaking arrow-key roving and Escape-to-close. Move + // focus into the menu on open (while hidden) and restore it on close. Bound + // per switcher instance so additional switchers behave the same. + document.querySelectorAll(".theme-switcher__toggle").forEach(function (toggle) { + var menu = (toggle.closest(".dropdown") || document).querySelector( + ".theme-switcher__menu", + ); + if (!menu) return; var toggleHidden = function () { return getComputedStyle(toggle).display === "none"; }; toggle.addEventListener("shown.bs.dropdown", function () { if (!toggleHidden()) return; var target = - menu.querySelector('.theme-switcher__option[aria-current="true"]') || + menu.querySelector('.theme-switcher__option[aria-checked="true"]') || menu.querySelector(".theme-switcher__option"); if (target) target.focus(); }); @@ -127,13 +129,7 @@ toggle.focus(); } }); - } - - window - .matchMedia("(prefers-color-scheme: dark)") - .addEventListener("change", function () { - if (stored() === AUTO) updateUI(AUTO); - }); + }); } if (document.readyState === "loading") { diff --git a/themes/flatcar/assets/scss/main.scss b/themes/flatcar/assets/scss/main.scss index 14f273001..ed5f04a67 100644 --- a/themes/flatcar/assets/scss/main.scss +++ b/themes/flatcar/assets/scss/main.scss @@ -47,6 +47,7 @@ *::before, *::after { animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } } diff --git a/themes/flatcar/assets/scss/search.scss b/themes/flatcar/assets/scss/search.scss index 55f98954f..0dd43d767 100644 --- a/themes/flatcar/assets/scss/search.scss +++ b/themes/flatcar/assets/scss/search.scss @@ -212,6 +212,14 @@ mark.search-highlight { border-radius: 2px; } +// Dark mode: the translucent yellow composites to a dark olive, so inherited +// light text drops below AA. Use near-solid brand yellow with dark text. +[data-bs-theme="dark"] .search-result-excerpt mark, +[data-bs-theme="dark"] mark.search-highlight { + background-color: rgba(255, 213, 0, 0.9); + color: $flatcar-navy; +} + // Highlight dismiss bar .search-highlight-bar { display: flex; diff --git a/themes/flatcar/assets/scss/sections/section-product.scss b/themes/flatcar/assets/scss/sections/section-product.scss index a9b7748c2..2b1f1b22c 100644 --- a/themes/flatcar/assets/scss/sections/section-product.scss +++ b/themes/flatcar/assets/scss/sections/section-product.scss @@ -902,7 +902,7 @@ height: 100%; &:hover { - color: $flatcar-darker-blue; + color: var(--flatcar-accent); text-decoration: none; } @@ -932,7 +932,7 @@ &:hover { background-color: rgba($flatcar-blue, 0.15); - color: $flatcar-darker-blue; + color: var(--flatcar-accent); } &:focus-visible { @@ -961,7 +961,7 @@ font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; - color: $flatcar-darker-blue; + color: var(--flatcar-accent); background: rgba($flatcar-blue, 0.08); border-bottom-right-radius: 6px; border-top-left-radius: 7px; @@ -1194,7 +1194,7 @@ text-decoration: none; &:hover { - color: $flatcar-darker-blue; + color: var(--flatcar-accent); } } diff --git a/themes/flatcar/layouts/partials/hero.html b/themes/flatcar/layouts/partials/hero.html index 2021e2daf..5a169480b 100644 --- a/themes/flatcar/layouts/partials/hero.html +++ b/themes/flatcar/layouts/partials/hero.html @@ -13,6 +13,9 @@ {{ if eq $title nil }} {{ $title = default .Title $hero.title }} {{ end }} +{{ if and (eq .Kind "term") (eq .Data.Singular "tag") }} + {{ $title = printf "Posts Tagged #%s" $title }} +{{ end }} {{ $description := index $hero "description" }} {{ if eq $description nil }} {{ $description = .Description }} diff --git a/themes/flatcar/layouts/partials/navbar.html b/themes/flatcar/layouts/partials/navbar.html index 5e11fbb5a..64b598613 100644 --- a/themes/flatcar/layouts/partials/navbar.html +++ b/themes/flatcar/layouts/partials/navbar.html @@ -70,9 +70,9 @@ - diff --git a/themes/flatcar/layouts/partials/theme-mode.html b/themes/flatcar/layouts/partials/theme-mode.html index cecb366e5..67445ba32 100644 --- a/themes/flatcar/layouts/partials/theme-mode.html +++ b/themes/flatcar/layouts/partials/theme-mode.html @@ -20,7 +20,7 @@ {{ if eq .part "head" }} {{ if not .page.IsHome }} - + {{ end }} {{ end }} From 7e6b7a70c1a8928f5fc5cc8749143dfbbbdc511c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ervin=20R=C3=A1cz?= Date: Wed, 22 Jul 2026 18:10:21 +0300 Subject: [PATCH 04/10] fix(a11y): pre-existing focus and contrast issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found during the dark-mode review; not part of the theme feature itself: - .navbar-toggler had `outline: none !important`, leaving the mobile menu button with no keyboard focus indicator; add a :focus-visible ring - pagination active/hover was white on cyan (3.09:1); use navy text (5.75:1) - docs body links (.td-content a) were cyan on white (3.09:1); use the theme link colour (5.85:1 light / 5.75:1 dark), consistent with site links Signed-off-by: Ervin Rácz --- themes/flatcar/assets/scss/_styles.scss | 6 ++++++ themes/flatcar/assets/scss/sections/section-docs.scss | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/themes/flatcar/assets/scss/_styles.scss b/themes/flatcar/assets/scss/_styles.scss index baa967c75..caf978448 100644 --- a/themes/flatcar/assets/scss/_styles.scss +++ b/themes/flatcar/assets/scss/_styles.scss @@ -81,6 +81,11 @@ h6 { height: 18px; outline: none !important; + &:focus-visible { + outline: 2px solid rgba($white, 0.85) !important; + outline-offset: 3px; + } + > span { background: white; width: 100%; @@ -387,6 +392,7 @@ h6 { .page-item.active .page-link, .page-link:hover { background-color: $flatcar-blue; border-color: $flatcar-blue; + color: $flatcar-navy; } [data-bs-theme="dark"] .pagination { diff --git a/themes/flatcar/assets/scss/sections/section-docs.scss b/themes/flatcar/assets/scss/sections/section-docs.scss index 3dd74684d..7bfce9007 100644 --- a/themes/flatcar/assets/scss/sections/section-docs.scss +++ b/themes/flatcar/assets/scss/sections/section-docs.scss @@ -520,7 +520,7 @@ $sideBarWidth: 350px; .td-content { a { - color: $flatcar-blue; + color: var(--bs-link-color); &:hover { text-decoration: underline; From 4fa8287ca76ca64c3247729e2a016f3e32572e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ervin=20R=C3=A1cz?= Date: Thu, 23 Jul 2026 12:09:18 +0300 Subject: [PATCH 05/10] fix(theme-switcher): improve browser compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace `addEventListener` with fallback to `addListener` for older browsers - Add `e.preventDefault()` to prevent default link behavior - Fix `prefers-reduced-motion` media query syntax - Update CSS selector for theme-switcher dropdown - Remove unused image max-width style Signed-off-by: Ervin Rácz --- themes/flatcar/assets/js/common.js | 3 ++- themes/flatcar/assets/js/theme-switcher.js | 16 ++++++++++------ themes/flatcar/assets/scss/layouts.scss | 5 ----- themes/flatcar/assets/scss/partials/navbar.scss | 2 +- themes/flatcar/layouts/partials/head.html | 2 +- themes/flatcar/layouts/partials/navbar.html | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/themes/flatcar/assets/js/common.js b/themes/flatcar/assets/js/common.js index 5cc239cff..741391572 100644 --- a/themes/flatcar/assets/js/common.js +++ b/themes/flatcar/assets/js/common.js @@ -21,6 +21,7 @@ document.querySelectorAll(".nav-item.dropdown .nav-link").forEach(function(item) item.addEventListener("click", function(e) { // Skip theme dropdown - it has its own handling if (this.classList.contains('theme-switcher__toggle')) { + e.preventDefault(); return; } e.preventDefault(); @@ -201,4 +202,4 @@ document.querySelectorAll(".contact-cookies-consent-notice").forEach( item.classList.remove("d-none"); } } -); \ No newline at end of file +); diff --git a/themes/flatcar/assets/js/theme-switcher.js b/themes/flatcar/assets/js/theme-switcher.js index 71d6b8b59..790f19979 100644 --- a/themes/flatcar/assets/js/theme-switcher.js +++ b/themes/flatcar/assets/js/theme-switcher.js @@ -87,11 +87,15 @@ function init() { updateUI(stored()); - window - .matchMedia("(prefers-color-scheme: dark)") - .addEventListener("change", function () { - if (stored() === AUTO) updateUI(AUTO); - }); + var colorScheme = window.matchMedia("(prefers-color-scheme: dark)"); + var handleColorSchemeChange = function () { + if (stored() === AUTO) updateUI(AUTO); + }; + if (colorScheme.addEventListener) { + colorScheme.addEventListener("change", handleColorSchemeChange); + } else { + colorScheme.addListener(handleColorSchemeChange); + } // Delegated so any number of switcher instances (e.g. a footer one) work. document.addEventListener("click", function (e) { @@ -109,7 +113,7 @@ // per switcher instance so additional switchers behave the same. document.querySelectorAll(".theme-switcher__toggle").forEach(function (toggle) { var menu = (toggle.closest(".dropdown") || document).querySelector( - ".theme-switcher__menu", + ".theme-switcher__menu" ); if (!menu) return; var toggleHidden = function () { diff --git a/themes/flatcar/assets/scss/layouts.scss b/themes/flatcar/assets/scss/layouts.scss index 449ba5d26..b7eff53f2 100644 --- a/themes/flatcar/assets/scss/layouts.scss +++ b/themes/flatcar/assets/scss/layouts.scss @@ -493,11 +493,6 @@ main.notfound { color: $white; text-align: center; - img.main { - max-width: 700px; - width: 100%; - } - h1 { font-size: 2.3rem; diff --git a/themes/flatcar/assets/scss/partials/navbar.scss b/themes/flatcar/assets/scss/partials/navbar.scss index 2ef86a4da..6aad9e1d0 100644 --- a/themes/flatcar/assets/scss/partials/navbar.scss +++ b/themes/flatcar/assets/scss/partials/navbar.scss @@ -100,7 +100,7 @@ } } -.nav-item.dropdown:not(:has(.theme-switcher__toggle)) { +.nav-item.dropdown:not(.theme-switcher) { &:hover,&.focus { @include media-breakpoint-up(md) { > .dropdown-menu { diff --git a/themes/flatcar/layouts/partials/head.html b/themes/flatcar/layouts/partials/head.html index 28f7c4cc6..686cadbc2 100644 --- a/themes/flatcar/layouts/partials/head.html +++ b/themes/flatcar/layouts/partials/head.html @@ -109,7 +109,7 @@ ::view-transition-new(site-nav) { animation: none; } - @media (prefers-reduced-motion) { + @media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; } diff --git a/themes/flatcar/layouts/partials/navbar.html b/themes/flatcar/layouts/partials/navbar.html index 64b598613..2ac32f1e7 100644 --- a/themes/flatcar/layouts/partials/navbar.html +++ b/themes/flatcar/layouts/partials/navbar.html @@ -61,7 +61,7 @@ (dict "mode" "light" "icon" "sun-icon" "label" "Light" "desc" "Light theme") (dict "mode" "dark" "icon" "moon-icon" "label" "Dark" "desc" "Dark theme") -}} - {{ end }} {{ end }} - {{- $themes := slice - (dict "mode" "auto" "icon" "auto-icon" "label" "Auto" "desc" "Follow system") - (dict "mode" "light" "icon" "sun-icon" "label" "Light" "desc" "Light theme") - (dict "mode" "dark" "icon" "moon-icon" "label" "Dark" "desc" "Dark theme") - -}} - + {{ partial "theme-switcher.html" (dict "location" "navbar") }} diff --git a/themes/flatcar/layouts/partials/theme-switcher.html b/themes/flatcar/layouts/partials/theme-switcher.html new file mode 100644 index 000000000..e57cdb0e2 --- /dev/null +++ b/themes/flatcar/layouts/partials/theme-switcher.html @@ -0,0 +1,51 @@ +{{- $location := default "navbar" .location -}} +{{- $isNavbar := eq $location "navbar" -}} +{{- $themes := slice + (dict "mode" "auto" "icon" "auto-icon" "label" "Auto" "desc" "Follow system") + (dict "mode" "light" "icon" "sun-icon" "label" "Light" "desc" "Light theme") + (dict "mode" "dark" "icon" "moon-icon" "label" "Dark" "desc" "Dark theme") +-}} +{{ if $isNavbar }} + +{{ else }} +
+{{ end }} From 8e9d4f396a7fa44e6f5d1a91ddb8c268b7c1d058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ervin=20R=C3=A1cz?= Date: Fri, 24 Jul 2026 15:22:57 +0300 Subject: [PATCH 10/10] fix(docs): offset the sticky side nav below the fixed header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use top: 5rem instead of 0 so the docs sidebar sticks below the fixed navbar rather than under it. Signed-off-by: Ervin Rácz --- themes/flatcar/assets/scss/sections/section-docs.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/flatcar/assets/scss/sections/section-docs.scss b/themes/flatcar/assets/scss/sections/section-docs.scss index 7bfce9007..7cbb69aa3 100644 --- a/themes/flatcar/assets/scss/sections/section-docs.scss +++ b/themes/flatcar/assets/scss/sections/section-docs.scss @@ -43,7 +43,7 @@ $sideBarWidth: 350px; padding-top: calc(#{$topElemsVPadding} + 0.2rem); position: -webkit-sticky; /* Safari */ position: sticky; - top: 0; + top: 5rem; } }