Design System

Tokens, components, and slide conventions for the PSY 348 decks

About this document

Reference for the shared custom.scss theme used across PSY 348/360/442 revealjs decks. This is the source of truth for design tokens and component conventions — it lives in this repo so it can never drift from the code it describes (a change to custom.scss and a change to this doc belong in the same commit).

For the narrative of how and why specific decisions were made session-by-session, see the “Teaching” claude.ai project doc (course_site_design_philosophy.md). This file is the settled result; that one is the working log.

Tokens

Defined at the top of custom.scss, under scss:defaults.

Token Value Use
$body-color #2F3E46 Body/paragraph/list text (dark desaturated green-gray)
$text-muted lighten($body-color, 50%) Captions, sub-labels
$body-bg #E9F1EC Slide background (sage pastel)
$accent #5C715E Headings, links, primary pill/badge fill
$accent2 #8FAF9E Card/badge borders, secondary accents
$accent3 #A7C7B5 Lighter borders (e.g. hero-def card)
$accent4 #DCE8E1 Lightest fill (e.g. you-callout background)

Fonts: Fira Sans (body/headings), Fira Code (code), Atkinson Hyperlegible (fallback — chosen for accessibility), Material Icons (icon font, used throughout via .material-icons).

Rule of thumb: don’t hardcode hex values in new components — use the token so a future palette change is a one-line edit.

Title slides — two patterns, don’t mix them

Most decks use Quarto’s YAML title: field, which auto-generates #title-slide (styled directly, near the top of the rules section: .title, .subtitle, .quarto-title-authors).

Some decks (lab1.qmd, lec2.qmd) have no YAML title and instead hand-build a manual ## {#custom-title} slide, styled via .cust-title / .cust-sub / .cust-pic / .cust-author. This pattern has a known gotcha — see Known Gotchas below. If you’re starting a new deck, prefer the YAML title: approach unless you specifically need the custom layout; it avoids the gotcha entirely.

lec2.qmd’s custom title also has an optional animated background layer (#custom-title, .title-bg-icons, .ghost-nodes) — six drifting Material Icons + five floating dots, slow and low-opacity by design. Copy this only if a deck wants that same “quiet motion” flourish; it’s not a default every custom-title slide needs.

Core components

.card — generic white card with hover-lift, used for image+caption groupings. .card-img crops to a fixed 260px height (object-fit: cover) so mixed-aspect-ratio source images stay uniform in a row.

.callout-important — Quarto’s default callout is restyled from red/orange to the sage accent, since on this site “important” means “definition, pay attention” rather than “warning/error.”

.blank-tag — small corner pill marking a “fill in the blank” slide for the presenter (axis/title-stripped plot for in-class guessing). White text on accent background.

.you-callout — inline pill for a short personalized aside (e.g. “…and you scored a 67”), lighter weight than .callout-important.

.stat-card — bordered card for a single big number + sublabel (.stat-num / .stat-sub).

.img-placeholder — dashed-border stand-in for “find a real image later,” so a slide’s layout/intent is legible before the asset hunt is done. Once a real photo replaces it, img inside is capped at max-height: 340px so a full-res photo can’t push content off the bottom of the slide.

.badge-row / .content-badge and .icon-row / .icon-row-item — two weights of “icon + short label” layout. .content-badge has card chrome (border, shadow, optional heading) for a handful of prominent items; .icon-row-item is bare icon + caption for recapping several examples/terms compactly (e.g. under a bullet list).

.bio-cluster — the “Hi, I’m Dave!” circular-photo-plus-scattered-badges layout (fixed pixel positioning, tuned to one specific slide). Not intended as a general-purpose pattern — treat as a one-off unless another bio slide needs the exact same shape.

.marquee-wrap / .marquee-track — continuous horizontal scroll (e.g. course-eval pull quotes), pauses on hover so a quote can be read.

.book-anim — small CSS crossfade flourish for a closing/Q&A slide.

.z-excerpt / .z-hit — wraps a small pandoc pipe table (a few rows/columns of the z-table) in a white rounded card with a sage header; the looked-up value is marked inline as [0.2611]{.z-hit} (sage pill). Used on lec6’s worked z-table examples so the slide shows exactly which row/column intersect instead of a full-page table image.

.z-cards / .z-card — 2x2 grid of white bordered cards (same chrome as .stat-card), each holding a title, a one-line question (.z-card-title, .z-card-q) and a small Raw / z-score panel-tabset with a histogram. First use: lec6’s “Compared to what?” slide (simulated rnorm() data, red line at the example score).

.keycap / .keycap-line — keyboard-key icon: shifted symbol small on top (.keycap-top), the key you actually press large below (.keycap-main). Markup: <span class="keycap"><span class="keycap-top">~</span><span class="keycap-main">`</span></span>. Wrap a sentence in ::: keycap-line to enlarge it. Used for the drop-console key (backtick) on lec7 and exam1_scrap.

Drop console (`) — custom.scss labels the two panes (#editor = SCRIPT, #terminal = CONSOLE) so students can tell them apart. The extension’s drop-runtime.js is also patched (RArgs:[] -> RArgs:["--quiet"]) to suppress R’s startup banner; reinstalling the extension undoes that one-liner.

.hl — inline highlight (accent3 pill) for picking parts of a sentence out of a blockquote, e.g. the pieces of an APA results sentence: [one-sample t-test]{.hl}. Used on lec9’s “Reporting Results” slide.

Term + definition (hero-def / term-list)

The newest component (added 9/4, applied first on lec3.qmd’s Operationalization slide). Built specifically with a future site-wide glossary in mind.

Markup uses pandoc’s native definition-list syntax — already the site’s convention (see lec1.qmd’s “Statistics / Describing / Analyzing…” slide) — rather than a bespoke div structure. Term / : definition compiles to plain <dl><dt><span class="term-pill">Term</span></dt><dd>Definition text</dd></dl>. That’s the most mechanical possible shape to script-extract later: grep every .term-pill span and its dd sibling, across every deck, no per-slide parsing needed.

.hero-def — one pivotal term per slide. Pill sits half-overlapping a white bordered card; definition is the card body underneath. The containing section auto-centers the card in the space below the slide heading via :has():

.reveal .slides section:has(> .hero-def) {
  height: 100%;
  display: flex;
  flex-direction: column;
}
.hero-def { margin: auto; /* ... */ }

Markup:

::: hero-def
[Operationalization]{.term-pill}
: The process of defining the measurement of a phenomenon that is not
  directly measurable (AKA a latent variable) though its existence is
  implied by other phenomena.
:::

.term-list — several terms on one slide. Lighter weight, no card chrome (a repeated hero-def five or six times would be visually loud). Each term/definition pair should be wrapped in its own ::: fragment ::: so they reveal one at a time rather than landing all at once:

::: term-list
::: fragment
[Term One]{.term-pill}
: Definition one.
:::
::: fragment
[Term Two]{.term-pill}
: Definition two.
:::
:::

Open item: lec1.qmd’s existing 7-term slide predates this component and doesn’t use ::: fragment ::: per pair — a good candidate to retrofit into .term-list, not yet done.

hero-example — an optional worked-example panel-tabset nested inside a hero-def card, for showing how the same construct gets operationalized different ways. First use: lec3.qmd’s Operationalization slide, tabbed Self-Report / Physiological / Behavioral measures of “disgust” (Dave’s go-to running construct across different areas of psych). Markup nests directly inside the hero-def div, after the definition:

::: hero-def
[Operationalization]{.term-pill}
: The process of defining the measurement of a phenomenon that is not
  directly measurable...

::: hero-example
::: panel-tabset
### Self-Report

"How disgusted do you feel right now?" -- rated on a 1-7 scale.

### Physiological

Skin conductance or facial EMG response while viewing a disgusting image.

### Behavioral

Willingness to touch a "contaminated" object, or how far participants
move their chair away from it.
:::
:::
:::

Styling is scoped to .hero-def .hero-example specifically (restyled nav-tabs: muted uppercase labels, active tab gets the accent color + underline) so it never touches the plain default Bootstrap tabset styling already used elsewhere on the site (lec2’s Anscombe plots) — those keep rendering with their untouched default look.

Slide structure conventions

Observed across the stats decks (lec8 to lec16) and now the standard for new slides.

Heading pattern: ## title plus ### subtitle. Each slide is a level-2 heading (the topic, reused across a run of slides, e.g. “Coffee Example”) followed by a level-3 heading that says what this slide does (“Manual”, “Data overview”). The subtitle is what lets a topic span several slides without repeating itself. Slides that are a single callout or carry the topic in their own title (a Recap, a Summary, a “Your tasks” callout) may skip the ###. Never use ### as a bullet label inside a slide.

Callout colors have jobs.

Callout Use it for Examples
.callout-tip The working: formulas, steps, \(H_0\), “Compute t”, “Decide”, “Effect size”, “Your tasks” the three-step manual calculation on lec11’s Coffee Example
.callout-important Things to stop and notice: \(H_A\), assumptions, answers, definitions that matter “Assumptions” on lec9/10/11, the “Answer” box on practice slides

Rule of thumb: tip = what we do, important = what to watch for or what the answer is. Side-by-side \(H_0\) / \(H_A\) slides pair a tip (left) with an important (right). Use .nonincremental on a callout that should appear all at once rather than line by line. .callout-note, -warning and -caution are not used on this site.

Formula colors use the deck palette, not named CSS colors. When coloring parts of a formula to match a bullet below it, use hex tokens from the table above (\color{#B5483B}{...} and [text]{style="color:#B5483B"}), never red, orange, blue. Good pairings on the pale sage background: #B5483B (“look here” red), #5C715E (accent), #2F3E46 (body text). Avoid amber/orange text: it is hard to read against the background.

Symbol colors for the statistics lectures. Each symbol keeps one color everywhere, so students learn “this color = this piece” across decks. Defined in lec4 and reused in lec6 and lec10:

Symbol Meaning Color
\(\bar{x}\) mean #B5483B
\(s\) standard deviation #5C715E
\(\sum\) sum #3E6E8E
\(n\) sample size #8A5A3C
\(s^2\) variance #7A5C99
\(x^2\) squared value #A0527A

The last four are muted tones chosen to stay distinguishable from the red and sage on the pale background. Reuse these rather than inventing new ones; add a row here when a new symbol needs a color.

Define pivotal terms with the term components. Open a method’s deck with a .hero-def (“What is a …?”), and close with a .term-list of the vocabulary for that lecture (each pair in its own ::: fragment :::). The glossary extraction depends on the .term-pill markup.

Fit check. Slides must fit at 1600x900 with every fragment revealed. Run scripts/check_slide_overflow.R <deck> after rendering (the title slide always reports ~980px; ignore it). Fix by splitting the slide or moving the plain-English line to the next slide, not by shrinking fonts.

Known gotchas

CSS specificity beats source order, every time. .reveal .slide p { color: $body-color; } sits near the top of the file and directly targets any <p> on a slide — including the <p> pandoc wraps fenced-div content in. A new component that puts colored text in a plain <p> inside a div (e.g. .my-thing p { color: #fff; }) will silently lose to that rule if its own selector has lower or equal specificity (class + tag vs. two classes + tag), no matter where in the file it’s written or how late it’s added. This bit .blank-tag and .you-callout once already (fixed by matching specificity: .reveal .slide .blank-tag p). Before shipping any new pill/badge/tag component with its own text color, check what specificity the existing global text rules have and match or exceed it — don’t assume one extra class is enough.

The ## {#custom-title} manual title-slide pattern loses top positioning. Quarto’s reveal.css never sets an explicit top on .reveal .slides > section.present in this site’s config. Normally that’s invisible because the auto-generated #title-slide slide is styled specially — but a manual custom-title slide is just an ordinary .slide.level2 section like any other, which exposes the gap: the browser resolves the unset top as the element’s normal-flow “static position,” landing the present slide below the previous slide’s canvas instead of overlapping it at the top (reads as “content pushed off / cut off”). Fixed globally with .reveal .slides > section.present { top: 0 !important; } — a no-op on already-correct decks, so this fix never needs revisiting per-deck.

Dependency checking

scripts/check_dependencies.R scans every .qmd/.R file for R package dependencies — explicit (library()/require()), namespace-qualified (pkg::fn()), and a curated list of “invisible” triggers (e.g. gt::gtsave(*.png) silently needs webshot2 + chromote). Run it on a fresh workspace (Rscript scripts/check_dependencies.R) before rendering to catch missing packages up front instead of one broken chunk at a time. If a render fails on a package that was never named directly in a .qmd, add its trigger pattern to the implicit_triggers list in that script so future runs catch it automatically.

Open items

  • Replace named CSS colors in formulas with palette hex tokens in the older decks. Done: lec4, lec6, lec10, lec11. Remaining: a few lines each in lec3/5/9/16. scripts/check_deck.R flags them ([colors]).
  • Retrofit lec1.qmd’s 7-term slide into .term-list (with per-pair ::: fragment :::).
  • Missing images still unresolved on lec3/5/7/8/11/15c (lec6’s z-table.png refs replaced by .z-excerpt tables, 9/28) (pre-existing, found during the folder reorg — not a design-system issue but tracked here since it blocks a clean render).
  • Extract the glossary: once several decks use .term-pill/dl/dd, a script can walk every deck and build a site-wide glossary page from the markup alone.
  • lec3.qmd’s Bobo doll slide still has a dead image and an unverified proposed swap (a moral-disgust “moving a chair” paradigm) — needs Dave to confirm the actual citation before it goes on a slide; not implemented.
  • Icon-library matching (future): scan a lecture’s text for key terms/concepts and map each to an icon (Material Icons / Bootstrap Icons, both already loaded in custom.scss) to auto-suggest slide and title-slide icons. title-bg.html currently takes a hand-picked icon list (data-title-icons); this step would generate it.
  • Dingbat borders: first trial on lec6.qmd’s title slide (#title-slide::before/::after, hex motif matching the footer). Reusable .dingbat-rule divider with .star / .vine variants exists in custom.scss but is unused on section slides — decide which motif(s) become the standard before spreading.