---
title: 'Design notes: Homage system'
name: design-notes
description: Visual tokens, palette families, and component rules for albersdown.
output:
  rmarkdown::html_vignette:
    toc: yes
    toc_depth: 2
    df_print: kable
params:
  family: red
  preset: study
  base_size: 13
  content_width: 80
  style: minimal
vignette: |
  %\VignetteIndexEntry{Design notes: Homage system}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
css: albers.css
resource_files:
- albers.css
- albers.js
includes:
  in_header: |-
    <script src="albers.js"></script>

---

```{r setup, include=FALSE}
knitr::opts_chunk$set(
  collapse = TRUE, comment = "#>", fig.align = "center", fig.retina = 2,
  out.width = "100%", fig.width = 7, fig.asp = 0.618, message = FALSE, warning = FALSE
)
set.seed(123)
oldopt <- options(pillar.sigfig = 7, width = 80)
`%||%` <- function(a, b) if (is.null(a)) b else a
library(ggplot2)
if (requireNamespace("ggplot2", quietly = TRUE) && requireNamespace("albersdown", quietly = TRUE)) {
  oldtheme <- ggplot2::theme_set(albersdown::theme_albers(
    family = params$family,
    preset = params$preset,
    base_size = params$base_size
  ))
}
```

```{r albers-family, echo=FALSE, results='asis'}
cat(sprintf('<script>document.addEventListener("DOMContentLoaded",function(){document.body.classList.add("palette-%s");});</script>', params$family))
```

```{r albers-preset, echo=FALSE, results='asis'}
cat(sprintf('<script>document.addEventListener("DOMContentLoaded",function(){document.body.classList.add("preset-%s");});</script>', params$preset))
```

```{r albers-style, echo=FALSE, results='asis'}
style_class <- switch(
  tolower(params$style %||% "balanced"),
  minimal = "style-minimal",
  assertive = "style-assertive",
  balanced = ""
)
if (nzchar(style_class)) {
  cat(sprintf('<script>document.addEventListener("DOMContentLoaded",function(){document.body.classList.remove("style-minimal","style-assertive");document.body.classList.add("%s");});</script>', style_class))
}
```

```{r content-width, echo=FALSE, results='asis'}
cat(sprintf('<style>:root{--content:%sch}</style>', params$content_width))
```

<div class="homage-hero" aria-hidden="true"></div>
<div class="albers-plate" aria-hidden="true">
  <div class="albers-plate__outer"></div>
  <div class="albers-plate__inner"></div>
  <div class="albers-plate__core"></div>
</div>
<div class="albers-composition" data-seed="design-notes" data-density="7" aria-hidden="true"></div>
<div class="hr-notch" role="presentation"></div>

## What's new in this release {#whats-new}

- Token-driven design system (`inst/tokens/albers-tokens.yml`) now controls families, presets, and dark calibrations.
- Deterministic composition blocks (`.albers-composition`) add a signature visual motif.
- Semantic callout variants (`callout-note`, `callout-insight`, `callout-warning`, `callout-experiment`) are available.
- Contrast validation now checks family/preset combinations against AA thresholds.

### Semantic callouts {#semantic-callouts}

<div class="callout callout-note">
Use <code>callout-note</code> for neutral guidance.
</div>

<div class="callout callout-insight">
Use <code>callout-insight</code> for interpretation and design intent.
</div>

<div class="callout callout-warning">
Use <code>callout-warning</code> when a choice has clear tradeoffs.
</div>

<div class="callout callout-experiment">
Use <code>callout-experiment</code> for exploratory or provisional guidance.
</div>

### Typography and syntax tone {#typography-syntax}

```{r syntax-demo}
fit <- lm(mpg ~ wt + hp, data = mtcars)
coef(summary(fit))
```

### Contrast guardrail example {#contrast-guardrails}

```{r contrast-example}
contrast_ratio <- function(fg, bg) {
  to_rgb <- function(x) as.numeric(grDevices::col2rgb(x)) / 255
  to_lin <- function(u) ifelse(u <= 0.03928, u / 12.92, ((u + 0.055) / 1.055)^2.4)
  luma <- function(x) {
    rgb <- to_rgb(x); lin <- to_lin(rgb)
    0.2126 * lin[1] + 0.7152 * lin[2] + 0.0722 * lin[3]
  }
  l1 <- luma(fg); l2 <- luma(bg)
  if (l1 < l2) { tmp <- l1; l1 <- l2; l2 <- tmp }
  (l1 + 0.05) / (l2 + 0.05)
}

ratio_structural <- contrast_ratio("#C22B23", "#e6e9ed")
ratio_adobe <- contrast_ratio("#C22B23", "#ece9e7")
stopifnot(ratio_structural >= 4.5, ratio_adobe >= 4.5)

data.frame(
  context = c("structural bg", "adobe bg"),
  ratio = c(ratio_structural, ratio_adobe)
)
```

## Overview {.overview}

- One family per page (A900/A700/A500/A300 → roles)
- Links + focus use AA tones; anchors reveal a typographic dash on hover
- Callouts and tables use quiet A300 tints; code blocks use a structural left ribbon

## Palette families and roles {#palettes}

Families available: red, lapis, ochre, teal, green, violet. Each has four tones:

- A900: links and focus in light mode; strongest contrast
- A700: highlights (one series per plot); nav active marker
- A500: borders, dash anchors, code ribbons, structural rules
- A300: tints for callouts and table stripe; links/focus in dark mode

## Image-derived palettes {#image-palettes}

The original Homage families favor a single hue family per plot or page. When you want a different aesthetic (pulled from the grid image), use the image-derived palettes and scales. They mirror the API and are fully opt-in.

Sequential (image-based)

```{r img-seq, fig.height=3.2}
mtcars |>
  ggplot(aes(wt, mpg, colour = hp)) +
  geom_point(size = 2.2) +
  labs(title = "Image-derived sequential (lapis)") +
  albersdown::scale_color_albers_img(
    "lapis",
    discrete = FALSE,
    breaks = c(100, 150, 200, 250, 300)
  ) +
  ggplot2::guides(
    colour = ggplot2::guide_colorbar(
      title.position = "top",
      barheight = grid::unit(70, "pt"),
      barwidth = grid::unit(10, "pt")
    )
  ) +
  ggplot2::theme(legend.position = "right")
```

Diverging (image-based)

```{r img-div, fig.height=3.2}
df <- transform(datasets::faithful, centered = waiting - mean(waiting))
ggplot(df, aes(eruptions, centered, colour = centered)) +
  geom_point(alpha = 0.9) +
  labs(title = "Image-derived diverging (red <-> teal)") +
  albersdown::scale_color_albers_img_red_teal(neutral = "#e5e7eb")
```

Notes
- The `*_img` scales are opt-in and don’t change existing defaults.
- `neutral` can be set to `"#e5e7eb"` (site border token) for visual coherence with pages.
- Prefer the original single-family scales for a quieter, unified look; reach for the image-derived or diverging scales to emphasize contrasts.

## Links, anchors, and rhythm {#links-anchors}

Links and focus rings always meet AA. Move the cursor over H2/H3 to reveal the structural dash anchor.

Style modes
- `style: minimal` (default): lighter rules and quieter dash language.
- `style: balanced`: the calibrated middle ground.
- `style: assertive`: stronger edges and more emphatic structural marks.

<div class="hr-notch" role="presentation"></div>

## Callouts and code blocks {#callouts-code}

> TIP: Callouts use an A500 border and a subtle A300 tint (≈8–10%). Keep them short and purposeful.

```r
# a small, readable function
foo <- function(x) if (length(x) == 0) NA_real_ else mean(x)
foo(c(1, 2, 3))
```

The code fence stays flat: no drop shadow, with an A500 left ribbon and a compact copy control.

## Tables {#tables}

Base HTML tables pick up a quiet A300 zebra stripe and thin borders.

```{r}
knitr::kable(head(mtcars[, 1:5]), format = "html")
```

## Plots: one highlight, others neutral {#plots}

```{r}
mtcars$grp <- ifelse(mtcars$cyl == 6, "highlight", "other")
mtcars$grp <- factor(mtcars$grp, levels = c("other", "highlight"))
ggplot(mtcars, aes(wt, mpg, color = grp)) +
  geom_point(size = 2.2) +
  albersdown::scale_color_albers_highlight(
    family = params$family,
    tone = "A700",
    highlight = "highlight",
    other_name = "other"
  ) +
  labs(title = "One highlighted series; others neutral",
       subtitle = "A700 draws the eye; gray recedes",
       x = "Weight (1000 lbs)", y = "MPG")
```

## See also {#see-also}

<div class="hr-notch" role="presentation"></div>

- Getting started: `articles/getting-started.html`
- pkgdown template docs: `reference/index.html`
- Palette and roles: `#palettes`

```{r cleanup, include=FALSE}
options(oldopt)
if (exists("oldtheme")) ggplot2::theme_set(oldtheme)
```
