diff --git a/DESCRIPTION b/DESCRIPTION
index 1d167fd..235325a 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -8,7 +8,8 @@ Authors@R:
Description: Provides a complete ggplot2 brand theme system for Civilytics
Consulting LLC, including editorial light, dark, and slide-optimized themes;
10 curated color palettes for qualitative, sequential, and diverging data;
- logo composition utilities; and data-wrangling helpers for public-sector
+ logo composition utilities; Quarto themes for HTML reports, PDF, Typst, and
+ Reveal.js presentations; and data-wrangling helpers for public-sector
analysis.
License: LGPL (>= 3)
URL: https://gitea.civilytics.org/Civilytics/civilyticsR
@@ -28,7 +29,8 @@ Imports:
Encoding: UTF-8
Suggests:
testthat (>= 3.0.0),
- tidycensus
+ tidycensus,
+ quarto
Config/testthat/edition: 3
Config/roxygen2/version: 8.0.0
RoxygenNote: 7.3.3
diff --git a/NAMESPACE b/NAMESPACE
index 79ca718..e4ef366 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -49,6 +49,9 @@ export(theme_civilytics_map)
export(theme_civilytics_slide)
export(theme_civilytics_slide_map)
export(trim_max)
+export(use_civilytics_brand)
+export(use_civilytics_revealjs)
+export(use_civilytics_theme)
export(waldInterval)
export(z_gap_test)
export(z_univariate)
diff --git a/R/civilytics-package.R b/R/civilytics-package.R
index 866eae7..67e4fcc 100644
--- a/R/civilytics-package.R
+++ b/R/civilytics-package.R
@@ -19,6 +19,13 @@
#' \item [scale_color_civilytics()] / [scale_fill_civilytics()] -- ggplot2 scales
#' }
#'
+#' @section Quarto templates:
+#' \itemize{
+#' \item [use_civilytics_revealjs()] -- install Reveal.js slide extension
+#' \item [use_civilytics_theme()] -- install HTML/PDF/Typst document theme
+#' \item [use_civilytics_brand()] -- install brand.yml only (Quarto 1.5+)
+#' }
+#'
#' @keywords internal
#' @importFrom stats qbeta
#' @importFrom stats qnorm
diff --git a/R/quarto.R b/R/quarto.R
new file mode 100644
index 0000000..ed3cc06
--- /dev/null
+++ b/R/quarto.R
@@ -0,0 +1,193 @@
+#' Install Civilytics Quarto Themes and Templates
+#'
+#' Helper functions that copy Civilytics Quarto assets from the installed
+#' package into your Quarto project directory. Logos are stored in a single
+#' canonical location (`inst/img/`) and copied to the paths expected by each
+#' template at install time.
+#'
+#' @name quarto-helpers
+NULL
+
+
+# -- internal utilities -------------------------------------------------------
+
+#' Copy a package file to a project directory
+#'
+#' @param src Relative path within the installed package (under `inst/`).
+#' @param dst Destination path relative to `path`.
+#' @param path Project root.
+#' @param force Overwrite existing files?
+#' @return Invisible logical indicating success.
+#' @keywords internal
+.copy_pkg_file <- function(src, dst, path, force) {
+ from <- system.file(src, package = "civilytics", mustWork = TRUE)
+ to <- file.path(path, dst)
+ dir.create(dirname(to), recursive = TRUE, showWarnings = FALSE)
+
+ if (file.exists(to) && !force) {
+ message(" skip: ", dst, " (already exists; use force = TRUE to overwrite)")
+ return(invisible(FALSE))
+ }
+
+ file.copy(from, to, overwrite = force)
+ message(" copy: ", dst)
+ invisible(TRUE)
+}
+
+#' Copy logo SVGs from inst/img/ to a target directory
+#'
+#' @param dest_dir Destination directory relative to `path`.
+#' @param path Project root.
+#' @param force Overwrite existing files?
+#' @param files Character vector of logo filenames to copy.
+#' @keywords internal
+.copy_logos <- function(dest_dir, path, force,
+ files = c("civilytics-mark.svg",
+ "civilytics-mark-reverse.svg",
+ "civilytics-wordmark.svg",
+ "civilytics-wordmark-reverse.svg",
+ "civilytics-pulse.svg")) {
+ for (f in files) {
+ .copy_pkg_file(file.path("img", f), file.path(dest_dir, f), path, force)
+ }
+}
+
+
+# -- public API ----------------------------------------------------------------
+
+#' Install the Civilytics Reveal.js extension
+#'
+#' Copies the Civilytics Reveal.js Quarto extension into
+#' `_extensions/civilytics-reveal/` under `path`, including brand logos
+#' from the package. After running this function, add
+#' `format: civilytics-reveal-revealjs` to your `.qmd` YAML front-matter.
+#'
+#' @param path Character. Project directory to install into. Default `"."`.
+#' @param force Logical. Overwrite existing files? Default `FALSE`.
+#'
+#' @return Invisible `NULL`.
+#' @export
+#'
+#' @examples
+#' \dontrun{
+#' use_civilytics_revealjs()
+#' }
+use_civilytics_revealjs <- function(path = ".", force = FALSE) {
+ path <- normalizePath(path, mustWork = TRUE)
+ message("Installing Civilytics Reveal.js extension into: ", path)
+
+ ext_src <- "quarto/extensions/civilytics-reveal"
+ ext_dst <- "_extensions/civilytics-reveal"
+
+ ext_files <- c(
+ "_extension.yml",
+ "civilytics.scss",
+ "civilytics.css",
+ "civilytics-head.html",
+ "civilytics-after.html"
+ )
+ for (f in ext_files) {
+ .copy_pkg_file(file.path(ext_src, f), file.path(ext_dst, f), path, force)
+ }
+
+ # Logos — single source from inst/img/
+ .copy_logos(file.path(ext_dst, "assets"), path, force)
+
+ message("\nDone! Add this to your .qmd front-matter:\n")
+ message("---")
+ message("format: civilytics-reveal-revealjs")
+ message("---")
+ invisible(NULL)
+}
+
+
+#' Install the Civilytics document theme
+#'
+#' Copies Civilytics HTML, PDF (LaTeX), and Typst theme files into
+#' your Quarto project. Also installs `_brand.yml` and the logo
+#' assets it references.
+#'
+#' @inheritParams use_civilytics_revealjs
+#'
+#' @return Invisible `NULL`.
+#' @export
+#'
+#' @examples
+#' \dontrun{
+#' use_civilytics_theme()
+#' }
+use_civilytics_theme <- function(path = ".", force = FALSE) {
+ path <- normalizePath(path, mustWork = TRUE)
+ message("Installing Civilytics document theme into: ", path)
+
+ # Brand file
+ .copy_pkg_file("quarto/_brand.yml", "_brand.yml", path, force)
+
+ # HTML theme
+ theme_files <- c("civilytics.scss", "_tokens.scss", "extras.css")
+ for (f in theme_files) {
+ .copy_pkg_file(file.path("quarto/theme", f), file.path("theme", f), path, force)
+ }
+
+ # LaTeX
+ latex_files <- c("civilytics.tex", "civilytics-title.tex")
+ for (f in latex_files) {
+ .copy_pkg_file(file.path("quarto/latex", f), file.path("latex", f), path, force)
+ }
+
+ # Typst
+ .copy_pkg_file(
+ "quarto/typst/civilytics-typst.typ",
+ "typst/civilytics-typst.typ",
+ path, force
+ )
+
+ # Logos — for _brand.yml (expects assets/logo/)
+ .copy_logos("assets/logo", path, force)
+
+ # Example report
+ .copy_pkg_file("quarto/examples/report.qmd", "examples/report.qmd", path, force)
+
+ message("\nDone! Example YAML for a report:\n")
+ message("---")
+ message("format:")
+ message(" html:")
+ message(" theme: theme/civilytics.scss")
+ message(" css: theme/extras.css")
+ message(" pdf:")
+ message(" include-in-header: latex/civilytics.tex")
+ message(" include-before-body: latex/civilytics-title.tex")
+ message(" typst:")
+ message(" template: typst/civilytics-typst.typ")
+ message("---")
+ message("\nSee examples/report.qmd for a complete example.")
+ invisible(NULL)
+}
+
+
+#' Install the Civilytics brand file only
+#'
+#' Copies `_brand.yml` and the logo assets it references into your
+#' Quarto project. This gives you Quarto 1.5+ automatic brand
+#' styling (colors, fonts, logos) without the full theme SCSS.
+#'
+#' @inheritParams use_civilytics_revealjs
+#'
+#' @return Invisible `NULL`.
+#' @export
+#'
+#' @examples
+#' \dontrun{
+#' use_civilytics_brand()
+#' }
+use_civilytics_brand <- function(path = ".", force = FALSE) {
+ path <- normalizePath(path, mustWork = TRUE)
+ message("Installing Civilytics brand file into: ", path)
+
+ .copy_pkg_file("quarto/_brand.yml", "_brand.yml", path, force)
+ .copy_logos("assets/logo", path, force)
+
+ message("\nDone! Quarto 1.5+ will auto-apply brand colors, fonts, and logos.")
+ message("See: https://quarto.org/docs/authoring/brand.html")
+ invisible(NULL)
+}
diff --git a/inst/quarto/_brand.yml b/inst/quarto/_brand.yml
new file mode 100644
index 0000000..73ad02a
--- /dev/null
+++ b/inst/quarto/_brand.yml
@@ -0,0 +1,66 @@
+# Quarto 1.5+ brand file
+# Optional but recommended — Quarto auto-applies these to HTML, PDF, and Revealjs.
+# https://quarto.org/docs/authoring/brand.html
+
+brand:
+ meta:
+ name: Civilytics Consulting
+ description: Turning public data into clear, actionable analysis for public good.
+
+ logo:
+ small: assets/logo/civilytics-mark.svg
+ medium: assets/logo/civilytics-wordmark.svg
+ large: assets/logo/civilytics-wordmark.svg
+
+ color:
+ palette:
+ paper: "#FAF7F2"
+ paper-2: "#F2EDE4"
+ ink: "#0E1A2B"
+ ink-2: "#2B3A52"
+ ink-3: "#5A6A82"
+ navy: "#22406A"
+ ember: "#C25311"
+ ember-2: "#923D00"
+ teal: "#1F6F70"
+ plum: "#6B3A5E"
+ moss: "#4A6B2F"
+ brass: "#B8751C"
+ background: paper
+ foreground: ink
+ primary: navy
+ secondary: ember
+ success: moss
+ info: navy
+ warning: "#9A5F18"
+ danger: "#A6271D"
+ light: paper-2
+ dark: ink
+ link: navy
+
+ typography:
+ fonts:
+ - family: "Source Serif 4"
+ source: google
+ - family: "Inter"
+ source: google
+ - family: "JetBrains Mono"
+ source: google
+ - family: "Libre Franklin"
+ source: google
+ base:
+ family: Inter
+ size: 1rem
+ line-height: 1.65
+ headings:
+ family: "Libre Franklin"
+ weight: 800
+ style: normal
+ line-height: 1.08
+ color: ink
+ monospace:
+ family: "JetBrains Mono"
+ size: 0.92em
+ link:
+ color: navy
+ decoration: underline
diff --git a/inst/quarto/examples/report.qmd b/inst/quarto/examples/report.qmd
new file mode 100644
index 0000000..76aecf0
--- /dev/null
+++ b/inst/quarto/examples/report.qmd
@@ -0,0 +1,80 @@
+---
+title: "Who pays when rent outpaces wages?"
+subtitle: "A 12-county analysis of cost-burdened renter households, 2019–2024."
+author:
+ - name: "Civilytics Research"
+ affiliation: "Civilytics Consulting"
+date: "2026-04-15"
+abstract: |
+ We combine ACS 5-year microdata with HUD Fair Market Rent estimates
+ to track changes in rent burden across 12 metropolitan counties.
+ Between 2019 and 2024, the share of renter households spending more
+ than 30% of income on housing rose by 6.4 percentage points; the
+ increase was concentrated in counties where median wages stagnated.
+categories: [Housing, Affordability, ACS]
+format:
+ html:
+ theme: ../theme/civilytics.scss
+ css: ../theme/extras.css
+ toc: true
+ toc-location: right
+ typst:
+ template: ../typst/civilytics-typst.typ
+ pdf:
+ include-in-header: ../latex/civilytics.tex
+ include-before-body: ../latex/civilytics-title.tex
+execute:
+ echo: false
+ warning: false
+---
+
+## Findings at a glance
+
+::: {.stat-callout}
+::: {}
+[6.4 pp]{.stat-num}
+[Increase in rent-burdened share, 2019→2024]{.stat-label .accent}
+:::
+::: {}
+[1 in 2]{.stat-num}
+[Renters in the studied counties now cost-burdened]{.stat-label}
+:::
+::: {}
+[$412]{.stat-num}
+[Median monthly rent gap, lowest-wage quintile]{.stat-label}
+:::
+:::
+
+[Methodology note]{.eyebrow}
+
+## Background
+
+Rent has outpaced wages in every county we studied. In some, the gap is
+modest and recent; in others, it has been widening for a decade. This
+report disaggregates the trend along three lines: county, income
+quintile, and household composition.
+
+```{r}
+#| label: fig-trend
+#| fig-cap: "Cost-burdened renter share, 12 metro counties, 2019–2024."
+#| fig-width: 7
+#| fig-height: 4.2
+library(ggplot2); library(civilytics)
+ggplot(economics, aes(date, unemploy / pop)) +
+ geom_line(color = "#C25311", linewidth = 1) +
+ labs(title = "Cost-burdened renter share, 2019–2024",
+ subtitle = "Share spending more than 30% of household income on rent",
+ x = NULL, y = NULL,
+ caption = "Source: ACS 5-yr microdata; Civilytics analysis.") +
+ scale_y_continuous(labels = scales::percent_format()) +
+ theme_civilytics()
+```
+
+> Across all 12 counties, the steepest increases came in places where
+> median wages were already in the bottom quartile of the metro region.
+
+## Recommendations
+
+1. Targeted rental assistance keyed to local wage trajectories.
+2. Quarterly publication of rent-burden indicators by county.
+3. Coordinated reporting between HUD and county human-services offices.
diff --git a/inst/quarto/examples/slides.qmd b/inst/quarto/examples/slides.qmd
new file mode 100644
index 0000000..c70a6c4
--- /dev/null
+++ b/inst/quarto/examples/slides.qmd
@@ -0,0 +1,65 @@
+---
+title: "Quarterly Analysis"
+subtitle: "A brief look at the numbers."
+author: "Civilytics Consulting"
+date: today
+format: civilytics-reveal-revealjs
+---
+
+## Findings at a glance
+
+::: {.columns}
+::: {.column width="50%"}
+
+[Key metric]{.stat-number}
+
+- Point one
+- Point two
+- Point three
+
+:::
+::: {.column width="50%"}
+
+```{r}
+#| fig-width: 6
+#| fig-height: 4
+library(ggplot2)
+library(civilytics)
+
+ggplot(mpg, aes(displ, hwy, colour = class)) +
+ geom_point(size = 2.5) +
+ scale_color_civilytics() +
+ theme_civilytics_slide() +
+ labs(title = "Engine Size vs Fuel Economy",
+ x = "Displacement (L)", y = "Highway MPG")
+```
+
+:::
+:::
+
+## Section divider {.section}
+
+Big idea goes here.
+
+## Data table
+
+| County | Burden (%) | Change |
+|:-------------|:----------:|-------:|
+| Dane | 38.2 | +4.1 |
+| Milwaukee | 52.7 | +7.3 |
+| Hennepin | 41.5 | +3.9 |
+
+: Cost-burdened renter share, selected counties.
+
+## A blockquote slide {.pullquote}
+
+> Rent has outpaced wages in every county we studied.
+
+Civilytics Research, 2026
+
+## Thank you {.thank-you}
+
+Questions?
+
+- jared@civilytics.com
+- civilytics.consulting
diff --git a/inst/quarto/extensions/civilytics-reveal/_extension.yml b/inst/quarto/extensions/civilytics-reveal/_extension.yml
new file mode 100644
index 0000000..52005e7
--- /dev/null
+++ b/inst/quarto/extensions/civilytics-reveal/_extension.yml
@@ -0,0 +1,56 @@
+title: Civilytics Reveal
+author: Civilytics Consulting
+version: 1.0.0
+quarto-required: ">=1.4.0"
+contributes:
+ formats:
+ revealjs:
+ # ---- Theme ----
+ theme: [default, civilytics.scss]
+
+ # ---- Slide geometry ----
+ width: 1280
+ height: 720
+ margin: 0
+ min-scale: 0.2
+ max-scale: 2.0
+ center: false
+
+ # ---- Navigation / chrome ----
+ controls: true
+ controls-layout: edges
+ progress: true
+ slide-number: "c/t"
+ hash-type: number
+ history: true
+ navigation-mode: linear
+ transition: fade
+ transition-speed: fast
+ background-transition: fade
+
+ # ---- Code ----
+ highlight-style: github
+ code-line-numbers: true
+ code-copy: true
+ code-overflow: wrap
+
+ # ---- Math ----
+ html-math-method: mathjax
+
+ # ---- Tables ----
+ tbl-cap-location: top
+
+ # ---- Links / fragments ----
+ link-external-newwindow: true
+ incremental: false
+
+ # ---- Brand chrome (injected by civilytics.js) ----
+ include-in-header:
+ - file: civilytics-head.html
+ include-after-body:
+ - file: civilytics-after.html
+
+ # ---- Footer / logo ----
+ # Override in the document front-matter if desired:
+ # footer: "Civilytics Consulting · 2026"
+ # logo: _extensions/civilytics-reveal/assets/civilytics-mark.svg
diff --git a/inst/quarto/extensions/civilytics-reveal/civilytics-after.html b/inst/quarto/extensions/civilytics-reveal/civilytics-after.html
new file mode 100644
index 0000000..ad995c4
--- /dev/null
+++ b/inst/quarto/extensions/civilytics-reveal/civilytics-after.html
@@ -0,0 +1,80 @@
+
+
diff --git a/inst/quarto/extensions/civilytics-reveal/civilytics-head.html b/inst/quarto/extensions/civilytics-reveal/civilytics-head.html
new file mode 100644
index 0000000..acf404a
--- /dev/null
+++ b/inst/quarto/extensions/civilytics-reveal/civilytics-head.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/inst/quarto/extensions/civilytics-reveal/civilytics.css b/inst/quarto/extensions/civilytics-reveal/civilytics.css
new file mode 100644
index 0000000..0fadd6b
--- /dev/null
+++ b/inst/quarto/extensions/civilytics-reveal/civilytics.css
@@ -0,0 +1,1014 @@
+/* Civilytics Reveal.js theme — plain CSS build. */
+
+/* ==========================================================
+ Civilytics Reveal.js theme — rules
+ Editorial, serif-led slides. Designed for Quarto + Pandoc.
+ Tokens mirror /colors_and_type.css from the Civilytics DS.
+ ========================================================== */
+
+@import url('https://fonts.googleapis.com/css2?family=Libre+Franklin:ital,wght@0,400;0,500;0,600;0,700;0,800;0,900;1,400;1,700&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600;8..60,700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
+
+:root {
+ /* Civilytics tokens — keep names in sync with the design system */
+ --paper: #FAF7F2;
+ --paper-2: #F2EDE4;
+ --paper-3: #E6DFD1;
+ --rule: #D6CEBD;
+ --rule-strong: #B8AE97;
+
+ --ink: #0E1A2B;
+ --ink-2: #2B3A52;
+ --ink-3: #5A6A82;
+ --ink-4: #8C97AB;
+
+ --navy-900: #0E1A2B;
+ --navy-700: #1A2E4A;
+ --navy-600: #22406A;
+ --navy-500: #2E5590;
+ --navy-100: #DDE6F2;
+
+ --accent-900: #451A00;
+ --accent-700: #923D00;
+ --accent-600: #C25311;
+ --accent-500: #DB6C25;
+ --accent-300: #F2A976;
+ --accent-200: #F8C8A3;
+ --accent-100: #FBE0C6;
+
+ --teal-600: #1F6F70;
+ --plum-600: #6B3A5E;
+ --moss-600: #4A6B2F;
+ --brass-600: #B8751C;
+
+ --font-display: "Libre Franklin", "Franklin Gothic", "Inter", system-ui, sans-serif;
+ --font-serif: "Source Serif 4", "Source Serif Pro", Georgia, serif;
+ --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
+ --font-mono: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
+
+ --tracking-caps: 0.08em;
+}
+
+/* -----------------------------------------------------------
+ Reveal root & slide chrome
+ ----------------------------------------------------------- */
+.reveal-viewport {
+ background: var(--paper);
+}
+
+.reveal {
+ font-family: var(--font-sans);
+ font-size: 32px;
+ font-weight: 400;
+ color: var(--ink);
+ letter-spacing: 0;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+.reveal ::selection { background: var(--accent-200); color: var(--ink); }
+.reveal ::-moz-selection { background: var(--accent-200); color: var(--ink); }
+
+.reveal .slides {
+ text-align: left;
+}
+
+.reveal .slides > section,
+.reveal .slides > section > section {
+ padding: 48px 72px;
+ line-height: 1.45;
+ font-weight: inherit;
+ box-sizing: border-box;
+ height: 100%;
+}
+
+/* -----------------------------------------------------------
+ Headings
+ ----------------------------------------------------------- */
+.reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 {
+ font-family: var(--font-display);
+ color: var(--ink);
+ font-weight: 800;
+ line-height: 1.08;
+ letter-spacing: -0.025em;
+ text-transform: none;
+ text-wrap: balance;
+ margin: 0 0 0.5em;
+}
+
+.reveal h1 { font-size: 2.4em; font-weight: 900; letter-spacing: -0.035em; line-height: 1.0; }
+.reveal h2 { font-size: 1.7em; font-weight: 800; letter-spacing: -0.03em; }
+.reveal h3 { font-size: 1.3em; font-weight: 700; letter-spacing: -0.022em; }
+.reveal h4 { font-size: 1.1em; font-weight: 700; letter-spacing: -0.018em; }
+
+/* Eyebrow kicker — used above headlines on many layouts */
+.reveal .eyebrow,
+.reveal [data-eyebrow]::before {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--font-sans);
+ font-size: 0.5em;
+ font-weight: 600;
+ color: var(--accent-600);
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ margin: 0 0 18px;
+}
+.reveal .eyebrow::before {
+ content: "";
+ display: inline-block;
+ width: 28px;
+ height: 2px;
+ background: var(--accent-600);
+}
+.reveal [data-eyebrow]::before {
+ content: attr(data-eyebrow);
+}
+
+/* -----------------------------------------------------------
+ Body copy, lists, links
+ ----------------------------------------------------------- */
+.reveal p {
+ font-family: var(--font-sans);
+ line-height: 1.55;
+ margin: 0 0 0.75em;
+ text-wrap: pretty;
+}
+
+.reveal .lead,
+.reveal p.lead {
+ font-family: var(--font-serif);
+ font-size: 1.15em;
+ font-weight: 400;
+ line-height: 1.5;
+ color: var(--ink);
+ max-width: 26em;
+ padding-left: 0.75em;
+ border-left: 2px solid var(--accent-600);
+ margin: 0.5em 0 1em;
+}
+
+.reveal ul, .reveal ol {
+ display: block;
+ margin: 0.25em 0 0.75em;
+ padding-left: 1.3em;
+}
+.reveal li {
+ margin: 0.35em 0;
+ line-height: 1.5;
+}
+.reveal ul { list-style: none; }
+.reveal ul > li { position: relative; }
+.reveal ul > li::before {
+ content: "";
+ position: absolute;
+ left: -1.1em;
+ top: 0.7em;
+ width: 10px;
+ height: 2px;
+ background: var(--accent-600);
+}
+.reveal ol { list-style: decimal; }
+.reveal ol > li::marker {
+ color: var(--accent-600);
+ font-family: var(--font-mono);
+ font-weight: 600;
+}
+
+.reveal a {
+ color: var(--navy-600);
+ text-decoration: underline;
+ text-decoration-thickness: 1px;
+ text-underline-offset: 3px;
+ transition: color 120ms ease-out, text-decoration-thickness 120ms ease-out;
+}
+.reveal a:hover {
+ color: var(--navy-700);
+ text-decoration-thickness: 2px;
+}
+
+.reveal hr {
+ border: none;
+ border-top: 1px solid var(--rule);
+ margin: 0.75em 0;
+}
+
+.reveal strong, .reveal b { font-weight: 700; color: var(--ink); }
+.reveal em, .reveal i { font-style: italic; }
+
+/* Small caps helper — for metadata rows */
+.reveal .smallcaps,
+.reveal .meta {
+ font-family: var(--font-sans);
+ font-size: 0.55em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--ink-3);
+}
+
+/* -----------------------------------------------------------
+ Block quote / pullquote
+ ----------------------------------------------------------- */
+.reveal blockquote {
+ font-family: var(--font-serif);
+ font-size: 1.1em;
+ font-style: italic;
+ line-height: 1.4;
+ color: var(--ink);
+ border-left: 3px solid var(--accent-600);
+ padding: 0.25em 0 0.25em 1em;
+ margin: 0.75em 0;
+ background: transparent;
+ box-shadow: none;
+ width: auto;
+}
+
+/* Big pullquote layout — .pullquote on a section */
+.reveal section.pullquote {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+.reveal section.pullquote blockquote {
+ font-size: 1.8em;
+ font-style: italic;
+ line-height: 1.2;
+ border-left: none;
+ padding: 0;
+ max-width: 22em;
+ letter-spacing: -0.01em;
+}
+.reveal section.pullquote blockquote::before {
+ content: "\201C";
+ display: block;
+ font-family: var(--font-serif);
+ font-size: 2.2em;
+ line-height: 0.9;
+ color: var(--accent-600);
+ margin-bottom: 0.1em;
+ font-style: normal;
+}
+.reveal section.pullquote .attribution,
+.reveal section.pullquote footer {
+ margin-top: 1em;
+ font-family: var(--font-sans);
+ font-size: 0.55em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--ink-3);
+ font-style: normal;
+}
+.reveal section.pullquote .attribution::before {
+ content: "— ";
+ color: var(--accent-600);
+}
+
+/* -----------------------------------------------------------
+ Code
+ ----------------------------------------------------------- */
+.reveal code {
+ font-family: var(--font-mono);
+ font-size: 0.85em;
+ color: var(--accent-700);
+ background: var(--paper-2);
+ padding: 0.08em 0.35em;
+ border-radius: 2px;
+ font-feature-settings: "tnum" 1, "zero" 1;
+}
+
+.reveal pre {
+ font-family: var(--font-mono);
+ font-size: 0.55em;
+ line-height: 1.5;
+ background: var(--ink);
+ color: var(--paper);
+ padding: 1em 1.25em;
+ border-radius: 8px;
+ box-shadow: none;
+ width: 100%;
+ max-width: 100%;
+ overflow: auto;
+ margin: 0.5em 0;
+}
+.reveal pre code {
+ background: transparent;
+ color: inherit;
+ padding: 0;
+ font-size: 1em;
+ display: block;
+ overflow: visible;
+ white-space: pre;
+ max-height: none;
+}
+
+/* Quarto code-line-number + highlight markers */
+.reveal pre code .hljs-ln-numbers,
+.reveal pre code .code-line-number {
+ color: var(--ink-4);
+ padding-right: 1em;
+ user-select: none;
+}
+.reveal div.sourceCode {
+ background: transparent;
+ border: none;
+ margin: 0.5em 0;
+}
+.reveal code span.co { color: #8C97AB; font-style: italic; } /* comments */
+.reveal code span.st { color: #F2A976; } /* strings */
+.reveal code span.dv,
+.reveal code span.fl { color: #F8C8A3; } /* numbers */
+.reveal code span.kw { color: #EA8A49; font-weight: 600; } /* keywords */
+.reveal code span.cf { color: #EA8A49; font-weight: 600; } /* control flow */
+.reveal code span.fu { color: #B3C6E0; } /* function */
+.reveal code span.op { color: #D6CEBD; } /* operator */
+.reveal code span.cn,
+.reveal code span.va { color: #7CB3B3; } /* variables */
+.reveal code span.at { color: #7CB3B3; }
+
+/* Quarto line-highlighting */
+.reveal pre code.sourceCode > span.has-line-highlight {
+ background: rgba(248, 200, 163, 0.14);
+}
+
+/* -----------------------------------------------------------
+ Tables
+ ----------------------------------------------------------- */
+.reveal table {
+ font-family: var(--font-sans);
+ font-size: 0.7em;
+ border-collapse: collapse;
+ margin: 0.5em 0;
+ width: 100%;
+ font-variant-numeric: tabular-nums;
+}
+.reveal table th,
+.reveal table td {
+ padding: 0.55em 0.9em;
+ text-align: left;
+ border: none;
+ border-bottom: 1px solid var(--rule);
+ vertical-align: baseline;
+}
+.reveal table th {
+ font-family: var(--font-sans);
+ font-size: 0.82em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--ink-3);
+ border-bottom: 2px solid var(--ink);
+ padding-bottom: 0.4em;
+ background: transparent;
+}
+.reveal table tbody tr:nth-child(even) td {
+ background: var(--paper-2);
+}
+.reveal table tbody tr:last-child td {
+ border-bottom: 2px solid var(--ink);
+}
+/* Numeric columns — add .numeric to a
/ | */
+.reveal table td.numeric,
+.reveal table th.numeric,
+.reveal table td[align="right"],
+.reveal table th[align="right"] {
+ text-align: right;
+ font-family: var(--font-mono);
+ font-variant-numeric: tabular-nums;
+}
+.reveal table caption {
+ caption-side: top;
+ text-align: left;
+ font-family: var(--font-sans);
+ font-size: 0.55em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--accent-600);
+ margin-bottom: 0.5em;
+}
+
+/* -----------------------------------------------------------
+ Images & figures
+ ----------------------------------------------------------- */
+.reveal img,
+.reveal section img {
+ background: transparent;
+ border: none;
+ box-shadow: none;
+ margin: 0.25em 0;
+ max-width: 100%;
+ height: auto;
+}
+.reveal figure {
+ margin: 0.5em 0;
+}
+.reveal figure img {
+ display: block;
+ border: 1px solid var(--rule);
+}
+.reveal figure figcaption,
+.reveal .caption {
+ font-family: var(--font-sans);
+ font-size: 0.55em;
+ line-height: 1.4;
+ color: var(--ink-3);
+ margin-top: 0.5em;
+ max-width: 42em;
+}
+.reveal figure figcaption strong,
+.reveal .caption strong {
+ color: var(--accent-600);
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ font-size: 0.9em;
+ display: inline-block;
+ margin-right: 0.5em;
+}
+
+/* -----------------------------------------------------------
+ TITLE SLIDE
+ Quarto renders the title slide with #title-slide +
+ ----------------------------------------------------------- */
+.reveal #title-slide,
+.reveal section.title-slide {
+ padding: 72px 80px;
+ display: flex !important;
+ flex-direction: column;
+ justify-content: space-between;
+ text-align: left;
+ background:
+ linear-gradient(180deg, var(--paper) 0%, var(--paper) 58%, var(--paper-2) 100%);
+}
+
+.reveal #title-slide .civilytics-mark,
+.reveal section.title-slide .civilytics-mark {
+ display: block;
+ width: 72px;
+ height: 72px;
+ margin-bottom: 32px;
+ object-fit: contain;
+}
+
+/* Civilytics wordmark lockup — bold serif "Civilytics" + inline pulse accent.
+ Rendered as an tag pointing at the official wordmark SVG asset
+ (assets/civilytics-wordmark.svg for ink-on-paper slides,
+ assets/civilytics-wordmark-reverse.svg for paper-on-ink slides).
+ Source viewBox is 558×160 (aspect ratio ≈ 3.4875). */
+img.civilytics-wordmark,
+.reveal img.civilytics-wordmark,
+.reveal .civilytics-wordmark {
+ display: block;
+ height: 48px;
+ width: 167.4px; /* 558/160 aspect * 48px height */
+ flex: 0 0 auto;
+ align-self: flex-start;
+ object-fit: contain;
+ margin: 0 0 36px;
+}
+
+.reveal #title-slide .civilytics-wordmark,
+.reveal section.title-slide .civilytics-wordmark {
+ height: 48px;
+ width: 167.4px;
+ margin: 0 0 36px;
+}
+
+.reveal #title-slide .title,
+.reveal section.title-slide h1.title,
+.reveal section.title-slide h1:first-of-type {
+ font-family: var(--font-display);
+ font-size: 2.6em;
+ font-weight: 900;
+ letter-spacing: -0.035em;
+ line-height: 0.98;
+ color: var(--ink);
+ margin: 0 0 0.3em;
+ max-width: 18em;
+ text-wrap: balance;
+}
+.reveal #title-slide .subtitle,
+.reveal section.title-slide p.subtitle,
+.reveal section.title-slide .subtitle {
+ font-family: var(--font-serif);
+ font-size: 1.05em;
+ font-weight: 400;
+ font-style: italic;
+ line-height: 1.35;
+ color: var(--ink-2);
+ margin: 0 0 1em;
+ max-width: 28em;
+ padding-left: 0.75em;
+ border-left: 2px solid var(--accent-600);
+}
+
+/* Author / date / affiliation block */
+.reveal #title-slide .quarto-title-authors,
+.reveal #title-slide .quarto-title-author-name,
+.reveal section.title-slide .author,
+.reveal section.title-slide .date,
+.reveal section.title-slide .affiliation {
+ font-family: var(--font-sans);
+ color: var(--ink-2);
+}
+.reveal #title-slide .author,
+.reveal #title-slide .quarto-title-author-name,
+.reveal section.title-slide .author {
+ font-size: 0.7em;
+ font-weight: 600;
+ color: var(--ink);
+ margin: 0.2em 0 0;
+ letter-spacing: 0;
+}
+.reveal #title-slide .affiliation,
+.reveal #title-slide .quarto-title-affiliation,
+.reveal section.title-slide .affiliation {
+ font-size: 0.55em;
+ font-weight: 500;
+ color: var(--ink-3);
+ letter-spacing: var(--tracking-caps);
+ text-transform: uppercase;
+ margin: 0.15em 0 0;
+}
+.reveal #title-slide .date,
+.reveal section.title-slide .date {
+ font-size: 0.5em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--accent-600);
+ margin-top: 1em;
+ padding-top: 0.8em;
+ border-top: 3px double var(--ink);
+ display: inline-block;
+}
+
+/* Editorial thick-top / thin-bottom rule block around title */
+.reveal section.title-slide .quarto-title-block,
+.reveal section.title-slide .title-block {
+ border-top: 6px solid var(--ink);
+ border-bottom: 1px solid var(--rule);
+ padding: 0.5em 0 1em;
+}
+
+/* -----------------------------------------------------------
+ SECTION DIVIDER — or
+ Quarto's {.section} header
+ ----------------------------------------------------------- */
+.reveal section.section-divider,
+.reveal section.section {
+ background: var(--ink);
+ color: var(--paper);
+ display: flex !important;
+ flex-direction: column;
+ justify-content: center;
+ padding: 80px 96px;
+}
+.reveal section.section-divider::before,
+.reveal section.section::before {
+ content: "";
+ display: block;
+ width: 72px;
+ height: 2px;
+ background: var(--accent-600);
+ margin-bottom: 24px;
+}
+.reveal section.section-divider h1,
+.reveal section.section-divider h2,
+.reveal section.section-divider h3,
+.reveal section.section h1,
+.reveal section.section h2,
+.reveal section.section h3 {
+ color: var(--paper);
+ font-size: 2.8em;
+ font-weight: 900;
+ letter-spacing: -0.035em;
+ line-height: 1.0;
+ max-width: 18em;
+}
+.reveal section.section-divider .eyebrow,
+.reveal section.section .eyebrow {
+ color: var(--accent-300);
+}
+.reveal section.section-divider .eyebrow::before,
+.reveal section.section .eyebrow::before {
+ background: var(--accent-300);
+}
+.reveal section.section-divider p,
+.reveal section.section p {
+ color: var(--paper);
+ opacity: 0.82;
+ font-family: var(--font-serif);
+ font-size: 1.05em;
+ font-style: italic;
+ max-width: 28em;
+}
+.reveal section.section-divider .section-number,
+.reveal section.section .section-number {
+ font-family: var(--font-mono);
+ font-size: 0.6em;
+ font-weight: 600;
+ color: var(--accent-300);
+ letter-spacing: var(--tracking-caps);
+ text-transform: uppercase;
+ margin-bottom: 0.5em;
+}
+
+/* -----------------------------------------------------------
+ FULL-BLEED IMAGE —
+ ----------------------------------------------------------- */
+.reveal section.full-bleed {
+ padding: 0 !important;
+}
+.reveal section.full-bleed .caption,
+.reveal section.full-bleed figcaption {
+ position: absolute;
+ left: 48px;
+ right: 48px;
+ bottom: 48px;
+ background: var(--paper);
+ padding: 14px 18px;
+ border-left: 3px solid var(--accent-600);
+ font-family: var(--font-sans);
+ font-size: 0.5em;
+ line-height: 1.45;
+ color: var(--ink);
+ max-width: 36em;
+ box-shadow: 0 4px 12px rgba(14, 26, 43, 0.12);
+}
+.reveal section.full-bleed .caption strong,
+.reveal section.full-bleed figcaption strong {
+ display: block;
+ font-size: 0.85em;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--accent-600);
+ margin-bottom: 4px;
+}
+
+/* -----------------------------------------------------------
+ BIG STAT / DATA CALLOUT — .big-stat section or .stat block
+ ----------------------------------------------------------- */
+.reveal .stat-number {
+ font-family: var(--font-serif);
+ font-weight: 700;
+ font-size: 3.6em;
+ line-height: 1;
+ letter-spacing: -0.02em;
+ font-variant-numeric: tabular-nums;
+ color: var(--ink);
+ margin: 0 0 0.1em;
+ display: block;
+}
+.reveal .stat-label {
+ font-family: var(--font-sans);
+ font-size: 0.55em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--ink-3);
+}
+.reveal .stat-note {
+ font-family: var(--font-serif);
+ font-style: italic;
+ font-size: 0.9em;
+ color: var(--ink-2);
+ max-width: 22em;
+ margin-top: 0.6em;
+}
+.reveal section.big-stat {
+ display: flex !important;
+ flex-direction: column;
+ justify-content: center;
+}
+.reveal section.big-stat .stat-number {
+ font-size: 5em;
+}
+
+/* Stat grid — multiple stats side-by-side */
+.reveal .stat-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 2em 3em;
+ margin: 1em 0;
+}
+.reveal .stat-grid .stat {
+ border-top: 3px solid var(--ink);
+ padding-top: 0.6em;
+}
+.reveal .stat-grid .stat .stat-number { font-size: 2.8em; }
+
+/* -----------------------------------------------------------
+ TWO-COLUMN — Quarto renders {.columns}{.column width=50%}
+ ----------------------------------------------------------- */
+.reveal .columns {
+ display: flex;
+ gap: 3em;
+ align-items: flex-start;
+ margin: 0.25em 0;
+}
+.reveal .column {
+ flex: 1;
+ min-width: 0;
+}
+.reveal .column > :first-child { margin-top: 0; }
+
+/* Hand-rolled two-col for pandoc (non-Quarto) users */
+.reveal .two-col {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 3em;
+ align-items: start;
+}
+.reveal .two-col--wide-left { grid-template-columns: 2fr 1fr; }
+.reveal .two-col--wide-right { grid-template-columns: 1fr 2fr; }
+
+/* -----------------------------------------------------------
+ AGENDA / TOC slide —
+ Pair with an ordered list; we restyle it.
+ ----------------------------------------------------------- */
+.reveal section.agenda ol {
+ list-style: none;
+ counter-reset: agenda;
+ padding: 0;
+ margin: 0.5em 0;
+}
+.reveal section.agenda ol > li {
+ counter-increment: agenda;
+ display: grid;
+ grid-template-columns: 2.4em 1fr auto;
+ gap: 0.6em 1.2em;
+ align-items: baseline;
+ padding: 0.55em 0;
+ border-top: 1px solid var(--rule);
+ font-family: var(--font-serif);
+ font-size: 1.05em;
+}
+.reveal section.agenda ol > li:last-child { border-bottom: 1px solid var(--rule); }
+.reveal section.agenda ol > li::before {
+ content: counter(agenda, decimal-leading-zero);
+ font-family: var(--font-mono);
+ font-weight: 600;
+ font-size: 0.7em;
+ color: var(--accent-600);
+ letter-spacing: var(--tracking-caps);
+}
+.reveal section.agenda ol > li .meta,
+.reveal section.agenda ol > li em {
+ font-family: var(--font-sans);
+ font-style: normal;
+ font-size: 0.7em;
+ color: var(--ink-3);
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ justify-self: end;
+}
+
+/* -----------------------------------------------------------
+ THANK-YOU / END slide —
+ ----------------------------------------------------------- */
+.reveal section.thank-you {
+ background: var(--ink);
+ color: var(--paper);
+ display: flex !important;
+ flex-direction: column;
+ justify-content: center;
+ padding: 80px 96px;
+}
+.reveal section.thank-you .civilytics-mark {
+ display: block;
+ width: 56px;
+ height: 56px;
+ margin-bottom: 28px;
+ object-fit: contain;
+}
+.reveal section.thank-you .civilytics-wordmark {
+ height: 40px;
+ width: 139.5px; /* 558/160 aspect * 40 */
+ flex: 0 0 auto;
+ align-self: flex-start;
+ margin: 0 0 32px;
+}
+.reveal section.thank-you h1,
+.reveal section.thank-you h2 {
+ color: var(--paper);
+ font-size: 3em;
+ font-weight: 900;
+ letter-spacing: -0.035em;
+ margin-bottom: 0.2em;
+}
+.reveal section.thank-you p {
+ color: var(--paper);
+ opacity: 0.85;
+ font-family: var(--font-serif);
+ font-size: 1.1em;
+ font-style: italic;
+ max-width: 26em;
+}
+.reveal section.thank-you .contact,
+.reveal section.thank-you ul.contact {
+ list-style: none;
+ padding: 0;
+ margin: 1.5em 0 0;
+ font-family: var(--font-sans);
+ font-size: 0.6em;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--accent-300);
+}
+.reveal section.thank-you .contact li,
+.reveal section.thank-you ul.contact li {
+ margin: 0.3em 0;
+}
+.reveal section.thank-you .contact li::before,
+.reveal section.thank-you ul.contact li::before { display: none; }
+.reveal section.thank-you a { color: var(--paper); text-decoration: underline; text-decoration-color: var(--accent-600); }
+.reveal section.thank-you a:hover { color: var(--accent-300); }
+
+/* -----------------------------------------------------------
+ REFERENCES / SOURCES — small, tight
+ ----------------------------------------------------------- */
+.reveal .references,
+.reveal section.references {
+ font-size: 0.72em;
+}
+.reveal section.references h2 { font-size: 1.4em; }
+.reveal .references ol,
+.reveal .references ul {
+ padding-left: 1.4em;
+}
+.reveal .references li {
+ font-family: var(--font-sans);
+ font-size: 0.95em;
+ line-height: 1.4;
+ color: var(--ink-2);
+ margin: 0.35em 0;
+}
+.reveal .references li::before { display: none; }
+
+/* Footnotes (Pandoc / Quarto) */
+.reveal .footnotes,
+.reveal div.footnotes {
+ font-size: 0.6em;
+ color: var(--ink-3);
+ border-top: 1px solid var(--rule);
+ padding-top: 0.6em;
+ margin-top: 1em;
+}
+.reveal a.footnote-ref,
+.reveal sup a {
+ color: var(--accent-600);
+ text-decoration: none;
+ font-family: var(--font-mono);
+ font-size: 0.7em;
+ font-weight: 600;
+ margin: 0 0.15em;
+}
+
+/* -----------------------------------------------------------
+ MATH — KaTeX / MathJax
+ ----------------------------------------------------------- */
+.reveal .katex,
+.reveal .MathJax {
+ font-size: 1em !important;
+ color: var(--ink);
+}
+.reveal .katex-display,
+.reveal mjx-container[display="true"] {
+ margin: 0.5em 0 !important;
+ padding: 0.5em 0;
+ border-left: 2px solid var(--accent-600);
+ padding-left: 1em;
+}
+
+/* -----------------------------------------------------------
+ CALLOUTS (Quarto)
+ ----------------------------------------------------------- */
+.reveal div.callout {
+ border: 1px solid var(--rule);
+ border-left: 3px solid var(--accent-600);
+ border-radius: 4px;
+ background: var(--paper-2);
+ margin: 0.75em 0;
+ font-size: 0.78em;
+ overflow: hidden;
+}
+.reveal div.callout > div.callout-header {
+ background: transparent;
+ padding: 0.5em 0.9em 0.2em;
+ font-family: var(--font-sans);
+ font-weight: 600;
+ font-size: 0.9em;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--accent-600);
+}
+.reveal div.callout > div.callout-body,
+.reveal div.callout-body-container {
+ padding: 0.2em 0.9em 0.6em;
+}
+.reveal div.callout.callout-note { border-left-color: var(--navy-600); }
+.reveal div.callout.callout-note > div.callout-header { color: var(--navy-600); }
+.reveal div.callout.callout-warning { border-left-color: #9A5F18; }
+.reveal div.callout.callout-warning > div.callout-header { color: #9A5F18; }
+.reveal div.callout.callout-important,
+.reveal div.callout.callout-caution { border-left-color: #A6271D; }
+.reveal div.callout.callout-important > div.callout-header,
+.reveal div.callout.callout-caution > div.callout-header { color: #A6271D; }
+.reveal div.callout.callout-tip { border-left-color: var(--moss-600); }
+.reveal div.callout.callout-tip > div.callout-header { color: var(--moss-600); }
+
+/* -----------------------------------------------------------
+ SLIDE NUMBER + FOOTER BRAND CHROME
+ ----------------------------------------------------------- */
+.reveal .slide-number {
+ font-family: var(--font-mono);
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--ink-3);
+ background: transparent;
+ right: 24px;
+ bottom: 18px;
+ letter-spacing: var(--tracking-caps);
+}
+.reveal .slide-number a { color: inherit; }
+
+/* Brand footer mark — inserted by extension JS */
+.reveal .civilytics-footer {
+ position: absolute;
+ left: 24px;
+ bottom: 18px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--font-sans);
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--ink-3);
+ pointer-events: none;
+ z-index: 30;
+}
+.reveal .civilytics-footer img,
+.reveal .civilytics-footer svg {
+ width: 22px;
+ height: auto;
+ opacity: 0.85;
+ flex: 0 0 auto;
+}
+
+/* Hide chrome on title / section / thank-you / full-bleed slides */
+.reveal .slides > section.title-slide ~ *,
+.reveal #title-slide.present ~ .civilytics-footer { /* no-op, readable marker */ }
+.reveal .slides > section.title-slide.present ~ .controls,
+.reveal .slides > section.section-divider.present ~ .controls,
+.reveal .slides > section.thank-you.present ~ .controls { }
+body.civilytics-hide-chrome .civilytics-footer,
+body.civilytics-hide-chrome .slide-number {
+ display: none !important;
+}
+
+/* -----------------------------------------------------------
+ PROGRESS BAR
+ ----------------------------------------------------------- */
+.reveal .progress {
+ color: var(--accent-600);
+ height: 3px;
+ background: rgba(14, 26, 43, 0.08);
+}
+
+/* -----------------------------------------------------------
+ CONTROLS
+ ----------------------------------------------------------- */
+.reveal .controls {
+ color: var(--ink-3);
+}
+.reveal .controls button { color: var(--ink-3) !important; }
+.reveal .controls button:hover { color: var(--accent-600) !important; }
+
+/* -----------------------------------------------------------
+ PRINT / PDF EXPORT
+ ----------------------------------------------------------- */
+@media print {
+ .reveal .civilytics-footer,
+ .reveal .slide-number { color: var(--ink-3); }
+ .reveal section { break-inside: avoid; }
+}
+
+/* -----------------------------------------------------------
+ SMALL-SCREEN GUARDRAILS
+ Reveal scales slides, but the narrowest keep-legible margins.
+ ----------------------------------------------------------- */
+@media (max-width: 640px) {
+ .reveal .slides > section,
+ .reveal .slides > section > section {
+ padding: 32px 36px;
+ }
+ .reveal section.section-divider,
+ .reveal section.thank-you {
+ padding: 48px 48px;
+ }
+}
diff --git a/inst/quarto/extensions/civilytics-reveal/civilytics.scss b/inst/quarto/extensions/civilytics-reveal/civilytics.scss
new file mode 100644
index 0000000..c3921d9
--- /dev/null
+++ b/inst/quarto/extensions/civilytics-reveal/civilytics.scss
@@ -0,0 +1,1056 @@
+/*-- scss:defaults --*/
+
+// Civilytics Reveal.js theme — defaults
+// These map into Reveal/Quarto's theme variables so built-in
+// components (lists, links, code, etc.) pick up the brand.
+
+// Fonts — loaded by the extension's @import block below
+$font-family-sans-serif: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif !default;
+$font-family-serif: "Source Serif 4", "Source Serif Pro", Georgia, "Times New Roman", serif !default;
+$font-family-monospace: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace !default;
+$font-family-display: "Libre Franklin", "Franklin Gothic", "Inter", system-ui, sans-serif !default;
+
+$presentation-font-size-root: 32px !default;
+$presentation-font-smaller: 0.7 !default;
+$presentation-line-height: 1.45 !default;
+
+$body-bg: #FAF7F2 !default;
+$body-color: #0E1A2B !default;
+
+$link-color: #22406A !default;
+$link-color-hover: #1A2E4A !default;
+
+$selection-bg: #F8C8A3 !default;
+$selection-color: #0E1A2B !default;
+
+$presentation-heading-font: $font-family-display !default;
+$presentation-heading-color: #0E1A2B !default;
+$presentation-heading-font-weight: 800 !default;
+$presentation-heading-line-height: 1.08 !default;
+$presentation-heading-letter-spacing: -0.025em !default;
+$presentation-heading-text-transform: none !default;
+
+$presentation-h1-font-size: 2.4em !default;
+$presentation-h2-font-size: 1.7em !default;
+$presentation-h3-font-size: 1.3em !default;
+$presentation-h4-font-size: 1.1em !default;
+
+$code-block-bg: #0E1A2B !default;
+$code-block-color: #FAF7F2 !default;
+$code-color: #923D00 !default;
+
+/*-- scss:rules --*/
+
+/* ==========================================================
+ Civilytics Reveal.js theme — rules
+ Editorial, serif-led slides. Designed for Quarto + Pandoc.
+ Tokens mirror /colors_and_type.css from the Civilytics DS.
+ ========================================================== */
+
+@import url('https://fonts.googleapis.com/css2?family=Libre+Franklin:ital,wght@0,400;0,500;0,600;0,700;0,800;0,900;1,400;1,700&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600;8..60,700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
+
+:root {
+ /* Civilytics tokens — keep names in sync with the design system */
+ --paper: #FAF7F2;
+ --paper-2: #F2EDE4;
+ --paper-3: #E6DFD1;
+ --rule: #D6CEBD;
+ --rule-strong: #B8AE97;
+
+ --ink: #0E1A2B;
+ --ink-2: #2B3A52;
+ --ink-3: #5A6A82;
+ --ink-4: #8C97AB;
+
+ --navy-900: #0E1A2B;
+ --navy-700: #1A2E4A;
+ --navy-600: #22406A;
+ --navy-500: #2E5590;
+ --navy-100: #DDE6F2;
+
+ --accent-900: #451A00;
+ --accent-700: #923D00;
+ --accent-600: #C25311;
+ --accent-500: #DB6C25;
+ --accent-300: #F2A976;
+ --accent-200: #F8C8A3;
+ --accent-100: #FBE0C6;
+
+ --teal-600: #1F6F70;
+ --plum-600: #6B3A5E;
+ --moss-600: #4A6B2F;
+ --brass-600: #B8751C;
+
+ --font-display: "Libre Franklin", "Franklin Gothic", "Inter", system-ui, sans-serif;
+ --font-serif: "Source Serif 4", "Source Serif Pro", Georgia, serif;
+ --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
+ --font-mono: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
+
+ --tracking-caps: 0.08em;
+}
+
+/* -----------------------------------------------------------
+ Reveal root & slide chrome
+ ----------------------------------------------------------- */
+.reveal-viewport {
+ background: var(--paper);
+}
+
+.reveal {
+ font-family: var(--font-sans);
+ font-size: 32px;
+ font-weight: 400;
+ color: var(--ink);
+ letter-spacing: 0;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+.reveal ::selection { background: var(--accent-200); color: var(--ink); }
+.reveal ::-moz-selection { background: var(--accent-200); color: var(--ink); }
+
+.reveal .slides {
+ text-align: left;
+}
+
+.reveal .slides > section,
+.reveal .slides > section > section {
+ padding: 48px 72px;
+ line-height: 1.45;
+ font-weight: inherit;
+ box-sizing: border-box;
+ height: 100%;
+}
+
+/* -----------------------------------------------------------
+ Headings
+ ----------------------------------------------------------- */
+.reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 {
+ font-family: var(--font-display);
+ color: var(--ink);
+ font-weight: 800;
+ line-height: 1.08;
+ letter-spacing: -0.025em;
+ text-transform: none;
+ text-wrap: balance;
+ margin: 0 0 0.5em;
+}
+
+.reveal h1 { font-size: 2.4em; font-weight: 900; letter-spacing: -0.035em; line-height: 1.0; }
+.reveal h2 { font-size: 1.7em; font-weight: 800; letter-spacing: -0.03em; }
+.reveal h3 { font-size: 1.3em; font-weight: 700; letter-spacing: -0.022em; }
+.reveal h4 { font-size: 1.1em; font-weight: 700; letter-spacing: -0.018em; }
+
+/* Eyebrow kicker — used above headlines on many layouts */
+.reveal .eyebrow,
+.reveal [data-eyebrow]::before {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--font-sans);
+ font-size: 0.5em;
+ font-weight: 600;
+ color: var(--accent-600);
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ margin: 0 0 18px;
+}
+.reveal .eyebrow::before {
+ content: "";
+ display: inline-block;
+ width: 28px;
+ height: 2px;
+ background: var(--accent-600);
+}
+.reveal [data-eyebrow]::before {
+ content: attr(data-eyebrow);
+}
+
+/* -----------------------------------------------------------
+ Body copy, lists, links
+ ----------------------------------------------------------- */
+.reveal p {
+ font-family: var(--font-sans);
+ line-height: 1.55;
+ margin: 0 0 0.75em;
+ text-wrap: pretty;
+}
+
+.reveal .lead,
+.reveal p.lead {
+ font-family: var(--font-serif);
+ font-size: 1.15em;
+ font-weight: 400;
+ line-height: 1.5;
+ color: var(--ink);
+ max-width: 26em;
+ padding-left: 0.75em;
+ border-left: 2px solid var(--accent-600);
+ margin: 0.5em 0 1em;
+}
+
+.reveal ul, .reveal ol {
+ display: block;
+ margin: 0.25em 0 0.75em;
+ padding-left: 1.3em;
+}
+.reveal li {
+ margin: 0.35em 0;
+ line-height: 1.5;
+}
+.reveal ul { list-style: none; }
+.reveal ul > li { position: relative; }
+.reveal ul > li::before {
+ content: "";
+ position: absolute;
+ left: -1.1em;
+ top: 0.7em;
+ width: 10px;
+ height: 2px;
+ background: var(--accent-600);
+}
+.reveal ol { list-style: decimal; }
+.reveal ol > li::marker {
+ color: var(--accent-600);
+ font-family: var(--font-mono);
+ font-weight: 600;
+}
+
+.reveal a {
+ color: var(--navy-600);
+ text-decoration: underline;
+ text-decoration-thickness: 1px;
+ text-underline-offset: 3px;
+ transition: color 120ms ease-out, text-decoration-thickness 120ms ease-out;
+}
+.reveal a:hover {
+ color: var(--navy-700);
+ text-decoration-thickness: 2px;
+}
+
+.reveal hr {
+ border: none;
+ border-top: 1px solid var(--rule);
+ margin: 0.75em 0;
+}
+
+.reveal strong, .reveal b { font-weight: 700; color: var(--ink); }
+.reveal em, .reveal i { font-style: italic; }
+
+/* Small caps helper — for metadata rows */
+.reveal .smallcaps,
+.reveal .meta {
+ font-family: var(--font-sans);
+ font-size: 0.55em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--ink-3);
+}
+
+/* -----------------------------------------------------------
+ Block quote / pullquote
+ ----------------------------------------------------------- */
+.reveal blockquote {
+ font-family: var(--font-serif);
+ font-size: 1.1em;
+ font-style: italic;
+ line-height: 1.4;
+ color: var(--ink);
+ border-left: 3px solid var(--accent-600);
+ padding: 0.25em 0 0.25em 1em;
+ margin: 0.75em 0;
+ background: transparent;
+ box-shadow: none;
+ width: auto;
+}
+
+/* Big pullquote layout — .pullquote on a section */
+.reveal section.pullquote {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+.reveal section.pullquote blockquote {
+ font-size: 1.8em;
+ font-style: italic;
+ line-height: 1.2;
+ border-left: none;
+ padding: 0;
+ max-width: 22em;
+ letter-spacing: -0.01em;
+}
+.reveal section.pullquote blockquote::before {
+ content: "\201C";
+ display: block;
+ font-family: var(--font-serif);
+ font-size: 2.2em;
+ line-height: 0.9;
+ color: var(--accent-600);
+ margin-bottom: 0.1em;
+ font-style: normal;
+}
+.reveal section.pullquote .attribution,
+.reveal section.pullquote footer {
+ margin-top: 1em;
+ font-family: var(--font-sans);
+ font-size: 0.55em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--ink-3);
+ font-style: normal;
+}
+.reveal section.pullquote .attribution::before {
+ content: "— ";
+ color: var(--accent-600);
+}
+
+/* -----------------------------------------------------------
+ Code
+ ----------------------------------------------------------- */
+.reveal code {
+ font-family: var(--font-mono);
+ font-size: 0.85em;
+ color: var(--accent-700);
+ background: var(--paper-2);
+ padding: 0.08em 0.35em;
+ border-radius: 2px;
+ font-feature-settings: "tnum" 1, "zero" 1;
+}
+
+.reveal pre {
+ font-family: var(--font-mono);
+ font-size: 0.55em;
+ line-height: 1.5;
+ background: var(--ink);
+ color: var(--paper);
+ padding: 1em 1.25em;
+ border-radius: 8px;
+ box-shadow: none;
+ width: 100%;
+ max-width: 100%;
+ overflow: auto;
+ margin: 0.5em 0;
+}
+.reveal pre code {
+ background: transparent;
+ color: inherit;
+ padding: 0;
+ font-size: 1em;
+ display: block;
+ overflow: visible;
+ white-space: pre;
+ max-height: none;
+}
+
+/* Quarto code-line-number + highlight markers */
+.reveal pre code .hljs-ln-numbers,
+.reveal pre code .code-line-number {
+ color: var(--ink-4);
+ padding-right: 1em;
+ user-select: none;
+}
+.reveal div.sourceCode {
+ background: transparent;
+ border: none;
+ margin: 0.5em 0;
+}
+.reveal code span.co { color: #8C97AB; font-style: italic; } /* comments */
+.reveal code span.st { color: #F2A976; } /* strings */
+.reveal code span.dv,
+.reveal code span.fl { color: #F8C8A3; } /* numbers */
+.reveal code span.kw { color: #EA8A49; font-weight: 600; } /* keywords */
+.reveal code span.cf { color: #EA8A49; font-weight: 600; } /* control flow */
+.reveal code span.fu { color: #B3C6E0; } /* function */
+.reveal code span.op { color: #D6CEBD; } /* operator */
+.reveal code span.cn,
+.reveal code span.va { color: #7CB3B3; } /* variables */
+.reveal code span.at { color: #7CB3B3; }
+
+/* Quarto line-highlighting */
+.reveal pre code.sourceCode > span.has-line-highlight {
+ background: rgba(248, 200, 163, 0.14);
+}
+
+/* -----------------------------------------------------------
+ Tables
+ ----------------------------------------------------------- */
+.reveal table {
+ font-family: var(--font-sans);
+ font-size: 0.7em;
+ border-collapse: collapse;
+ margin: 0.5em 0;
+ width: 100%;
+ font-variant-numeric: tabular-nums;
+}
+.reveal table th,
+.reveal table td {
+ padding: 0.55em 0.9em;
+ text-align: left;
+ border: none;
+ border-bottom: 1px solid var(--rule);
+ vertical-align: baseline;
+}
+.reveal table th {
+ font-family: var(--font-sans);
+ font-size: 0.82em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--ink-3);
+ border-bottom: 2px solid var(--ink);
+ padding-bottom: 0.4em;
+ background: transparent;
+}
+.reveal table tbody tr:nth-child(even) td {
+ background: var(--paper-2);
+}
+.reveal table tbody tr:last-child td {
+ border-bottom: 2px solid var(--ink);
+}
+/* Numeric columns — add .numeric to a / | */
+.reveal table td.numeric,
+.reveal table th.numeric,
+.reveal table td[align="right"],
+.reveal table th[align="right"] {
+ text-align: right;
+ font-family: var(--font-mono);
+ font-variant-numeric: tabular-nums;
+}
+.reveal table caption {
+ caption-side: top;
+ text-align: left;
+ font-family: var(--font-sans);
+ font-size: 0.55em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--accent-600);
+ margin-bottom: 0.5em;
+}
+
+/* -----------------------------------------------------------
+ Images & figures
+ ----------------------------------------------------------- */
+.reveal img,
+.reveal section img {
+ background: transparent;
+ border: none;
+ box-shadow: none;
+ margin: 0.25em 0;
+ max-width: 100%;
+ height: auto;
+}
+.reveal figure {
+ margin: 0.5em 0;
+}
+.reveal figure img {
+ display: block;
+ border: 1px solid var(--rule);
+}
+.reveal figure figcaption,
+.reveal .caption {
+ font-family: var(--font-sans);
+ font-size: 0.55em;
+ line-height: 1.4;
+ color: var(--ink-3);
+ margin-top: 0.5em;
+ max-width: 42em;
+}
+.reveal figure figcaption strong,
+.reveal .caption strong {
+ color: var(--accent-600);
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ font-size: 0.9em;
+ display: inline-block;
+ margin-right: 0.5em;
+}
+
+/* -----------------------------------------------------------
+ TITLE SLIDE
+ Quarto renders the title slide with #title-slide +
+ ----------------------------------------------------------- */
+.reveal #title-slide,
+.reveal section.title-slide {
+ padding: 72px 80px;
+ display: flex !important;
+ flex-direction: column;
+ justify-content: space-between;
+ text-align: left;
+ background:
+ linear-gradient(180deg, var(--paper) 0%, var(--paper) 58%, var(--paper-2) 100%);
+}
+
+.reveal #title-slide .civilytics-mark,
+.reveal section.title-slide .civilytics-mark {
+ display: block;
+ width: 72px;
+ height: 72px;
+ margin-bottom: 32px;
+ object-fit: contain;
+}
+
+/* Civilytics wordmark lockup — bold serif "Civilytics" + inline pulse accent.
+ Rendered as an tag pointing at the official wordmark SVG asset
+ (assets/civilytics-wordmark.svg for ink-on-paper slides,
+ assets/civilytics-wordmark-reverse.svg for paper-on-ink slides).
+ Source viewBox is 558×160 (aspect ratio ≈ 3.4875). */
+img.civilytics-wordmark,
+.reveal img.civilytics-wordmark,
+.reveal .civilytics-wordmark {
+ display: block;
+ height: 48px;
+ width: 167.4px; /* 558/160 aspect * 48px height */
+ flex: 0 0 auto;
+ align-self: flex-start;
+ object-fit: contain;
+ margin: 0 0 36px;
+}
+
+.reveal #title-slide .civilytics-wordmark,
+.reveal section.title-slide .civilytics-wordmark {
+ height: 48px;
+ width: 167.4px;
+ margin: 0 0 36px;
+ color: var(--ink);
+}
+
+.reveal #title-slide .title,
+.reveal section.title-slide h1.title,
+.reveal section.title-slide h1:first-of-type {
+ font-family: var(--font-display);
+ font-size: 2.6em;
+ font-weight: 900;
+ letter-spacing: -0.035em;
+ line-height: 0.98;
+ color: var(--ink);
+ margin: 0 0 0.3em;
+ max-width: 18em;
+ text-wrap: balance;
+}
+.reveal #title-slide .subtitle,
+.reveal section.title-slide p.subtitle,
+.reveal section.title-slide .subtitle {
+ font-family: var(--font-serif);
+ font-size: 1.05em;
+ font-weight: 400;
+ font-style: italic;
+ line-height: 1.35;
+ color: var(--ink-2);
+ margin: 0 0 1em;
+ max-width: 28em;
+ padding-left: 0.75em;
+ border-left: 2px solid var(--accent-600);
+}
+
+/* Author / date / affiliation block */
+.reveal #title-slide .quarto-title-authors,
+.reveal #title-slide .quarto-title-author-name,
+.reveal section.title-slide .author,
+.reveal section.title-slide .date,
+.reveal section.title-slide .affiliation {
+ font-family: var(--font-sans);
+ color: var(--ink-2);
+}
+.reveal #title-slide .author,
+.reveal #title-slide .quarto-title-author-name,
+.reveal section.title-slide .author {
+ font-size: 0.7em;
+ font-weight: 600;
+ color: var(--ink);
+ margin: 0.2em 0 0;
+ letter-spacing: 0;
+}
+.reveal #title-slide .affiliation,
+.reveal #title-slide .quarto-title-affiliation,
+.reveal section.title-slide .affiliation {
+ font-size: 0.55em;
+ font-weight: 500;
+ color: var(--ink-3);
+ letter-spacing: var(--tracking-caps);
+ text-transform: uppercase;
+ margin: 0.15em 0 0;
+}
+.reveal #title-slide .date,
+.reveal section.title-slide .date {
+ font-size: 0.5em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--accent-600);
+ margin-top: 1em;
+ padding-top: 0.8em;
+ border-top: 3px double var(--ink);
+ display: inline-block;
+}
+
+/* Editorial thick-top / thin-bottom rule block around title */
+.reveal section.title-slide .quarto-title-block,
+.reveal section.title-slide .title-block {
+ border-top: 6px solid var(--ink);
+ border-bottom: 1px solid var(--rule);
+ padding: 0.5em 0 1em;
+}
+
+/* -----------------------------------------------------------
+ SECTION DIVIDER — or
+ Quarto's {.section} header
+ ----------------------------------------------------------- */
+.reveal section.section-divider,
+.reveal section.section {
+ background: var(--ink);
+ color: var(--paper);
+ display: flex !important;
+ flex-direction: column;
+ justify-content: center;
+ padding: 80px 96px;
+}
+.reveal section.section-divider::before,
+.reveal section.section::before {
+ content: "";
+ display: block;
+ width: 72px;
+ height: 2px;
+ background: var(--accent-600);
+ margin-bottom: 24px;
+}
+.reveal section.section-divider h1,
+.reveal section.section-divider h2,
+.reveal section.section-divider h3,
+.reveal section.section h1,
+.reveal section.section h2,
+.reveal section.section h3 {
+ color: var(--paper);
+ font-size: 2.8em;
+ font-weight: 900;
+ letter-spacing: -0.035em;
+ line-height: 1.0;
+ max-width: 18em;
+}
+.reveal section.section-divider .eyebrow,
+.reveal section.section .eyebrow {
+ color: var(--accent-300);
+}
+.reveal section.section-divider .eyebrow::before,
+.reveal section.section .eyebrow::before {
+ background: var(--accent-300);
+}
+.reveal section.section-divider p,
+.reveal section.section p {
+ color: var(--paper);
+ opacity: 0.82;
+ font-family: var(--font-serif);
+ font-size: 1.05em;
+ font-style: italic;
+ max-width: 28em;
+}
+.reveal section.section-divider .section-number,
+.reveal section.section .section-number {
+ font-family: var(--font-mono);
+ font-size: 0.6em;
+ font-weight: 600;
+ color: var(--accent-300);
+ letter-spacing: var(--tracking-caps);
+ text-transform: uppercase;
+ margin-bottom: 0.5em;
+}
+
+/* -----------------------------------------------------------
+ FULL-BLEED IMAGE —
+ ----------------------------------------------------------- */
+.reveal section.full-bleed {
+ padding: 0 !important;
+}
+.reveal section.full-bleed .caption,
+.reveal section.full-bleed figcaption {
+ position: absolute;
+ left: 48px;
+ right: 48px;
+ bottom: 48px;
+ background: var(--paper);
+ padding: 14px 18px;
+ border-left: 3px solid var(--accent-600);
+ font-family: var(--font-sans);
+ font-size: 0.5em;
+ line-height: 1.45;
+ color: var(--ink);
+ max-width: 36em;
+ box-shadow: 0 4px 12px rgba(14, 26, 43, 0.12);
+}
+.reveal section.full-bleed .caption strong,
+.reveal section.full-bleed figcaption strong {
+ display: block;
+ font-size: 0.85em;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--accent-600);
+ margin-bottom: 4px;
+}
+
+/* -----------------------------------------------------------
+ BIG STAT / DATA CALLOUT — .big-stat section or .stat block
+ ----------------------------------------------------------- */
+.reveal .stat-number {
+ font-family: var(--font-serif);
+ font-weight: 700;
+ font-size: 3.6em;
+ line-height: 1;
+ letter-spacing: -0.02em;
+ font-variant-numeric: tabular-nums;
+ color: var(--ink);
+ margin: 0 0 0.1em;
+ display: block;
+}
+.reveal .stat-label {
+ font-family: var(--font-sans);
+ font-size: 0.55em;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--ink-3);
+}
+.reveal .stat-note {
+ font-family: var(--font-serif);
+ font-style: italic;
+ font-size: 0.9em;
+ color: var(--ink-2);
+ max-width: 22em;
+ margin-top: 0.6em;
+}
+.reveal section.big-stat {
+ display: flex !important;
+ flex-direction: column;
+ justify-content: center;
+}
+.reveal section.big-stat .stat-number {
+ font-size: 5em;
+}
+
+/* Stat grid — multiple stats side-by-side */
+.reveal .stat-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 2em 3em;
+ margin: 1em 0;
+}
+.reveal .stat-grid .stat {
+ border-top: 3px solid var(--ink);
+ padding-top: 0.6em;
+}
+.reveal .stat-grid .stat .stat-number { font-size: 2.8em; }
+
+/* -----------------------------------------------------------
+ TWO-COLUMN — Quarto renders {.columns}{.column width=50%}
+ ----------------------------------------------------------- */
+.reveal .columns {
+ display: flex;
+ gap: 3em;
+ align-items: flex-start;
+ margin: 0.25em 0;
+}
+.reveal .column {
+ flex: 1;
+ min-width: 0;
+}
+.reveal .column > :first-child { margin-top: 0; }
+
+/* Hand-rolled two-col for pandoc (non-Quarto) users */
+.reveal .two-col {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 3em;
+ align-items: start;
+}
+.reveal .two-col--wide-left { grid-template-columns: 2fr 1fr; }
+.reveal .two-col--wide-right { grid-template-columns: 1fr 2fr; }
+
+/* -----------------------------------------------------------
+ AGENDA / TOC slide —
+ Pair with an ordered list; we restyle it.
+ ----------------------------------------------------------- */
+.reveal section.agenda ol {
+ list-style: none;
+ counter-reset: agenda;
+ padding: 0;
+ margin: 0.5em 0;
+}
+.reveal section.agenda ol > li {
+ counter-increment: agenda;
+ display: grid;
+ grid-template-columns: 2.4em 1fr auto;
+ gap: 0.6em 1.2em;
+ align-items: baseline;
+ padding: 0.55em 0;
+ border-top: 1px solid var(--rule);
+ font-family: var(--font-serif);
+ font-size: 1.05em;
+}
+.reveal section.agenda ol > li:last-child { border-bottom: 1px solid var(--rule); }
+.reveal section.agenda ol > li::before {
+ content: counter(agenda, decimal-leading-zero);
+ font-family: var(--font-mono);
+ font-weight: 600;
+ font-size: 0.7em;
+ color: var(--accent-600);
+ letter-spacing: var(--tracking-caps);
+}
+.reveal section.agenda ol > li .meta,
+.reveal section.agenda ol > li em {
+ font-family: var(--font-sans);
+ font-style: normal;
+ font-size: 0.7em;
+ color: var(--ink-3);
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ justify-self: end;
+}
+
+/* -----------------------------------------------------------
+ THANK-YOU / END slide —
+ ----------------------------------------------------------- */
+.reveal section.thank-you {
+ background: var(--ink);
+ color: var(--paper);
+ display: flex !important;
+ flex-direction: column;
+ justify-content: center;
+ padding: 80px 96px;
+}
+.reveal section.thank-you .civilytics-mark {
+ display: block;
+ width: 56px;
+ height: 56px;
+ margin-bottom: 28px;
+ object-fit: contain;
+}
+.reveal section.thank-you .civilytics-wordmark {
+ height: 40px;
+ width: 139.5px; /* 558/160 aspect * 40 */
+ flex: 0 0 auto;
+ align-self: flex-start;
+ margin: 0 0 32px;
+}
+.reveal section.thank-you h1,
+.reveal section.thank-you h2 {
+ color: var(--paper);
+ font-size: 3em;
+ font-weight: 900;
+ letter-spacing: -0.035em;
+ margin-bottom: 0.2em;
+}
+.reveal section.thank-you p {
+ color: var(--paper);
+ opacity: 0.85;
+ font-family: var(--font-serif);
+ font-size: 1.1em;
+ font-style: italic;
+ max-width: 26em;
+}
+.reveal section.thank-you .contact,
+.reveal section.thank-you ul.contact {
+ list-style: none;
+ padding: 0;
+ margin: 1.5em 0 0;
+ font-family: var(--font-sans);
+ font-size: 0.6em;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--accent-300);
+}
+.reveal section.thank-you .contact li,
+.reveal section.thank-you ul.contact li {
+ margin: 0.3em 0;
+}
+.reveal section.thank-you .contact li::before,
+.reveal section.thank-you ul.contact li::before { display: none; }
+.reveal section.thank-you a { color: var(--paper); text-decoration: underline; text-decoration-color: var(--accent-600); }
+.reveal section.thank-you a:hover { color: var(--accent-300); }
+
+/* -----------------------------------------------------------
+ REFERENCES / SOURCES — small, tight
+ ----------------------------------------------------------- */
+.reveal .references,
+.reveal section.references {
+ font-size: 0.72em;
+}
+.reveal section.references h2 { font-size: 1.4em; }
+.reveal .references ol,
+.reveal .references ul {
+ padding-left: 1.4em;
+}
+.reveal .references li {
+ font-family: var(--font-sans);
+ font-size: 0.95em;
+ line-height: 1.4;
+ color: var(--ink-2);
+ margin: 0.35em 0;
+}
+.reveal .references li::before { display: none; }
+
+/* Footnotes (Pandoc / Quarto) */
+.reveal .footnotes,
+.reveal div.footnotes {
+ font-size: 0.6em;
+ color: var(--ink-3);
+ border-top: 1px solid var(--rule);
+ padding-top: 0.6em;
+ margin-top: 1em;
+}
+.reveal a.footnote-ref,
+.reveal sup a {
+ color: var(--accent-600);
+ text-decoration: none;
+ font-family: var(--font-mono);
+ font-size: 0.7em;
+ font-weight: 600;
+ margin: 0 0.15em;
+}
+
+/* -----------------------------------------------------------
+ MATH — KaTeX / MathJax
+ ----------------------------------------------------------- */
+.reveal .katex,
+.reveal .MathJax {
+ font-size: 1em !important;
+ color: var(--ink);
+}
+.reveal .katex-display,
+.reveal mjx-container[display="true"] {
+ margin: 0.5em 0 !important;
+ padding: 0.5em 0;
+ border-left: 2px solid var(--accent-600);
+ padding-left: 1em;
+}
+
+/* -----------------------------------------------------------
+ CALLOUTS (Quarto)
+ ----------------------------------------------------------- */
+.reveal div.callout {
+ border: 1px solid var(--rule);
+ border-left: 3px solid var(--accent-600);
+ border-radius: 4px;
+ background: var(--paper-2);
+ margin: 0.75em 0;
+ font-size: 0.78em;
+ overflow: hidden;
+}
+.reveal div.callout > div.callout-header {
+ background: transparent;
+ padding: 0.5em 0.9em 0.2em;
+ font-family: var(--font-sans);
+ font-weight: 600;
+ font-size: 0.9em;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--accent-600);
+}
+.reveal div.callout > div.callout-body,
+.reveal div.callout-body-container {
+ padding: 0.2em 0.9em 0.6em;
+}
+.reveal div.callout.callout-note { border-left-color: var(--navy-600); }
+.reveal div.callout.callout-note > div.callout-header { color: var(--navy-600); }
+.reveal div.callout.callout-warning { border-left-color: #9A5F18; }
+.reveal div.callout.callout-warning > div.callout-header { color: #9A5F18; }
+.reveal div.callout.callout-important,
+.reveal div.callout.callout-caution { border-left-color: #A6271D; }
+.reveal div.callout.callout-important > div.callout-header,
+.reveal div.callout.callout-caution > div.callout-header { color: #A6271D; }
+.reveal div.callout.callout-tip { border-left-color: var(--moss-600); }
+.reveal div.callout.callout-tip > div.callout-header { color: var(--moss-600); }
+
+/* -----------------------------------------------------------
+ SLIDE NUMBER + FOOTER BRAND CHROME
+ ----------------------------------------------------------- */
+.reveal .slide-number {
+ font-family: var(--font-mono);
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--ink-3);
+ background: transparent;
+ right: 24px;
+ bottom: 18px;
+ letter-spacing: var(--tracking-caps);
+}
+.reveal .slide-number a { color: inherit; }
+
+/* Brand footer mark — inserted by extension JS */
+.reveal .civilytics-footer {
+ position: absolute;
+ left: 24px;
+ bottom: 18px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-family: var(--font-sans);
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: var(--tracking-caps);
+ color: var(--ink-3);
+ pointer-events: none;
+ z-index: 30;
+}
+.reveal .civilytics-footer img,
+.reveal .civilytics-footer svg {
+ width: 22px;
+ height: auto;
+ opacity: 0.85;
+ flex: 0 0 auto;
+}
+
+/* Hide chrome on title / section / thank-you / full-bleed slides */
+.reveal .slides > section.title-slide ~ *,
+.reveal #title-slide.present ~ .civilytics-footer { /* no-op, readable marker */ }
+.reveal .slides > section.title-slide.present ~ .controls,
+.reveal .slides > section.section-divider.present ~ .controls,
+.reveal .slides > section.thank-you.present ~ .controls { }
+body.civilytics-hide-chrome .civilytics-footer,
+body.civilytics-hide-chrome .slide-number {
+ display: none !important;
+}
+
+/* -----------------------------------------------------------
+ PROGRESS BAR
+ ----------------------------------------------------------- */
+.reveal .progress {
+ color: var(--accent-600);
+ height: 3px;
+ background: rgba(14, 26, 43, 0.08);
+}
+
+/* -----------------------------------------------------------
+ CONTROLS
+ ----------------------------------------------------------- */
+.reveal .controls {
+ color: var(--ink-3);
+}
+.reveal .controls button { color: var(--ink-3) !important; }
+.reveal .controls button:hover { color: var(--accent-600) !important; }
+
+/* -----------------------------------------------------------
+ PRINT / PDF EXPORT
+ ----------------------------------------------------------- */
+@media print {
+ .reveal .civilytics-footer,
+ .reveal .slide-number { color: var(--ink-3); }
+ .reveal section { break-inside: avoid; }
+}
+
+/* -----------------------------------------------------------
+ SMALL-SCREEN GUARDRAILS
+ Reveal scales slides, but the narrowest keep-legible margins.
+ ----------------------------------------------------------- */
+@media (max-width: 640px) {
+ .reveal .slides > section,
+ .reveal .slides > section > section {
+ padding: 32px 36px;
+ }
+ .reveal section.section-divider,
+ .reveal section.thank-you {
+ padding: 48px 48px;
+ }
+}
diff --git a/inst/quarto/latex/civilytics-title.tex b/inst/quarto/latex/civilytics-title.tex
new file mode 100644
index 0000000..2d31ca2
--- /dev/null
+++ b/inst/quarto/latex/civilytics-title.tex
@@ -0,0 +1,41 @@
+% Civilytics — title-page partial.
+% Replaces Quarto's default \maketitle. Uses values from YAML
+% (\thetitle, \theauthor, \thedate) plus an \ifabstract block.
+
+\begin{titlepage}
+ \pagecolor{paper}
+ \color{ink}
+ \vspace*{0.5in}
+ {\sffamily\bfseries\scriptsize\color{ember}\MakeUppercase{— Civilytics Research}\par}
+ \vspace{12pt}
+ {\displayfont\fontsize{32pt}{34pt}\selectfont\bfseries\color{ink}\thetitle\par}
+ \vspace{8pt}
+ \ifx\thesubtitle\@empty\else
+ {\rmfamily\itshape\fontsize{14pt}{18pt}\selectfont\color{ink2}\thesubtitle\par}
+ \fi
+ \vspace{18pt}
+ \noindent\rule{\linewidth}{2pt}\par
+ \vspace{1pt}
+ \noindent{\color{rule}\rule{\linewidth}{0.4pt}}\par
+ \vspace{8pt}
+ \noindent
+ \begin{minipage}[t]{0.5\linewidth}
+ {\sffamily\scriptsize\color{ink3}\MakeUppercase{Authors}\par}
+ \vspace{2pt}
+ {\sffamily\small\color{ink}\theauthor\par}
+ \end{minipage}\hfill
+ \begin{minipage}[t]{0.4\linewidth}\raggedleft
+ {\sffamily\scriptsize\color{ink3}\MakeUppercase{Published}\par}
+ \vspace{2pt}
+ {\sffamily\small\color{ink}\thedate\par}
+ \end{minipage}\par
+ \vspace{1.5cm}
+
+ % Pulse mark, in ember
+ \begin{center}
+ \begin{tikzpicture}[overlay, remember picture]
+ \end{tikzpicture}
+ {\color{ember}\rule{40pt}{2pt}}
+ \end{center}
+\end{titlepage}
+\clearpage
diff --git a/inst/quarto/latex/civilytics.tex b/inst/quarto/latex/civilytics.tex
new file mode 100644
index 0000000..5bdcc4e
--- /dev/null
+++ b/inst/quarto/latex/civilytics.tex
@@ -0,0 +1,112 @@
+% =============================================================
+% Civilytics — LaTeX preamble for Quarto PDF
+% Usage in YAML:
+% format:
+% pdf:
+% include-in-header: quarto/latex/civilytics.tex
+% include-before-body: quarto/latex/civilytics-title.tex
+% Requires: xelatex or lualatex (for system fonts).
+% =============================================================
+
+\usepackage{xcolor}
+\usepackage{fontspec}
+\usepackage{titlesec}
+\usepackage{titling}
+\usepackage{fancyhdr}
+\usepackage{enumitem}
+\usepackage{booktabs}
+\usepackage{caption}
+\usepackage[hidelinks]{hyperref}
+\usepackage{microtype}
+\usepackage{tcolorbox}
+\tcbuselibrary{skins,breakable}
+
+% --- Civilytics palette ---
+\definecolor{paper}{HTML}{FAF7F2}
+\definecolor{paper2}{HTML}{F2EDE4}
+\definecolor{ink}{HTML}{0E1A2B}
+\definecolor{ink2}{HTML}{2B3A52}
+\definecolor{ink3}{HTML}{5A6A82}
+\definecolor{rule}{HTML}{D6CEBD}
+\definecolor{ruleStrong}{HTML}{B8AE97}
+\definecolor{navy}{HTML}{22406A}
+\definecolor{ember}{HTML}{C25311}
+\definecolor{emberDark}{HTML}{923D00}
+\definecolor{teal}{HTML}{1F6F70}
+\definecolor{plum}{HTML}{6B3A5E}
+
+% --- Page color ---
+\pagecolor{paper}
+\color{ink}
+
+% --- Fonts (require local install or fontspec lookup) ---
+\setmainfont{Source Serif 4}[
+ UprightFont = *,
+ ItalicFont = * Italic,
+ BoldFont = * SemiBold,
+ BoldItalicFont = * SemiBold Italic,
+ Ligatures = TeX,
+]
+\setsansfont{Inter}[Ligatures = TeX]
+\setmonofont{JetBrains Mono}[Scale = 0.92]
+\newfontfamily\displayfont{Libre Franklin}[Ligatures = TeX]
+
+% --- Hyperlinks ---
+\hypersetup{
+ colorlinks = true,
+ linkcolor = navy,
+ urlcolor = navy,
+ citecolor = navy,
+ filecolor = navy,
+}
+
+% --- Section headings ---
+\titleformat{\section}[block]
+ {\bfseries\Large\displayfont\color{ink}}
+ {}{0pt}
+ {\titlerule[0.4pt]\vspace{4pt}}[\vspace{-6pt}]
+\titleformat{\subsection}[block]
+ {\bfseries\large\displayfont\color{ink}}{}{0pt}{}
+\titleformat{\subsubsection}[block]
+ {\bfseries\normalsize\displayfont\color{ink}}{}{0pt}{}
+
+% --- Page header / footer ---
+\pagestyle{fancy}
+\fancyhf{}
+\renewcommand{\headrulewidth}{0pt}
+\renewcommand{\footrulewidth}{0pt}
+\fancyhead[L]{\sffamily\scriptsize\color{ink3}\MakeUppercase{Civilytics Consulting}}
+\fancyhead[R]{\sffamily\scriptsize\color{ink3}\thetitle}
+\fancyfoot[L]{\sffamily\scriptsize\color{ink3}civilytics.consulting}
+\fancyfoot[C]{\sffamily\scriptsize\color{ink3}\thepage}
+\fancyfoot[R]{\sffamily\scriptsize\color{ink3}\textcopyright\ 2026}
+
+% --- Captions ---
+\captionsetup{
+ font={sf,small,color=ink3},
+ labelfont={sf,bf,color=ink3},
+ labelsep=period,
+ justification=raggedright,
+ singlelinecheck=false,
+}
+
+% --- Pullquote ---
+\newtcolorbox{pullquote}{
+ enhanced, breakable, frame hidden, colback=paper,
+ borderline west = {2pt}{0pt}{ember},
+ left=14pt, right=4pt, top=4pt, bottom=4pt,
+ fontupper={\itshape\rmfamily\large\color{ink}},
+}
+
+% --- Code blocks (Pandoc + listings or minted both inherit colors below) ---
+\definecolor{codebg}{HTML}{0E1A2B}
+\definecolor{codefg}{HTML}{FAF7F2}
+
+% --- Lists tighter ---
+\setlist{itemsep=2pt, topsep=4pt}
+
+% --- Tabular numerals ---
+\newcommand{\tnum}[1]{{\addfontfeature{Numbers={Tabular,Lining}}#1}}
+
+% --- Ember accent rule above section starts ---
+\newcommand{\emberrule}{{\color{ember}\rule{60pt}{3pt}}\par\vspace{4pt}}
diff --git a/inst/quarto/theme/_tokens.scss b/inst/quarto/theme/_tokens.scss
new file mode 100644
index 0000000..7dd370c
--- /dev/null
+++ b/inst/quarto/theme/_tokens.scss
@@ -0,0 +1,73 @@
+/*-- scss:defaults --*/
+// =============================================================
+// Civilytics tokens — single source of truth for colors + type.
+// Mirrored from colors_and_type.css so figures, themes, and
+// preview pages stay in sync.
+// =============================================================
+
+// Neutrals — warm paper → civic ink
+$paper: #FAF7F2 !default;
+$paper-2: #F2EDE4 !default;
+$paper-3: #E6DFD1 !default;
+$rule: #D6CEBD !default;
+$rule-strong: #B8AE97 !default;
+
+$ink: #0E1A2B !default;
+$ink-2: #2B3A52 !default;
+$ink-3: #5A6A82 !default;
+$ink-4: #8C97AB !default;
+
+// Brand — Civic Navy
+$navy-900: #0E1A2B; $navy-800: #132339; $navy-700: #1A2E4A;
+$navy-600: #22406A; $navy-500: #2E5590; $navy-400: #4A74B0;
+$navy-300: #7A9BCA; $navy-200: #B3C6E0; $navy-100: #DDE6F2; $navy-50: #EEF3FA;
+
+// Accent — Ember
+$accent-900: #451A00; $accent-800: #6B2B00; $accent-700: #923D00;
+$accent-600: #C25311; $accent-500: #DB6C25; $accent-400: #EA8A49;
+$accent-300: #F2A976; $accent-200: #F8C8A3; $accent-100: #FBE0C6; $accent-50: #FDF1E4;
+
+// Supporting (data-viz)
+$teal-600: #1F6F70; $teal-300: #7CB3B3; $teal-100: #D3E6E6;
+$plum-600: #6B3A5E; $plum-300: #B392A7; $plum-100: #E7DAE1;
+$moss-600: #4A6B2F; $moss-300: #9CB47D; $moss-100: #DEE8CF;
+$brass-600: #B8751C; $brass-100: #F9E6C8;
+
+// Semantic
+$success: $moss-600; $warning: #9A5F18; $danger: #A6271D; $info: $navy-500;
+
+// Bootstrap-friendly aliases (used by Quarto's HTML theme)
+$body-bg: $paper !default;
+$body-color: $ink !default;
+$primary: $navy-600 !default;
+$secondary: $accent-600 !default;
+$success-bs: $success !default;
+$info-bs: $info !default;
+$warning-bs: $warning !default;
+$danger-bs: $danger !default;
+$light: $paper-2 !default;
+$dark: $ink !default;
+
+$link-color: $navy-600 !default;
+$link-hover-color: $navy-700 !default;
+
+// Type stacks
+$font-display: 'Libre Franklin', 'Franklin Gothic', 'Inter', system-ui, sans-serif !default;
+$font-serif: 'Source Serif 4', 'Source Serif Pro', Georgia, 'Times New Roman', serif !default;
+$font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif !default;
+$font-mono: 'JetBrains Mono', 'SF Mono', Menlo, Consolas, monospace !default;
+
+$font-family-sans-serif: $font-sans !default;
+$font-family-monospace: $font-mono !default;
+$font-family-base: $font-sans !default;
+
+// Sizing
+$font-size-base: 1rem !default;
+$line-height-base: 1.65 !default;
+
+// Borders + radii
+$border-color: $rule !default;
+$border-radius: 4px !default;
+$border-radius-sm: 2px !default;
+$border-radius-lg: 8px !default;
+$border-radius-xl: 14px !default;
diff --git a/inst/quarto/theme/civilytics.scss b/inst/quarto/theme/civilytics.scss
new file mode 100644
index 0000000..16b5727
--- /dev/null
+++ b/inst/quarto/theme/civilytics.scss
@@ -0,0 +1,331 @@
+/*-- scss:defaults --*/
+// =============================================================
+// Civilytics — Quarto HTML report + website theme
+// Built on Bootstrap defaults via Quarto's SCSS pipeline.
+// Inherits all tokens from _tokens.scss.
+// =============================================================
+@import "_tokens.scss";
+
+// Quarto-specific defaults
+$navbar-bg: $paper;
+$navbar-fg: $ink;
+$navbar-hl: $accent-600;
+$footer-bg: $ink;
+$footer-fg: $paper;
+
+$code-bg: $paper-2;
+$code-color: $accent-700;
+$pre-bg: $ink;
+$pre-color: $paper;
+
+// Tighter table look
+$table-border-color: $rule;
+$table-color: $ink;
+
+// Headings inherit display family + tight tracking
+$h1-font-size: 2.875rem;
+$h2-font-size: 2.125rem;
+$h3-font-size: 1.625rem;
+$h4-font-size: 1.25rem;
+$headings-font-family: $font-display;
+$headings-font-weight: 800;
+$headings-line-height: 1.08;
+$headings-color: $ink;
+
+$callout-color-note: $navy-600;
+$callout-color-tip: $teal-600;
+$callout-color-caution: $accent-700;
+$callout-color-warning: $warning;
+$callout-color-important: $danger;
+
+
+/*-- scss:rules --*/
+// =============================================================
+// Editorial layer — overrides Bootstrap + Quarto defaults so
+// rendered .qmd files look like Civilytics publications, not
+// generic blog posts.
+// =============================================================
+
+@import url('https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@500;600;700;800;900&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600;8..60,700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
+
+body {
+ font-family: $font-sans;
+ font-size: 1rem;
+ line-height: 1.7;
+ color: $ink;
+ background: $paper;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+}
+
+// Headings — display sans for headlines, tight tracking
+h1, h2, h3, h4, h5, h6 {
+ font-family: $font-display;
+ letter-spacing: -0.025em;
+ text-wrap: balance;
+ color: $ink;
+}
+h1 { font-weight: 900; letter-spacing: -0.035em; line-height: 1.02; }
+h2 { font-weight: 800; letter-spacing: -0.03em; margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid $rule; }
+h3 { font-weight: 700; margin-top: 1.75rem; }
+h4 { font-weight: 700; }
+h5, h6 { font-family: $font-sans; font-weight: 600; text-transform: none; }
+
+// Article-body prose — long-form reading uses Source Serif 4
+.quarto-title-block { margin-bottom: 2.5rem; }
+.quarto-title .title {
+ font-family: $font-display;
+ font-weight: 900;
+ font-size: 3.25rem;
+ line-height: 1.0;
+ letter-spacing: -0.04em;
+ margin-bottom: 0.75rem;
+}
+.quarto-title .subtitle {
+ font-family: $font-serif;
+ font-style: italic;
+ font-weight: 400;
+ font-size: 1.375rem;
+ color: $ink-2;
+ line-height: 1.4;
+ max-width: 60ch;
+}
+.quarto-title-meta {
+ font-family: $font-sans;
+ font-size: 0.8125rem;
+ color: $ink-3;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ border-top: 3px double $ink;
+ border-bottom: 1px solid $rule;
+ padding: 0.75rem 0;
+ margin-top: 1.5rem;
+}
+.quarto-title-meta .quarto-title-meta-heading { color: $ink-4; font-size: 0.6875rem; }
+.quarto-title-meta .quarto-title-meta-contents { color: $ink; font-size: 0.875rem; text-transform: none; letter-spacing: 0; font-weight: 500; }
+
+// Lead / abstract — italic serif lockup
+.abstract, p.abstract {
+ font-family: $font-serif;
+ font-style: italic;
+ font-size: 1.25rem;
+ line-height: 1.5;
+ color: $ink;
+ border-left: 2px solid $accent-600;
+ padding-left: 1.25rem;
+ margin: 2rem 0 2.5rem;
+ max-width: 62ch;
+}
+
+// Body paragraphs in article
+main p, main li {
+ font-family: $font-serif;
+ font-size: 1.0625rem;
+ line-height: 1.65;
+ color: $ink;
+ text-wrap: pretty;
+}
+main ul, main ol { padding-left: 1.5em; }
+main li { margin-bottom: 0.4em; }
+
+// Links — civic navy, thickening underline
+a { color: $navy-600; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; transition: color 120ms ease; }
+a:hover { color: $navy-700; text-decoration-thickness: 2px; }
+a:visited { color: $plum-600; }
+
+// Pullquote / blockquote
+blockquote {
+ font-family: $font-serif;
+ font-style: italic;
+ font-size: 1.625rem;
+ line-height: 1.4;
+ color: $ink;
+ border-left: 3px solid $accent-600;
+ padding: 0.5rem 0 0.5rem 1.5rem;
+ margin: 2rem 0;
+}
+blockquote p { font-family: inherit !important; font-size: inherit !important; line-height: inherit !important; }
+
+// Code — light by default
+code, kbd, samp {
+ font-family: $font-mono;
+ font-size: 0.92em;
+ font-feature-settings: "tnum" 1, "zero" 1;
+}
+code:not(pre code) {
+ background: $paper-2;
+ color: $accent-700;
+ padding: 0.1em 0.4em;
+ border-radius: 2px;
+ border: 1px solid $rule;
+}
+
+// Code blocks — light variant (default), with a dark-emphasis option below
+div.sourceCode, pre.sourceCode {
+ background: $paper-2;
+ border: 1px solid $rule;
+ border-radius: 8px;
+}
+div.sourceCode pre {
+ background: transparent;
+ color: $ink;
+ padding: 1rem 1.25rem;
+ font-size: 0.875rem;
+ line-height: 1.55;
+ margin: 0;
+}
+// Highlight tokens (Quarto/Pandoc default classes)
+pre.sourceCode {
+ .kw { color: $navy-700; font-weight: 600; } // keyword
+ .dt { color: $teal-600; } // data type
+ .st { color: $accent-700; } // string
+ .co { color: $ink-3; font-style: italic; } // comment
+ .fu { color: $navy-600; } // function
+ .op, .sc { color: $ink-2; } // operator
+ .dv, .fl { color: $plum-600; } // numbers
+ .va { color: $brass-600; } // variable
+}
+// Dark variant — opt in by adding {.dark} to the code chunk
+.cell.dark div.sourceCode,
+div.sourceCode.dark,
+pre.dark {
+ background: $ink;
+ border-color: $navy-800;
+ pre, code { color: $paper; }
+ .kw { color: lighten($navy-300, 5%); }
+ .dt { color: $teal-300; }
+ .st { color: $accent-300; }
+ .co { color: $ink-4; }
+ .fu { color: $navy-200; }
+ .op, .sc { color: $paper-3; }
+ .dv, .fl { color: $plum-300; }
+}
+
+// Tables — editorial grid
+.table, table {
+ font-family: $font-sans;
+ font-size: 0.9375rem;
+ border-collapse: collapse;
+ width: 100%;
+ font-variant-numeric: tabular-nums;
+ margin: 1.5rem 0;
+}
+.table thead th, table thead th {
+ font-family: $font-sans;
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: $ink-3;
+ border-top: 2px solid $ink;
+ border-bottom: 1px solid $rule-strong;
+ padding: 0.6rem 0.75rem;
+ text-align: left;
+}
+.table tbody td, table tbody td {
+ border-bottom: 1px solid $rule;
+ padding: 0.55rem 0.75rem;
+ color: $ink;
+}
+.table tbody tr:last-child td, table tbody tr:last-child td { border-bottom: 2px solid $ink; }
+.table.numeric td, table .numeric { font-family: $font-mono; }
+
+// Figures + captions
+figure, .quarto-figure { margin: 2rem 0; }
+figcaption, .figure-caption, .quarto-figcaption {
+ font-family: $font-sans;
+ font-size: 0.8125rem;
+ color: $ink-3;
+ line-height: 1.45;
+ margin-top: 0.5rem;
+ padding-top: 0.5rem;
+ border-top: 1px solid $rule;
+ text-align: left;
+}
+
+// Callouts — keep Quarto markup, restyle as editorial sidebars
+.callout {
+ border-radius: 8px;
+ border: 1px solid $rule;
+ background: $paper-2;
+ margin: 1.5rem 0;
+ border-left: 3px solid $navy-600;
+}
+.callout-note { border-left-color: $navy-600; }
+.callout-tip { border-left-color: $teal-600; }
+.callout-caution { border-left-color: $accent-700; }
+.callout-warning { border-left-color: $warning; }
+.callout-important { border-left-color: $danger; }
+.callout-title { font-family: $font-sans; font-weight: 600; font-size: 0.9375rem; color: $ink; text-transform: none; letter-spacing: 0; }
+.callout-body p { font-family: $font-sans !important; font-size: 0.9375rem !important; line-height: 1.55 !important; }
+
+// Eyebrow / kicker (Quarto category renders as `.quarto-categories`)
+.quarto-categories {
+ display: inline-flex;
+ gap: 0.5rem;
+ margin-bottom: 0.5rem;
+}
+.quarto-categories .quarto-category {
+ font-family: $font-sans;
+ font-size: 0.6875rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: $accent-600;
+ background: transparent;
+ border: none;
+ padding: 0;
+}
+.quarto-categories .quarto-category::before { content: "— "; color: $accent-600; }
+
+// Navbar (used by website projects)
+.navbar {
+ background: rgba(250, 247, 242, 0.92) !important;
+ backdrop-filter: blur(12px);
+ border-bottom: 1px solid $rule;
+ padding: 0.875rem 0;
+}
+.navbar-brand { font-family: $font-display; font-weight: 800; font-size: 1.125rem; letter-spacing: -0.02em; color: $ink !important; }
+.navbar-nav .nav-link { font-family: $font-sans; font-weight: 500; font-size: 0.9375rem; color: $ink-2 !important; padding: 0.4rem 0.85rem !important; }
+.navbar-nav .nav-link:hover, .navbar-nav .nav-link.active { color: $accent-600 !important; }
+
+// Footer
+.nav-footer, footer.footer {
+ background: $ink;
+ color: $paper;
+ font-family: $font-sans;
+ font-size: 0.875rem;
+ padding: 2rem 0;
+ border-top: 4px solid $accent-600;
+}
+.nav-footer a, footer.footer a { color: $paper; text-decoration: underline; text-decoration-thickness: 1px; }
+
+// TOC — right-side sidebar
+#TOC, .sidebar.toc-active, nav#TOC {
+ font-family: $font-sans;
+ font-size: 0.8125rem;
+}
+#TOC ul, nav#TOC ul { list-style: none; padding-left: 0.75rem; border-left: 1px solid $rule; }
+#TOC li, nav#TOC li { margin: 0.25rem 0; }
+#TOC a, nav#TOC a { color: $ink-3; text-decoration: none; }
+#TOC a:hover, #TOC a.active, nav#TOC a:hover, nav#TOC a.active { color: $accent-600; border-left: 2px solid $accent-600; padding-left: 0.5rem; margin-left: -0.75rem; }
+
+// HR — editorial divider
+hr { border: none; border-top: 1px solid $rule; margin: 2.5rem 0; }
+hr.section-break { border: none; height: 4px; background: linear-gradient(to right, $accent-600 60px, $rule 60px); margin: 3rem 0; }
+
+// Listing pages (website blog/index)
+.quarto-listing .listing-item {
+ background: white;
+ border: 1px solid $rule;
+ border-top: 3px solid $accent-600;
+ border-radius: 8px;
+ padding: 1.5rem;
+ transition: border-color 120ms ease;
+}
+.quarto-listing .listing-item:hover { border-color: $rule-strong; }
+.quarto-listing .listing-title a { color: $ink; text-decoration: none; font-family: $font-display; font-weight: 800; }
+.quarto-listing .listing-date { font-family: $font-sans; font-size: 0.75rem; color: $ink-3; text-transform: uppercase; letter-spacing: 0.08em; }
+
+// Selection
+::selection { background: $accent-200; color: $ink; }
diff --git a/inst/quarto/theme/extras.css b/inst/quarto/theme/extras.css
new file mode 100644
index 0000000..ab51606
--- /dev/null
+++ b/inst/quarto/theme/extras.css
@@ -0,0 +1,63 @@
+/* Civilytics — extras layered on top of the SCSS theme.
+ Use for utilities that don't need to participate in the
+ Bootstrap variable cascade. */
+
+/* Stat callout — drop into reports for hero numbers */
+.stat-callout {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ gap: 2rem;
+ margin: 2rem 0 2.5rem;
+ padding: 1.5rem 0;
+ border-top: 3px double #0E1A2B;
+ border-bottom: 1px solid #D6CEBD;
+}
+.stat-callout .stat-num {
+ font-family: 'Source Serif 4', Georgia, serif;
+ font-weight: 700;
+ font-size: 3rem;
+ line-height: 1;
+ letter-spacing: -0.02em;
+ color: #0E1A2B;
+ font-variant-numeric: tabular-nums;
+ display: block;
+}
+.stat-callout .stat-label {
+ font-family: 'Inter', system-ui, sans-serif;
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: #5A6A82;
+ margin-top: 0.4rem;
+}
+.stat-callout .stat-label.accent { color: #C25311; }
+
+/* Pulse glyph utility — matches design system */
+.civilytics-pulse {
+ display: inline-block;
+ vertical-align: middle;
+ width: 30px;
+ height: 14px;
+}
+
+/* Section eyebrow — use above an h1/h2 */
+.eyebrow {
+ font-family: 'Inter', system-ui, sans-serif;
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: #C25311;
+ margin: 0 0 0.5rem;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+.eyebrow::before {
+ content: "";
+ display: inline-block;
+ width: 24px;
+ height: 2px;
+ background: #C25311;
+}
diff --git a/inst/quarto/typst/civilytics-typst.typ b/inst/quarto/typst/civilytics-typst.typ
new file mode 100644
index 0000000..097f4d4
--- /dev/null
+++ b/inst/quarto/typst/civilytics-typst.typ
@@ -0,0 +1,244 @@
+// =============================================================
+// Civilytics — Typst template for Quarto PDF
+// Usage in YAML:
+// format:
+// typst:
+// template: quarto/typst/civilytics-typst.typ
+// =============================================================
+
+#let paper-bg = rgb("#FAF7F2")
+#let ink = rgb("#0E1A2B")
+#let ink-2 = rgb("#2B3A52")
+#let ink-3 = rgb("#5A6A82")
+#let rule = rgb("#D6CEBD")
+#let rule-strong = rgb("#B8AE97")
+#let navy = rgb("#22406A")
+#let ember = rgb("#C25311")
+#let ember-dark = rgb("#923D00")
+#let plum = rgb("#6B3A5E")
+#let teal = rgb("#1F6F70")
+
+#let serif-stack = ("Source Serif 4", "Source Serif Pro", "Georgia", "Times New Roman")
+#let sans-stack = ("Inter", "Helvetica Neue", "Arial")
+#let display-stack = ("Libre Franklin", "Inter", "Helvetica Neue")
+#let mono-stack = ("JetBrains Mono", "Menlo", "Consolas")
+
+#let civilytics(
+ title: none,
+ subtitle: none,
+ authors: (),
+ date: none,
+ abstract: none,
+ toc: true,
+ doc,
+) = {
+ // Page setup
+ set page(
+ paper: "us-letter",
+ margin: (top: 1in, bottom: 1in, left: 1.1in, right: 1.1in),
+ fill: paper-bg,
+ header: context {
+ if counter(page).get().first() > 1 {
+ set text(font: sans-stack, size: 8pt, fill: ink-3, tracking: 0.06em)
+ upper[Civilytics Consulting]
+ h(1fr)
+ upper(if title != none { title } else { "" })
+ v(2pt)
+ line(length: 100%, stroke: 0.4pt + rule)
+ }
+ },
+ footer: context {
+ set text(font: sans-stack, size: 8pt, fill: ink-3)
+ line(length: 100%, stroke: 0.4pt + rule)
+ v(4pt)
+ grid(
+ columns: (1fr, auto, 1fr),
+ align: (left, center, right),
+ [civilytics.consulting],
+ counter(page).display("1 / 1", both: true),
+ [© 2026]
+ )
+ },
+ )
+
+ // Body text
+ set text(font: serif-stack, size: 10.5pt, fill: ink, lang: "en")
+ set par(justify: false, leading: 0.6em, first-line-indent: 0pt)
+
+ // Headings
+ show heading.where(level: 1): it => {
+ v(1.4em)
+ block[
+ #set text(font: display-stack, weight: 900, size: 22pt, fill: ink, tracking: -0.025em)
+ #it.body
+ ]
+ v(0.2em)
+ line(length: 60pt, stroke: 3pt + ember)
+ v(0.6em)
+ }
+ show heading.where(level: 2): it => {
+ v(1.1em)
+ line(length: 100%, stroke: 0.5pt + rule)
+ v(0.5em)
+ block[
+ #set text(font: display-stack, weight: 800, size: 16pt, fill: ink, tracking: -0.02em)
+ #it.body
+ ]
+ v(0.3em)
+ }
+ show heading.where(level: 3): it => {
+ v(0.8em)
+ block[
+ #set text(font: display-stack, weight: 700, size: 13pt, fill: ink, tracking: -0.015em)
+ #it.body
+ ]
+ v(0.2em)
+ }
+ show heading.where(level: 4): it => block[
+ #set text(font: sans-stack, weight: 600, size: 11pt, fill: ink-2)
+ #it.body
+ ]
+
+ // Links
+ show link: it => text(fill: navy, underline(it))
+
+ // Inline code
+ show raw.where(block: false): it => box(
+ fill: rgb("#F2EDE4"),
+ inset: (x: 3pt, y: 1pt),
+ outset: (y: 2pt),
+ radius: 1pt,
+ text(font: mono-stack, size: 0.92em, fill: ember-dark)[#it]
+ )
+
+ // Code blocks
+ show raw.where(block: true): it => block(
+ fill: ink,
+ width: 100%,
+ inset: 12pt,
+ radius: 4pt,
+ )[
+ #set text(font: mono-stack, size: 8.5pt, fill: paper-bg)
+ #it
+ ]
+
+ // Block quote
+ show quote.where(block: true): it => block(
+ stroke: (left: 2pt + ember),
+ inset: (left: 14pt, top: 4pt, bottom: 4pt),
+ spacing: 1.2em,
+ )[
+ #set text(font: serif-stack, size: 13pt, style: "italic", fill: ink)
+ #it.body
+ ]
+
+ // Figures
+ show figure: it => block(spacing: 1.4em)[
+ #it.body
+ #v(4pt)
+ #line(length: 100%, stroke: 0.4pt + rule)
+ #v(2pt)
+ #set text(font: sans-stack, size: 8.5pt, fill: ink-3)
+ #it.caption
+ ]
+
+ // Tables
+ set table(
+ stroke: (x, y) => (
+ top: if y == 0 { 1.5pt + ink } else if y == 1 { 0.6pt + rule-strong } else { 0pt },
+ bottom: if y == 0 { 0pt } else { 0.4pt + rule },
+ ),
+ inset: (x: 8pt, y: 6pt),
+ )
+ show table.cell.where(y: 0): set text(
+ font: sans-stack, size: 8pt, weight: 600, fill: ink-3, tracking: 0.06em
+ )
+
+ // ----- Title block -----
+ if title != none {
+ block[
+ #set text(font: sans-stack, size: 8pt, weight: 600, fill: ember, tracking: 0.1em)
+ #upper[— Civilytics Research]
+ ]
+ v(8pt)
+ block[
+ #set text(font: display-stack, weight: 900, size: 32pt, fill: ink, tracking: -0.035em)
+ #set par(leading: 0.4em)
+ #title
+ ]
+ if subtitle != none {
+ v(8pt)
+ block[
+ #set text(font: serif-stack, style: "italic", size: 14pt, fill: ink-2)
+ #set par(leading: 0.55em)
+ #subtitle
+ ]
+ }
+ v(18pt)
+ line(length: 100%, stroke: 3pt + ink)
+ v(2pt)
+ line(length: 100%, stroke: 0.4pt + rule)
+ v(8pt)
+
+ // Authors + date row
+ grid(
+ columns: (1fr, 1fr),
+ align: (left, right),
+ [
+ #set text(font: sans-stack, size: 7.5pt, fill: ink-3, tracking: 0.08em)
+ #upper[Authors] \
+ #set text(font: sans-stack, size: 10pt, fill: ink, weight: 500, tracking: 0em)
+ #if authors.len() > 0 {
+ authors.map(a => if type(a) == str { a } else { a.name }).join(", ")
+ }
+ ],
+ [
+ #set text(font: sans-stack, size: 7.5pt, fill: ink-3, tracking: 0.08em)
+ #upper[Published] \
+ #set text(font: sans-stack, size: 10pt, fill: ink, weight: 500, tracking: 0em)
+ #if date != none { date }
+ ]
+ )
+ v(24pt)
+
+ if abstract != none {
+ block(
+ stroke: (left: 2pt + ember),
+ inset: (left: 14pt),
+ )[
+ #set text(font: serif-stack, style: "italic", size: 12pt, fill: ink)
+ #set par(leading: 0.55em)
+ #abstract
+ ]
+ v(20pt)
+ }
+
+ if toc {
+ block[
+ #set text(font: sans-stack, size: 7.5pt, fill: ink-3, tracking: 0.08em)
+ #upper[Contents]
+ ]
+ v(4pt)
+ line(length: 100%, stroke: 0.4pt + rule)
+ v(8pt)
+ outline(title: none, indent: auto, depth: 3)
+ v(20pt)
+ pagebreak()
+ }
+ }
+
+ doc
+}
+
+// Quarto entry point
+#show: doc => civilytics(
+ title: $title$,
+ $if(subtitle)$subtitle: $subtitle$,$endif$
+ $if(by-author)$authors: ($for(by-author)$"$it.name.literal$",$endfor$),$endif$
+ $if(date)$date: $date$,$endif$
+ $if(abstract)$abstract: [$abstract$],$endif$
+ toc: $if(toc)$true$else$false$endif$,
+ doc
+)
+
+$body$
diff --git a/man/civilytics-package.Rd b/man/civilytics-package.Rd
index a132667..3eb4e0f 100644
--- a/man/civilytics-package.Rd
+++ b/man/civilytics-package.Rd
@@ -30,6 +30,15 @@ public-sector analysis.
}
}
+\section{Quarto templates}{
+
+\itemize{
+ \item [use_civilytics_revealjs()] -- install Reveal.js slide extension
+ \item [use_civilytics_theme()] -- install HTML/PDF/Typst document theme
+ \item [use_civilytics_brand()] -- install brand.yml only (Quarto 1.5+)
+}
+}
+
\seealso{
Useful links:
\itemize{
diff --git a/man/dot-copy_logos.Rd b/man/dot-copy_logos.Rd
new file mode 100644
index 0000000..1fdff42
--- /dev/null
+++ b/man/dot-copy_logos.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/quarto.R
+\name{.copy_logos}
+\alias{.copy_logos}
+\title{Copy logo SVGs from inst/img/ to a target directory}
+\usage{
+.copy_logos(
+ dest_dir,
+ path,
+ force,
+ files = c("civilytics-mark.svg", "civilytics-mark-reverse.svg",
+ "civilytics-wordmark.svg", "civilytics-wordmark-reverse.svg", "civilytics-pulse.svg")
+)
+}
+\arguments{
+\item{dest_dir}{Destination directory relative to `path`.}
+
+\item{path}{Project root.}
+
+\item{force}{Overwrite existing files?}
+
+\item{files}{Character vector of logo filenames to copy.}
+}
+\description{
+Copy logo SVGs from inst/img/ to a target directory
+}
+\keyword{internal}
diff --git a/man/dot-copy_pkg_file.Rd b/man/dot-copy_pkg_file.Rd
new file mode 100644
index 0000000..3c65fd8
--- /dev/null
+++ b/man/dot-copy_pkg_file.Rd
@@ -0,0 +1,24 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/quarto.R
+\name{.copy_pkg_file}
+\alias{.copy_pkg_file}
+\title{Copy a package file to a project directory}
+\usage{
+.copy_pkg_file(src, dst, path, force)
+}
+\arguments{
+\item{src}{Relative path within the installed package (under `inst/`).}
+
+\item{dst}{Destination path relative to `path`.}
+
+\item{path}{Project root.}
+
+\item{force}{Overwrite existing files?}
+}
+\value{
+Invisible logical indicating success.
+}
+\description{
+Copy a package file to a project directory
+}
+\keyword{internal}
diff --git a/man/quarto-helpers.Rd b/man/quarto-helpers.Rd
new file mode 100644
index 0000000..cd6eac2
--- /dev/null
+++ b/man/quarto-helpers.Rd
@@ -0,0 +1,11 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/quarto.R
+\name{quarto-helpers}
+\alias{quarto-helpers}
+\title{Install Civilytics Quarto Themes and Templates}
+\description{
+Helper functions that copy Civilytics Quarto assets from the installed
+package into your Quarto project directory. Logos are stored in a single
+canonical location (`inst/img/`) and copied to the paths expected by each
+template at install time.
+}
diff --git a/man/use_civilytics_brand.Rd b/man/use_civilytics_brand.Rd
new file mode 100644
index 0000000..85dcedf
--- /dev/null
+++ b/man/use_civilytics_brand.Rd
@@ -0,0 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/quarto.R
+\name{use_civilytics_brand}
+\alias{use_civilytics_brand}
+\title{Install the Civilytics brand file only}
+\usage{
+use_civilytics_brand(path = ".", force = FALSE)
+}
+\arguments{
+\item{path}{Character. Project directory to install into. Default `"."`.}
+
+\item{force}{Logical. Overwrite existing files? Default `FALSE`.}
+}
+\value{
+Invisible `NULL`.
+}
+\description{
+Copies `_brand.yml` and the logo assets it references into your
+Quarto project. This gives you Quarto 1.5+ automatic brand
+styling (colors, fonts, logos) without the full theme SCSS.
+}
+\examples{
+\dontrun{
+use_civilytics_brand()
+}
+}
diff --git a/man/use_civilytics_revealjs.Rd b/man/use_civilytics_revealjs.Rd
new file mode 100644
index 0000000..2712acc
--- /dev/null
+++ b/man/use_civilytics_revealjs.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/quarto.R
+\name{use_civilytics_revealjs}
+\alias{use_civilytics_revealjs}
+\title{Install the Civilytics Reveal.js extension}
+\usage{
+use_civilytics_revealjs(path = ".", force = FALSE)
+}
+\arguments{
+\item{path}{Character. Project directory to install into. Default `"."`.}
+
+\item{force}{Logical. Overwrite existing files? Default `FALSE`.}
+}
+\value{
+Invisible `NULL`.
+}
+\description{
+Copies the Civilytics Reveal.js Quarto extension into
+`_extensions/civilytics-reveal/` under `path`, including brand logos
+from the package. After running this function, add
+`format: civilytics-reveal-revealjs` to your `.qmd` YAML front-matter.
+}
+\examples{
+\dontrun{
+use_civilytics_revealjs()
+}
+}
diff --git a/man/use_civilytics_theme.Rd b/man/use_civilytics_theme.Rd
new file mode 100644
index 0000000..e806876
--- /dev/null
+++ b/man/use_civilytics_theme.Rd
@@ -0,0 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/quarto.R
+\name{use_civilytics_theme}
+\alias{use_civilytics_theme}
+\title{Install the Civilytics document theme}
+\usage{
+use_civilytics_theme(path = ".", force = FALSE)
+}
+\arguments{
+\item{path}{Character. Project directory to install into. Default `"."`.}
+
+\item{force}{Logical. Overwrite existing files? Default `FALSE`.}
+}
+\value{
+Invisible `NULL`.
+}
+\description{
+Copies Civilytics HTML, PDF (LaTeX), and Typst theme files into
+your Quarto project. Also installs `_brand.yml` and the logo
+assets it references.
+}
+\examples{
+\dontrun{
+use_civilytics_theme()
+}
+}
| |