fix(book): add body.quarto-dark selectors to foldbox dark mode CSS

foldbox.css and foldbox.lua both used @media (prefers-color-scheme: dark)
as the sole dark mode mechanism. This only activates on OS-level preference
and ignores Quarto's manual toggle button (which adds body.quarto-dark).

Result: toggling dark mode via the site button left all callout boxes
(Learning Objectives, Quiz, Checkpoint, etc.) rendering with light-mode
colors -- near-white summary text on a white background, invisible to
the reader.

Fix: emit both mechanisms in parallel.
- foldbox.css: duplicate structural rules under body.quarto-dark selectors
- foldbox.lua: refactor generateDarkModeCSS() to emit @media block plus
  a body.quarto-dark block via a shared emitCalloutRules() helper

Fixes callout rendering for all toggle-based dark mode users.
This commit is contained in:
Rocky
2026-05-18 03:48:55 +05:30
parent d60cca574b
commit 4ed27bda32
2 changed files with 63 additions and 26 deletions
@@ -257,7 +257,12 @@ details[class*="callout-"] li {
/* Dark mode support - Quarto-aligned dark theme */
/* Per-callout dark mode colors (--background-color, --text-color, border-color,
summary text, code text) are dynamically generated by foldbox.lua from YAML.
Only structural dark mode rules remain here. */
Only structural dark mode rules remain here.
Both selectors are required:
- @media covers OS-level dark preference
- body.quarto-dark covers Quarto's manual toggle button */
/* OS-level dark preference */
@media (prefers-color-scheme: dark) {
/* Base foldbox dark styling */
@@ -283,4 +288,26 @@ details[class*="callout-"] li {
details.fbx-question>summary::after {
background-color: var(--text-color);
}
}
/* Quarto manual toggle button - body.quarto-dark */
body.quarto-dark details.fbx-default,
body.quarto-dark details.fbx-answer,
body.quarto-dark details.fbx-question {
--text-color: #e6e6e6;
border-color: #454d55;
}
body.quarto-dark details.fbx-default>div,
body.quarto-dark details.fbx-answer>div,
body.quarto-dark details.fbx-question>div,
body.quarto-dark details[class*="callout-"]>div {
background-color: var(--bs-dark, #212529);
color: #e6e6e6;
}
body.quarto-dark details.fbx-default>summary::after,
body.quarto-dark details.fbx-answer>summary::after,
body.quarto-dark details.fbx-question>summary::after {
background-color: var(--text-color);
}
@@ -166,37 +166,47 @@ insertPreamble = function(doc, classDefs, fmt)
return(result)
end
-- Generate dark mode CSS overrides from YAML colors
-- This covers BOTH @media (prefers-color-scheme: dark) AND manual toggle
-- Generate dark mode CSS overrides from YAML colors.
-- Emits two parallel rule-sets so dark styling works regardless of how dark
-- mode is activated:
-- 1. @media (prefers-color-scheme: dark) -- OS-level system preference
-- 2. body.quarto-dark selector -- Quarto's manual toggle button
local generateDarkModeCSS = function ()
local darkCSS = {}
if classDefs == nil then return "" end
-- 1. @media query block for system-level dark mode (detected by OS)
table.insert(darkCSS, "<style>\n@media (prefers-color-scheme: dark) {\n")
for cls, options in pairs(classDefs) do
if options.colors and options.colors[2] then
local borderHex = options.colors[2]
local r, g, b = hexToRGB(borderHex)
table.insert(darkCSS, " details."..cls.." {\n")
table.insert(darkCSS, " --text-color: #e6e6e6;\n")
table.insert(darkCSS, " --background-color: rgba("..r..", "..g..", "..b..", "..DARK_BG_OPACITY..");\n")
table.insert(darkCSS, " --title-background-color: rgba("..r..", "..g..", "..b..", "..DARK_BG_OPACITY..");\n")
table.insert(darkCSS, " border-color: #"..borderHex..";\n")
table.insert(darkCSS, " }\n")
-- Bright summary text
table.insert(darkCSS, " details."..cls.." summary,\n")
table.insert(darkCSS, " details."..cls.." summary strong,\n")
table.insert(darkCSS, " details."..cls.." > summary {\n")
table.insert(darkCSS, " color: #f0f0f0 !important;\n")
table.insert(darkCSS, " }\n")
-- Code elements
table.insert(darkCSS, " details."..cls.." code {\n")
table.insert(darkCSS, " color: #e6e6e6 !important;\n")
table.insert(darkCSS, " }\n")
-- Helper: emit per-callout rules for one selector context
local function emitCalloutRules(prefix)
for cls, options in pairs(classDefs) do
if options.colors and options.colors[2] then
local borderHex = options.colors[2]
local r, g, b = hexToRGB(borderHex)
table.insert(darkCSS, prefix.."details."..cls.." {\n")
table.insert(darkCSS, " --text-color: #e6e6e6;\n")
table.insert(darkCSS, " --background-color: rgba("..r..", "..g..", "..b..", "..DARK_BG_OPACITY..");\n")
table.insert(darkCSS, " --title-background-color: rgba("..r..", "..g..", "..b..", "..DARK_BG_OPACITY..");\n")
table.insert(darkCSS, " border-color: #"..borderHex..";\n")
table.insert(darkCSS, " }\n")
table.insert(darkCSS, prefix.."details."..cls.." summary,\n")
table.insert(darkCSS, prefix.."details."..cls.." summary strong,\n")
table.insert(darkCSS, prefix.."details."..cls.." > summary {\n")
table.insert(darkCSS, " color: #f0f0f0 !important;\n")
table.insert(darkCSS, " }\n")
table.insert(darkCSS, prefix.."details."..cls.." code {\n")
table.insert(darkCSS, " color: #e6e6e6 !important;\n")
table.insert(darkCSS, " }\n")
end
end
end
table.insert(darkCSS, "}\n</style>\n")
-- 1. OS-level dark mode via @media query
table.insert(darkCSS, "<style>\n@media (prefers-color-scheme: dark) {\n")
emitCalloutRules(" ")
table.insert(darkCSS, "}\n")
-- 2. Quarto manual toggle button via body.quarto-dark class
emitCalloutRules("body.quarto-dark ")
table.insert(darkCSS, "</style>\n")
return pandoc.utils.stringify(darkCSS)
end