feat: add Quarto themes and templates (revealjs, HTML, PDF, Typst)
R-CMD-check / R CMD check (push) Successful in 3m52s
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:
+4
-2
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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}}
|
||||||
@@ -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;
|
||||||
@@ -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; }
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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$
|
||||||
@@ -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{
|
||||||
|
|||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -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.
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user