diff --git a/README.md b/README.md index 65363a55..94dde681 100644 --- a/README.md +++ b/README.md @@ -319,4 +319,23 @@ The styling of this website is happening in a number of different places. Some o The front page includes a number of detailed styling, these are all located in [styles-frontpage.scss](styles-frontpage.scss). They are all wrapped in `#FrontPage` ID so they shouldn't affect anything not located in the front page. -The sidebar for the [Get Started](start/) section has a unique style, and that is specified in the [start/styles.css](start/styles.css) file, that is loaded into each of these pages with either `css: styles.css` or `css: ../styles.css`. \ No newline at end of file +The sidebar for the [Get Started](start/) section has a unique style, and that is specified in the [start/styles.css](start/styles.css) file, that is loaded into each of these pages with either `css: styles.css` or `css: ../styles.css`. + +### Color palette + +The site uses an accessible cranberry color family (base `#BA355F`) documented by the Posit creative team against WCAG 2.2 AA. The full seven-step ramp (three light tints, the base, three dark shades) plus shared surfaces, inks, and code colors live in [`_cranberry.scss`](_cranberry.scss) as SCSS variables (`$cranberry-light-1` … `$cranberry-dark-3`, `$surface-light`/`$surface-dark`, `$ink`/`$ink-inverse`, `$theme-blue`/`$theme-blue-dark`). Both the light and dark themes layer this partial so the ramp is defined once. + +**Sass layering gotcha:** Quarto emits `scss:defaults` from *later-listed* theme files first, so `_cranberry.scss` must come **after** the files that consume its variables (it is listed last in each theme list). Conversely, `scss:rules` cascade in listing order. + +### Light and dark themes + +The `theme:` key (under `format: html:` in [_quarto.yml](_quarto.yml) — the light/dark mapping form is only honored there, not at the top level) defines both a light and a dark theme, so Quarto injects a light/dark toggle in the navbar. `respect-user-color-scheme: true` makes the initial choice follow the reader's OS/browser preference, falling back to light. + +- **Light** layers `[cosmo, styles.scss, styles-frontpage.scss, _cranberry.scss]`. +- **Dark** layers `[cosmo, styles-dark.scss, styles.scss, styles-frontpage.scss, _cranberry.scss]`. The dark surface derives from the existing tidymodels ink (`#1A162D`). + +Dark-specific overrides live in [`styles-dark.scss`](styles-dark.scss). Note the ordering: `styles-dark.scss` is listed *before* `styles.scss` so its `scss:defaults` (Bootstrap variables like `$body-bg`, `$link-color`, `$code-color`) win; its `scss:rules` are scoped under `.quarto-dark` so they win on specificity rather than source order. The default code blue (`#4758AB`) fails contrast on the dark surface, so dark mode uses `#919BCD` instead. + +Plain CSS files that aren't run through Sass ([`find/listing-cards.css`](find/listing-cards.css) and [`start/styles.css`](start/styles.css)) carry their own `.quarto-dark { … }` override blocks with the ramp values inlined as literals. + +**Page-level theme overrides** must also use the mapping form. For example `learn/models/parsnip-predictions/index.qmd` sets `theme: {light: [style.scss], dark: [style.scss]}`; a bare list there fails to merge with the site's light/dark themes. \ No newline at end of file diff --git a/_cranberry.scss b/_cranberry.scss new file mode 100644 index 00000000..8abcb104 --- /dev/null +++ b/_cranberry.scss @@ -0,0 +1,30 @@ +/*-- scss:defaults --*/ + +// Tidymodels cranberry color family & accessibility guidance. +// Prepared by the Posit creative team. Seven-step accessible family keyed to the +// base cranberry #BA355F, mirroring the tint structure of the Posit brand +// guidelines. WCAG 2.2 AA contrast is documented against the site's light +// surface (#FFFFFF) and the dark surface (#1A162D, the existing tidymodels ink). +// +// This partial only defines shared color variables; it is layered into both the +// light and dark themes so the ramp is defined once. + +// The family ------------------------------------------------------------------ +// Value on #FFFFFF on #1A162D +$cranberry-light-1: #ECC6D2; // 1.55:1 fills only 11.34:1 AAA +$cranberry-light-2: #DB96AC; // 2.34:1 fills only 7.50:1 AAA +$cranberry-light-3: #CB6585; // 3.66:1 Large / UI 4.80:1 AA +$cranberry-base: #BA355F; // 5.58:1 AA 3.14:1 Large / UI +$cranberry-dark-1: #8B2847; // 8.43:1 AAA 2.08:1 fills only +$cranberry-dark-2: #631D33; // 11.97:1 AAA 1.47:1 fills only +$cranberry-dark-3: #3A111F; // 16.41:1 AAA 1.07:1 fills only + +// Surfaces & neutrals --------------------------------------------------------- +$surface-light: #FFFFFF; +$surface-dark: #1A162D; // existing tidymodels ink +$ink: #1A162D; // body text on light (17.55:1 AAA) +$ink-inverse: #DDDDDD; // body text on dark (12.92:1 AAA) + +// Code color — default fails on dark (2.72:1); use the dark-safe variant there. +$theme-blue: #4758AB; +$theme-blue-dark: #919BCD; // 6.50:1 AA on #1A162D diff --git a/_quarto.yml b/_quarto.yml index fdad6619..68523f97 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -102,10 +102,23 @@ format: include-after-body: html/plausible.html code-link: true css: fonts/fonts.css + respect-user-color-scheme: true grid: body-width: 840px - -theme: - - cosmo - - styles.scss - - styles-frontpage.scss + # _cranberry.scss defines the shared color ramp. Quarto emits `scss:defaults` + # from later-listed files first, so it must come after the files that use it. + theme: + light: + - cosmo + - styles.scss + - styles-frontpage.scss + - _cranberry.scss + dark: + # styles-dark.scss is listed before styles.scss so its `scss:defaults` + # overrides win; its `scss:rules` are scoped under `.quarto-dark` so they + # win on specificity rather than source order. + - cosmo + - styles-dark.scss + - styles.scss + - styles-frontpage.scss + - _cranberry.scss diff --git a/find/listing-cards.css b/find/listing-cards.css index 5882737b..524611be 100644 --- a/find/listing-cards.css +++ b/find/listing-cards.css @@ -83,3 +83,14 @@ .parsnip-card-meta-2 { grid-template-columns: 1fr; } .listing-filter-summary { padding: 0.5rem 0.7rem; } } + +/* Dark theme — this is plain CSS (not run through Sass), so the cranberry ramp + values from _cranberry.scss are inlined here as literals. */ +.quarto-dark .listing-filter-summary, +.quarto-dark .listing-filter-panel { + background: #241f3a; + border-color: rgba(221, 221, 221, 0.25); +} +.quarto-dark .parsnip-card + .parsnip-card { border-top-color: rgba(221, 221, 221, 0.15); } +.quarto-dark .parsnip-card-meta { color: #b3b0bd; } +.quarto-dark .parsnip-card-meta code { background: rgba(236, 198, 210, 0.08); } diff --git a/learn/models/parsnip-predictions/index.qmd b/learn/models/parsnip-predictions/index.qmd index 7e282ba7..7eb6cd1e 100644 --- a/learn/models/parsnip-predictions/index.qmd +++ b/learn/models/parsnip-predictions/index.qmd @@ -13,7 +13,10 @@ toc: true toc-depth: 3 format: html: - theme: ["style.scss"] + # Mapping shape (not a bare list) so it merges into the site's light/dark themes. + theme: + light: ["style.scss"] + dark: ["style.scss"] r-packages: - tidymodels - agua diff --git a/start/styles.css b/start/styles.css index 89b1cc8b..cc84aa06 100644 --- a/start/styles.css +++ b/start/styles.css @@ -21,5 +21,16 @@ div.sidebar-item-container { counter-increment: section; content: counter(section, decimal) ""; padding-right: 0.5em; - color: #CA225E; /* $theme-cranberry */ + color: #BA355F; /* $theme-cranberry */ +} + +/* Dark theme — plain CSS, so cranberry ramp values are inlined as literals. */ +.quarto-dark .sidebar-menu-container { + border-right-color: rgba(221, 221, 221, 0.2); +} +.quarto-dark div.sidebar-item-container { + color: rgba(221, 221, 221, 0.6); +} +.quarto-dark .sidebar-item:not(:first-child)::before { + color: #CB6585; /* cranberry-light-3, 4.80:1 on dark */ } diff --git a/styles-dark.scss b/styles-dark.scss new file mode 100644 index 00000000..d97c1b92 --- /dev/null +++ b/styles-dark.scss @@ -0,0 +1,106 @@ +/*-- scss:defaults --*/ + +// Dark theme overrides. The shared cranberry ramp is defined in _cranberry.scss +// (layered ahead of this file). Surface derives from the existing tidymodels +// ink (#1A162D) so the color family stays intact. Contrast ratios are against +// that dark surface. See Tidymodels-ColorGuidance-Accessibility-2025. + +$body-bg: $surface-dark; // #1A162D +$body-color: $ink-inverse; // #DDDDDD, 12.92:1 AAA +$link-color: $cranberry-light-3; // #CB6585, 4.80:1 AA +$link-hover-color:$cranberry-light-2; // #DB96AC, 7.50:1 AAA +$code-color: $theme-blue-dark; // #919BCD, 6.50:1 AA (default blue fails at 2.72:1) +$navbar-bg: $surface-dark; + +/*-- scss:rules --*/ + +// Overrides for surfaces hardcoded to light in styles.scss / styles-frontpage.scss. +// Scoped under .quarto-dark so they win on specificity regardless of source order. +.quarto-dark { + + h2 { + color: $ink-inverse; + } + + // code chunks + inline code — dark translucent tint instead of the light grey + div.sourceCode, + p code:not(.sourceCode), + li code:not(.sourceCode), + td code:not(.sourceCode), + #session-info div pre code { + background-color: rgba($cranberry-light-1, 0.08); + border-color: rgba($cranberry-light-1, 0.12); + } + + #session-info div pre code { + color: $ink-inverse; + } + + // output tables — flip the odd (white) rows and bottom border to dark surface + .cell-output-display table { + thead { + background-color: rgba($cranberry-base, 0.25); + } + + tbody { + tr:nth-of-type(even) > * { + background-color: rgba($cranberry-light-1, 0.08); + --bs-table-accent-bg: rgba($cranberry-light-1, 0.08); + } + + tr:nth-of-type(odd) > * { + background-color: $surface-dark; + --bs-table-accent-bg: $surface-dark; + } + + tr:last-of-type { + border-bottom-color: rgba($ink-inverse, 0.2); + } + } + } + + // sidebar resource stickies + .section.resources .event a { + color: $ink-inverse; + + &:hover { + color: $cranberry-light-3; + } + } + + // Front page — bands and the event card hardcode light surfaces in + // styles-frontpage.scss. Override them here and drop the decorative + // tidy-back-*.svg backgrounds (light-canvas illustrations with white + // strokes that clash on the dark surface). + #FrontPage { + + .event { + background-color: rgba($cranberry-light-1, 0.06); + border-color: rgba($cranberry-light-1, 0.15); + color: $ink-inverse; + } + + .band.first .bandContent, + .band.third .bandContent { + background-color: transparent; + background-image: none; + } + + .band.second { + background-color: $surface-dark; + border-top-color: rgba($ink-inverse, 0.15); + border-bottom-color: rgba($ink-inverse, 0.15); + + .bandContent { + background-color: transparent; + background-image: none; + color: $ink-inverse; + } + } + + .band.first, + .band.third { + background-color: $surface-dark; + } + } +} diff --git a/styles.scss b/styles.scss index f31b665a..e32148c5 100644 --- a/styles.scss +++ b/styles.scss @@ -1,16 +1,18 @@ /*-- scss:defaults --*/ -$theme-cranberry: #CA225E; -$theme-blue: #4758AB; -$theme-white: #FFFFFF; +// Color ramp ($cranberry-*, $theme-blue, $surface-*, $ink*) is defined in +// _cranberry.scss, layered ahead of this file in _quarto.yml. +$theme-cranberry: $cranberry-base; // #BA355F +$theme-white: $surface-light; $theme-grey: #DDDDDD; -$theme-black: #1a162d; +$theme-black: $ink; // #1a162d $theme-code-bg: rgba(233,236,239,.3); // Suppress cosmo's default Source Sans Pro Google Fonts import — we use Lato. $web-font-path: ""; // Colors ---------------------------------------------------------------------- -$link-color: $theme-cranberry; +$link-color: $cranberry-base; +$link-hover-color: $cranberry-dark-1; // 8.43:1 AAA on white // Fonts ----------------------------------------------------------------------- $font-family-sans-serif: 'Lato', sans-serif; @@ -26,7 +28,7 @@ $code-color: $theme-blue; $navbar-bg: $theme-white; $footer-bg: rgba($theme-cranberry, 0.2); -$footer-fg: darken($theme-cranberry, 12%); +$footer-fg: $cranberry-dark-1; // 8.43:1 AAA on white /*-- scss:rules --*/ @@ -40,6 +42,12 @@ a { text-decoration: none; } +// Accessible keyboard focus indicator (≥3:1, per the cranberry guidance). +:focus-visible { + outline: 2px solid $cranberry-base; + outline-offset: 2px; +} + // Inline SVG icons — used in place of Font Awesome / Bootstrap Icons to keep // the front page from loading their CSS and woff2 font files. Currently used by: // * fa-flag-checkered + fa-lightbulb in index.qmd (FA Free 6 Solid) @@ -228,7 +236,7 @@ p code:not(.sourceCode), li code:not(.sourceCode), td code:not(.sourceCode) { thead { font-weight: 600; - background-color: rgba($theme-cranberry, 0.15); + background-color: $cranberry-light-1; // L1 fill, ≥12:1 with ink text tr th { padding: 15px 13px; @@ -242,8 +250,8 @@ p code:not(.sourceCode), li code:not(.sourceCode), td code:not(.sourceCode) { tbody { tr:nth-of-type(even) > * { - background-color: rgba($theme-cranberry, 0.03); - --bs-table-accent-bg: rgba($theme-cranberry, 0.03); + background-color: rgba($cranberry-light-1, 0.25); + --bs-table-accent-bg: rgba($cranberry-light-1, 0.25); } tr:nth-of-type(odd) > * {