feat: add Quarto themes and templates (revealjs, HTML, PDF, Typst)
R-CMD-check / R CMD check (push) Successful in 3m52s

Integrate Civilytics Reveal.js slide extension, HTML/PDF/Typst document
themes, and _brand.yml into the package under inst/quarto/. Three new
helper functions (use_civilytics_revealjs, use_civilytics_theme,
use_civilytics_brand) copy assets into a user's Quarto project. Logos
are stored once in inst/img/ and distributed at install time.
This commit is contained in:
2026-05-20 09:39:27 -06:00
parent 41fe2b6170
commit 136c53643c
25 changed files with 3641 additions and 2 deletions
+4 -2
View File
@@ -8,7 +8,8 @@ Authors@R:
Description: Provides a complete ggplot2 brand theme system for Civilytics Description: Provides a complete ggplot2 brand theme system for Civilytics
Consulting LLC, including editorial light, dark, and slide-optimized themes; Consulting LLC, including editorial light, dark, and slide-optimized themes;
10 curated color palettes for qualitative, sequential, and diverging data; 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. analysis.
License: LGPL (>= 3) License: LGPL (>= 3)
URL: https://gitea.civilytics.org/Civilytics/civilyticsR URL: https://gitea.civilytics.org/Civilytics/civilyticsR
@@ -28,7 +29,8 @@ Imports:
Encoding: UTF-8 Encoding: UTF-8
Suggests: Suggests:
testthat (>= 3.0.0), testthat (>= 3.0.0),
tidycensus tidycensus,
quarto
Config/testthat/edition: 3 Config/testthat/edition: 3
Config/roxygen2/version: 8.0.0 Config/roxygen2/version: 8.0.0
RoxygenNote: 7.3.3 RoxygenNote: 7.3.3
+3
View File
@@ -49,6 +49,9 @@ export(theme_civilytics_map)
export(theme_civilytics_slide) export(theme_civilytics_slide)
export(theme_civilytics_slide_map) export(theme_civilytics_slide_map)
export(trim_max) export(trim_max)
export(use_civilytics_brand)
export(use_civilytics_revealjs)
export(use_civilytics_theme)
export(waldInterval) export(waldInterval)
export(z_gap_test) export(z_gap_test)
export(z_univariate) export(z_univariate)
+7
View File
@@ -19,6 +19,13 @@
#' \item [scale_color_civilytics()] / [scale_fill_civilytics()] -- ggplot2 scales #' \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 #' @keywords internal
#' @importFrom stats qbeta #' @importFrom stats qbeta
#' @importFrom stats qnorm #' @importFrom stats qnorm
+193
View File
@@ -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)
}
+66
View File
@@ -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
+80
View File
@@ -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.
+65
View File
@@ -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
@@ -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
@@ -0,0 +1,80 @@
<!-- Civilytics Reveal — brand chrome injector -->
<script>
(function () {
// The Civilytics wordmark is the bold serif "Civilytics" lockup with the pulse
// waveform riding inline at the end. It ships as a baked SVG in two variants:
// ink-on-paper (default) and paper-on-ink (reverse) for dark backgrounds.
// We render it as an <img> so the official artwork is preserved exactly —
// the file picks up its color from the SVG itself, not from CSS currentColor.
var ASSET_BASE = (function () {
// Find the URL this script's <link rel="stylesheet" href="..civilytics.css..">
// was loaded from, so we can resolve sibling assets the same way.
var links = document.querySelectorAll('link[rel="stylesheet"]');
for (var i = 0; i < links.length; i++) {
var href = links[i].getAttribute('href') || '';
var m = href.match(/^(.*\/)civilytics\.css(\?|$)/);
if (m) return m[1] + 'assets/';
}
// Fallback: assume Quarto-style sibling layout
return '_extensions/civilytics-reveal/assets/';
})();
function makeWordmarkImg(reverse) {
var img = document.createElement('img');
img.className = 'civilytics-wordmark';
img.alt = 'Civilytics';
img.src = ASSET_BASE + (reverse ? 'civilytics-wordmark-reverse.svg' : 'civilytics-wordmark.svg');
return img;
}
function injectWordmark(slide, reverse) {
if (!slide) return;
if (slide.querySelector(':scope > .civilytics-wordmark')) return;
slide.insertBefore(makeWordmarkImg(reverse), slide.firstChild);
}
function init() {
if (!window.Reveal) { setTimeout(init, 50); return; }
var root = document.querySelector('.reveal');
if (!root) return;
// Title slide — paper background, ink wordmark
document.querySelectorAll(
'.slides #title-slide, .slides section.title-slide'
).forEach(function (s) { injectWordmark(s, false); });
// Thank-you slide — ink background, reversed wordmark
document.querySelectorAll('.slides section.thank-you').forEach(function (s) {
injectWordmark(s, true);
});
// Brand footer (pulse glyph + "Civilytics") bottom-left on body slides.
// Uses inline SVG so it inherits the slide's text color via currentColor.
if (!root.querySelector('.civilytics-footer')) {
var footer = document.createElement('div');
footer.className = 'civilytics-footer';
footer.setAttribute('aria-hidden', 'true');
footer.innerHTML =
'<svg viewBox="0 0 30 14" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter">' +
'<path d="M0 8 L5 8 L8 3 L12 13 L16 1 L20 11 L23 8 L30 8"/>' +
'</svg>' +
'<span>Civilytics</span>';
root.appendChild(footer);
}
function updateChrome() {
var current = Reveal.getCurrentSlide();
if (!current) return;
var hide = current.matches('.title-slide, #title-slide, .section-divider, .section, .thank-you, .full-bleed, .no-chrome');
document.body.classList.toggle('civilytics-hide-chrome', !!hide);
}
Reveal.on('ready', updateChrome);
Reveal.on('slidechanged', updateChrome);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
@@ -0,0 +1,3 @@
<!-- Civilytics Reveal — fonts preconnect (theme CSS also @imports, this just warms the connection) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+41
View File
@@ -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
+112
View File
@@ -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}}
+73
View File
@@ -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;
+331
View File
@@ -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; }
+63
View File
@@ -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;
}
+244
View File
@@ -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$
+9
View File
@@ -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{ \seealso{
Useful links: Useful links:
\itemize{ \itemize{
+27
View File
@@ -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}
+24
View File
@@ -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}
+11
View File
@@ -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.
}
+26
View File
@@ -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()
}
}
+27
View File
@@ -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()
}
}
+26
View File
@@ -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()
}
}