diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b0b90fd94..624ed6d086 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,124 @@
 
 Year: **Current (2023)** · [2022](./CHANGELOG-2022.md) · [2021](./CHANGELOG-2021.md)
 
+## 0.6.12
+
+[Released December 7, 2023.](https://github.com/observablehq/plot/releases/tag/v0.6.12)
+
+To better support dark mode, we’ve made a breaking change to default styles: the background color is now `unset` (transparent) instead of `white`. Additionally, marks that expect to use the same color as the background (for instance, the tip mark) now use the CSS custom property `--plot-background` instead of `white`.
+
+As since [version 0.6.6](#066), you can override Plot’s styles via the `plot-d6a7b5` class. For example, the following stylesheet applies a dark background and white foreground:
+
+```css
+.plot-d6a7b5 {
+  --plot-background: #333;
+  background: var(--plot-background);
+  color: white;
+}
+```
+
+Previously Plot forced you to choose between [rect](https://observablehq.com/plot/marks/rect), [bar](https://observablehq.com/plot/marks/bar), and [cell](https://observablehq.com/plot/marks/cell) based on whether the *x* and *y* scales were ordinal or quantitative. Rejoice: the [rect mark](https://observablehq.com/plot/marks/rect) now supports *band* scales so you can simply use rect in all cases! 🎉 (The bar and cell marks still offer conveniences for ordinal scales, so use them if you like.)
+
+<img src="./img/rect-band.png" width="626" alt="A bar chart showing the relative frequency of English letters.">
+
+```js
+Plot.rectY(alphabet, {x: "letter", y: "frequency"}).plot()
+```
+
+Categorical color scales now default to the new *observable10* color scheme by [Jeff Pettiross](https://github.com/pettiross). These colors are intended as a drop-in replacement for *tableau10* with similar ease of discrimination and ordering, but with a slightly more saturated vibe that helps charts pop.
+
+<img src="./img/observable10.png" width="600" alt="Color swatches of the ten colors in the observable10 color scheme: blue, yellow, red, teal, purple, pink, brown, light blue, green, and gray.">
+
+```js
+Plot.cellX(d3.range(10)).plot({color: {type: "categorical"}})
+```
+
+The new [difference mark](https://observablehq.com/plot/marks/difference) puts a metric in context by comparing it to another metric or constant value. Like the [area mark](https://observablehq.com/plot/marks/area), the region between two lines is filled; unlike the area mark, alternating color shows when the metric is above or below the comparison.
+
+<img src="./img/difference-zero.png" width="630" alt="A difference chart showing a moving average of global temperature anomaly; positive anomalies are shown in green, and negative anomalies are shown in blue.">
+
+```js
+Plot.differenceY(gistemp, Plot.windowY(28, {x: "Date", y: "Anomaly"})).plot()
+```
+
+The chart above shows a moving average of global temperature anomaly; above-average temperatures are shown in green and below-average temperatures are shown in blue.
+
+The difference mark can also compare a metric to its earlier self, showing change over time. The chart below shows the year-over-year change in Apple stock price; the gray region represents an increase over last year while the red region represents a decrease.
+
+<img src="./img/difference-shift.png" width="630" alt="A difference chart showing the year-over-year change in Apple stock price; the gray region represents an increase over last year, while the red region represents a decrease.">
+
+```js
+Plot.differenceY(
+  aapl,
+  Plot.shiftX("year", {
+    x: "Date",
+    y: "Close",
+    positiveFillOpacity: 0.2,
+    positiveFill: "currentColor",
+    negativeFillOpacity: 0.8,
+    negativeFill: "red"
+  })
+).plot()
+```
+
+The chart above is constructed using the new [shift transform](https://observablehq.com/plot/transforms/shift), which derives a time-shifted copy of a metric for the given time interval, enabling year–over-year, month-over-month, or any other period-over-period comparison.
+
+The new [find reducer](https://observablehq.com/plot/transforms/group#find) allows you to pivot data with the [bin](https://observablehq.com/plot/transforms/bin) and [group](https://observablehq.com/plot/transforms/group) transforms, effectively turning “tall” data (with fewer columns) into “wide” data (with more columns). For example, say you have time-series data that has separate rows for observed daily temperatures in San Francisco (SF) and San Jose (SJ):
+
+```csv
+date,station,tmax,tmin
+2020-12-31,SJ,59,43
+2020-12-31,SF,60,47
+2020-12-30,SJ,58,33
+2020-12-30,SF,57,40
+2020-12-29,SJ,62,38
+2020-12-29,SF,61,41
+2020-12-28,SJ,57,43
+2020-12-28,SF,56,47
+2020-12-27,SJ,62,43
+2020-12-27,SF,57,46
+2020-12-26,SJ,60,46
+2020-12-26,SF,61,49
+```
+
+To compare the average minimum temperatures of San Francisco and San Jose as another difference chart:
+
+<img src="./img/difference-find.png" width="630" alt="A difference chart showing moving averages of daily minimum temperatures of San Francisco and San Jose; the green region represents when San Francisco was warmer than San Jose, and the blue region when San Francisco was cooler than San Jose.">
+
+```js
+Plot.plot({
+  x: {tickFormat: "%b"},
+  y: {grid: true},
+  marks: [
+    Plot.ruleY([32]),
+    Plot.differenceY(
+      temperature,
+      Plot.windowY(
+        14,
+        Plot.groupX(
+          {y1: Plot.find((d) => d.station === "SJ"), y2: Plot.find((d) => d.station === "SF")},
+          {x: "date", y: "tmin"}
+        )
+      )
+    )
+  ]
+})
+```
+
+The green region above represents when San Francisco was warmer than San Jose, and the blue region when San Francisco was cooler than San Jose.
+
+The [tip mark](https://observablehq.com/plot/marks/tip) now supports a **preferredAnchor** option, providing greater control over tip placement: if the tip  fits within the frame at the preferred anchor, this anchor will be used. (In contrast, the **anchor** option uses the specified anchor regardless of whether it will fit.) The tip mark now also prefers the *bottom* anchor by default for a more traditional, less comic-like appearance.
+
+The [**marker** option](https://observablehq.com/plot/features/markers) now supports new marker types: *tick*, *tick-x*, and *tick-y*. The [bin](https://observablehq.com/plot/transforms/bin) and [group](https://observablehq.com/plot/transforms/group) transforms now pass _data_ to reducers. The [group](https://observablehq.com/plot/transforms/group) and [hexbin](https://observablehq.com/plot/transforms/hexbin) transforms now support _x_ and _y_ reducers.
+
+This release includes several additional fixes:
+
+* The default axis for a *time* scale now uses local time.
+* The text mark’s **lineWidth** option is now more accurate when **monospace** is true.
+* The tip mark no longer truncates **title** text.
+* The scale **type** option is now case-insensitive.
+* Transform type definitions have correct overload precedence.
+
 ## 0.6.11
 
 [Released September 20, 2023.](https://github.com/observablehq/plot/releases/tag/v0.6.11)
diff --git a/docs/features/markers.md b/docs/features/markers.md
index d9396ac699..82f306c8c6 100644
--- a/docs/features/markers.md
+++ b/docs/features/markers.md
@@ -56,9 +56,9 @@ The following named markers are supported:
 * *dot* - a filled *circle* without a stroke and 2.5px radius
 * *circle*, equivalent to *circle-fill* - a filled circle with a white stroke and 3px radius
 * *circle-stroke* - a hollow circle with a colored stroke and a white fill and 3px radius
-* *tick* - a small opposing line
-* *tick-x* - a small horizontal line
-* *tick-y* - a small vertical line
+* *tick* <VersionBadge version="0.6.12" pr="1872" /> - a small opposing line
+* *tick-x* <VersionBadge version="0.6.12" pr="1872" /> - a small horizontal line
+* *tick-y* <VersionBadge version="0.6.12" pr="1872" /> - a small vertical line
 
 If **marker** is true, it defaults to *circle*. If **marker** is a function, it will be called with a given *color* and must return an [SVG marker element](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker).
 
diff --git a/docs/features/scales.md b/docs/features/scales.md
index 52f3e2fc39..78b0da69ba 100644
--- a/docs/features/scales.md
+++ b/docs/features/scales.md
@@ -11,7 +11,7 @@ const align = ref(0.5);
 const radius = ref(8);
 const schemeq = ref("turbo");
 const schemed = ref("rdbu");
-const schemeo = ref("Tableau10");
+const schemeo = ref("Observable10");
 const interpolateq = ref("rgb");
 const anomaly = gistemp.map((d) => d.Anomaly);
 const aapl = shallowRef([]);
@@ -457,6 +457,7 @@ Plot also provides color schemes for discrete data. Use the *categorical* type f
         <option>Accent</option>
         <option>Category10</option>
         <option>Dark2</option>
+        <option>Observable10</option>
         <option>Paired</option>
         <option>Pastel1</option>
         <option>Pastel2</option>
@@ -730,7 +731,7 @@ Plot.plot({
 
 The normal scale types — *linear*, *sqrt*, *pow*, *log*, *symlog*, and *ordinal* — can be used to encode color. In addition, Plot supports special scale types for color:
 
-* *categorical* - like *ordinal*, but defaults to *tableau10*
+* *categorical* - like *ordinal*, but defaults to *observable10*
 * *sequential* - like *linear*
 * *cyclical* - like *linear*, but defaults to *rainbow*
 * *threshold* - discretizes using thresholds given as the **domain**; defaults to *rdylbu*
@@ -878,6 +879,7 @@ Plot.plot({
       ["Accent", d3.schemeAccent],
       ["Category10", d3.schemeCategory10],
       ["Dark2", d3.schemeDark2],
+      ["Observable10", Plot.scale({color: {type: "categorical"}}).range],
       ["Paired", d3.schemePaired],
       ["Pastel1", d3.schemePastel1],
       ["Pastel2", d3.schemePastel2],
diff --git a/docs/marks/difference.md b/docs/marks/difference.md
index 9480ef259b..f551e2d7ea 100644
--- a/docs/marks/difference.md
+++ b/docs/marks/difference.md
@@ -18,7 +18,7 @@ onMounted(() => {
 
 </script>
 
-# Difference mark <VersionBadge pr="1896" />
+# Difference mark <VersionBadge version="0.6.12" pr="1896" />
 
 The **difference mark** puts a metric in context by comparing it. Like the [area mark](./area.md), the region between two lines is filled; unlike the area mark, alternating color shows when the metric is above or below the comparison value.
 
diff --git a/docs/marks/tip.md b/docs/marks/tip.md
index 0079e8afb9..388d10c7de 100644
--- a/docs/marks/tip.md
+++ b/docs/marks/tip.md
@@ -184,7 +184,7 @@ Plot.plot({
 ```
 :::
 
-If you don’t specify an explicit **anchor**, the tip mark will choose one automatically, using the **preferredAnchor** <VersionBadge pr="1872" /> if it fits. The preferred anchor defaults to *bottom*, except when using the **tip** option and the [pointerY pointing mode](../interactions/pointer.md), in which case it defaults to *left*. In some cases, it may not be possible to fit the tip within the plot’s frame; consider setting the plot’s **style** to `overflow: visible;` to prevent the tip from being truncated.
+If you don’t specify an explicit **anchor**, the tip mark will choose one automatically, using the **preferredAnchor** <VersionBadge version="0.6.12" pr="1872" /> if it fits. The preferred anchor defaults to *bottom*, except when using the **tip** option and the [pointerY pointing mode](../interactions/pointer.md), in which case it defaults to *left*. In some cases, it may not be possible to fit the tip within the plot’s frame; consider setting the plot’s **style** to `overflow: visible;` to prevent the tip from being truncated.
 
 The tip mark is compatible with transforms that derive **x** and **y** dynamically from data, such as the [centroid transform](../transforms/centroid.md) which computes polygon centroids. Below, a map of the United States shows state names. We reduce the size of the tips by setting the **textPadding** option to 3 pixels instead of the default 8.
 
diff --git a/docs/transforms/group.md b/docs/transforms/group.md
index df128f8dcc..9108d4f7d8 100644
--- a/docs/transforms/group.md
+++ b/docs/transforms/group.md
@@ -368,8 +368,8 @@ The following named reducers are supported:
 * *deviation* - the standard deviation
 * *variance* - the variance per [Welford’s algorithm](https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm)
 * *identity* - the array of values
-* *x* - the group’s *x* value (when grouping on *x*)
-* *y* - the group’s *y* value (when grouping on *y*)
+* *x* <VersionBadge version="0.6.12" pr="1916" /> - the group’s *x* value (when grouping on *x*)
+* *y* <VersionBadge version="0.6.12" pr="1916" /> - the group’s *y* value (when grouping on *y*)
 
 In addition, a reducer may be specified as:
 
@@ -440,7 +440,7 @@ Plot.groupZ({x: "proportion"}, {fill: "species"})
 
 Groups on the first channel of **z**, **fill**, or **stroke**, if any. If none of **z**, **fill**, or **stroke** are channels, then all data (within each facet) is placed into a single group.
 
-## find(*test*) {#find}
+## find(*test*) {#find} <VersionBadge version="0.6.12" pr="1914" />
 
 ```js
 Plot.groupX(
diff --git a/docs/transforms/hexbin.md b/docs/transforms/hexbin.md
index 1ca25d311b..91198eaf64 100644
--- a/docs/transforms/hexbin.md
+++ b/docs/transforms/hexbin.md
@@ -197,8 +197,8 @@ The following named reducers are supported:
 * *variance* - the variance per [Welford’s algorithm](https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm)
 * *mode* - the value with the most occurrences
 * *identity* - the array of values
-* *x* - the hexagon’s *x* center
-* *y* - the hexagon’s *y* center
+* *x* <VersionBadge version="0.6.12" pr="1916" /> - the hexagon’s *x* center
+* *y* <VersionBadge version="0.6.12" pr="1916" /> - the hexagon’s *y* center
 
 In addition, a reducer may be specified as:
 
diff --git a/docs/transforms/shift.md b/docs/transforms/shift.md
index 8ab45d3a73..297dbeb8b5 100644
--- a/docs/transforms/shift.md
+++ b/docs/transforms/shift.md
@@ -13,7 +13,7 @@ onMounted(() => {
 
 </script>
 
-# Shift transform <VersionBadge pr="1896" />
+# Shift transform <VersionBadge version="0.6.12" pr="1896" />
 
 The **shift transform** is a specialized [map transform](./map.md) that derives an output **x1** channel by shifting the **x** channel; it can be used with the [difference mark](../marks/difference.md) to show change over time. For example, the chart below shows the price of Apple stock. The <span style="border-bottom: solid #01ab63 3px;">green region</span> shows when the price went up over the given interval, while the <span style="border-bottom: solid #4269d0 3px;">blue region</span> shows when the price went down.
 
diff --git a/img/difference-find.png b/img/difference-find.png
new file mode 100644
index 0000000000..06ef816340
Binary files /dev/null and b/img/difference-find.png differ
diff --git a/img/difference-shift.png b/img/difference-shift.png
new file mode 100644
index 0000000000..fa01ff0ba8
Binary files /dev/null and b/img/difference-shift.png differ
diff --git a/img/difference-zero.png b/img/difference-zero.png
new file mode 100644
index 0000000000..7cb3a5c9db
Binary files /dev/null and b/img/difference-zero.png differ
diff --git a/img/observable10.png b/img/observable10.png
new file mode 100644
index 0000000000..284a5aacbf
Binary files /dev/null and b/img/observable10.png differ
diff --git a/img/rect-band.png b/img/rect-band.png
new file mode 100644
index 0000000000..dd82d59dd6
Binary files /dev/null and b/img/rect-band.png differ
diff --git a/package.json b/package.json
index a3258edfc3..2a9dfe76dd 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@observablehq/plot",
   "description": "A JavaScript library for exploratory data visualization.",
-  "version": "0.6.11",
+  "version": "0.6.12",
   "author": {
     "name": "Observable, Inc.",
     "url": "https://observablehq.com"