You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
motief/reports/overton_window/overton_window.qmd

700 lines
27 KiB

---
title: "Has the Overton Window Shifted?"
subtitle: "Acceptance Through Moderation in the Dutch Tweede Kamer (2016–2026)"
author: "Stemwijzer Analysis"
date: today
format: html
jupyter: python3
---
```{python}
#| label: setup
#| include: false
import duckdb
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from pathlib import Path
ROOT = Path(".").resolve().parents[1]
DB_PATH = str(ROOT / "data" / "motions.db")
con = duckdb.connect(DB_PATH, read_only=True)
BREAK_YEAR = 2024
PARTY_COLOURS = {
"VVD": "#1E73BE", "PVV": "#002366", "D66": "#00A36C",
"CDA": "#4CAF50", "CU": "#0288D1", "NSC": "#FF8F00",
"SGP": "#F4511E", "FVD": "#6A1B9A", "JA21": "#7B1FA2",
"BBB": "#8D6E63", "SP": "#E53935", "GroenLinks-PvdA": "#2E7D32",
"PvdD": "#43A047", "Volt": "#572AB7", "DENK": "#00897B",
}
```
> **Verdict:** The Overton window widened — more right-wing positions became
> politically acceptable. But the mechanism was right-wing moderation, not
> centrist conversion. The effect may be temporary.
## Introduction
Did the PVV's November 2023 election victory shift the Dutch Overton window?
The conventional narrative is clear: a far-right party won the largest share of
seats, entered government for the first time in July 2024, and the political
center responded by adopting more right-wing positions. Centrist parties,
according to this story, moved right to accommodate the new political reality.
The data tells a different story.
Using 29,591 Tweede Kamer motions with full MP-level vote records, Procrustes-aligned
SVD spatial analysis, and 2D extremity scoring (stijl-extremiteit vs materiële
impact), we find that **the Overton window widened**: centrist support for
right-wing motions surged from 25% to 51%, while centrist support for left-wing
motions stayed flat at 49%. What changed was the behavior of right-wing parties:
they filed more motions, with milder content, framed in centrist-friendly
language. Centrist voting support surged from 0.251 to 0.507 (Cohen's d = +0.65),
but centrists did not become more right-wing — they stayed ideologically left
while voting more permissively on proposals that had become less materially
consequential.
This article presents the evidence across three indicators — centrist voting
support, SVD spatial divergence, and 2D extremity decomposition — and examines
the mechanisms through which right-wing motions gained centrist support.
## About Stemwijzer
Stemwijzer is a data-driven political compass built from real parliamentary voting
records. It analyzes 29,591 motions from the Tweede Kamer (2016–2026), each with
per-MP vote records, to compute latent political dimensions using Singular Value
Decomposition (SVD). Users vote on real motions and find which MPs match their
positions — not based on party manifestos or campaign promises, but on how
representatives actually voted.
The platform tracks party positions across 11 annual windows using
Procrustes-aligned SVD, allowing year-over-year comparison of spatial drift.
Every motion has been scored on two independent dimensions of extremity:
**stijl-extremiteit** (stylistic rhetoric, 1–5) and **materiële impact**
(material policy consequence, 1–5), manually validated with 75% auditor agreement.
The Overton analysis presented here builds on this infrastructure. The same
SVD compass, extremity scores, and vote-level data that power the Stemwijzer
Explorer dashboard drive these findings.
## Methodology
**Right-wing motion classification.** We identify right-wing motions using a
hybrid keyword + voting-pattern classifier. A seed set of right-wing keywords
(vuurwerkverbod, stikstof, nareis, etc.) is expanded through an iterative
keyword-vote loop — motions whose voting pattern correlates with right-wing
party support are flagged, their distinctive terms extracted, and the keyword
set refined. The final classifier identifies 3,030 motions as right-wing across
2016–2026, with full voting records for centrist support computation.
**2D extremity scoring.** Every motion in the database (29,591) has been scored
by an LLM on two dimensions: *stijl-extremiteit* (stylistic extremity:
inflammatory language, rhetorical framing) and *materiële impact* (material
impact: rights restriction, institutional change, resource reallocation), each
on a 1–5 scale. Manual audit of 117 stratified motions achieved 75% agreement.
The two dimensions are only moderately correlated (Pearson r = 0.43 for all
motions, r = 0.47 for right-wing), confirming they capture distinct
phenomena.
**Strict centrist definition.** We define the centrist bloc narrowly as four
parties — D66, CDA, ChristenUnie, NSC — excluding VVD and BBB, which lean
center-right and would inflate centrist support mechanically. A strict
opposition-only filter further controls for coalition effects by excluding
motions whose lead submitter belongs to the governing coalition.
**SVD alignment.** Party positions are computed via SVD on annual voting
matrices and aligned using chained Procrustes orthogonal rotation followed by
global PCA, placing all annual party positions in a common 2D reference frame.
Centrist and right-wing centers of gravity are computed as the mean of
party-level axis scores within each bloc.
```{python}
#| label: chart-1-yearly-cs
#| fig-cap: "Centrist Support for Right-Wing Motions Over Time (2016–2026)"
#| column: page
yearly = con.execute("""
SELECT
year,
AVG(centrist_support_strict) AS mean_cs,
STDDEV(centrist_support_strict) AS std_cs,
COUNT(*) AS n
FROM right_wing_motions
WHERE classified = TRUE
GROUP BY year ORDER BY year
""").fetchdf()
fig1 = go.Figure()
fig1.add_trace(go.Scatter(
x=yearly["year"], y=yearly["mean_cs"],
mode="lines+markers", name="All right-wing",
line=dict(color="#002366", width=3),
marker=dict(size=8),
error_y=dict(
type="data",
array=1.96 * yearly["std_cs"] / np.sqrt(yearly["n"]),
visible=True, thickness=0.8, width=2
)
))
pre = yearly[yearly["year"] < BREAK_YEAR]
post = yearly[yearly["year"] >= BREAK_YEAR]
fig1.add_hline(
y=pre["mean_cs"].mean(),
line_dash="dot", line_color="#90CAF9",
annotation_text=f"Pre-2024 mean ({pre['mean_cs'].mean():.3f})"
)
fig1.add_hline(
y=post["mean_cs"].mean(),
line_dash="dot", line_color="#1E88E5",
annotation_text=f"Post-2024 mean ({post['mean_cs'].mean():.3f})"
)
fig1.add_vline(
x=BREAK_YEAR - 0.5, line_dash="dot", line_color="black", opacity=0.5
)
fig1.update_layout(
title="Centrist Support (Strict) for Right-Wing Motions",
xaxis=dict(title="Year", dtick=1),
yaxis=dict(title="Centrist Support (fraction of parties)", range=[0, 1.1]),
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
template="plotly_white", height=450,
)
fig1.show()
```
## Indicator 1: Centrist Voting Support
The cleanest signal is in how centrist parties voted on right-wing motions.
Average support rose from 0.251 pre-2024 to 0.507 post-2024 — a Cohen's d of
+0.65, a medium-to-large effect. The breakpoint is unmistakably 2024.
This is not a coalition artifact. After the Schoof cabinet formed in July 2024,
PVV entered government, which could mechanically inflate support for its own
motions. When we restrict analysis to opposition-only right-wing motions (lead
submitter outside the governing coalition), the effect is larger: d = +0.85,
with support jumping from 0.270 to 0.543. Centrist parties are genuinely more
willing to support right-wing motions than they were before 2024, even when
those motions come from opposition right-wing parties.
The gradient across extremity levels persists: centrists still differentiate by
how radical a motion is, but at a consistently higher baseline. High-extremity
motions gained proportionally more support than mild motions, consistent with
genuine tolerance expansion rather than compositional shift.
**Pass rate is useless as an indicator.** Dutch parliament passes 96%+ of motions
in both periods. With near-zero variance, pass rate cannot register a shift of
any magnitude. Centrist support among MPs is the meaningful behavioral measure.
```{python}
#| label: chart-2-gravity
#| fig-cap: "Gravity-Controlled Centrist Support by Material Impact Level, Pre vs Post 2024"
#| column: page
gravity = con.execute("""
SELECT
CASE WHEN r.year < 2024 THEN 'pre-2024' ELSE 'post-2024' END AS period,
e.materiele_impact AS m_level,
AVG(r.centrist_support_strict) AS cs,
COUNT(*) AS n
FROM right_wing_motions r
JOIN extremity_scores_all e ON r.motion_id = e.motion_id
WHERE r.classified = TRUE AND e.materiele_impact IS NOT NULL
GROUP BY period, m_level ORDER BY period, m_level
""").fetchdf()
levels = sorted(gravity["m_level"].unique())
pre_vals = gravity[gravity["period"] == "pre-2024"].set_index("m_level")
post_vals = gravity[gravity["period"] == "post-2024"].set_index("m_level")
fig2 = go.Figure()
fig2.add_trace(go.Bar(
name="Pre-2024",
x=[f"M={l}" for l in levels],
y=[pre_vals.loc[l, "cs"] if l in pre_vals.index else 0 for l in levels],
marker_color="#90CAF9",
text=[f"N={int(pre_vals.loc[l, 'n'])}" if l in pre_vals.index else "" for l in levels],
textposition="outside",
offset=0,
))
fig2.add_trace(go.Bar(
name="Post-2024",
x=[f"M={l}" for l in levels],
y=[post_vals.loc[l, "cs"] if l in post_vals.index else 0 for l in levels],
marker_color="#1E88E5",
text=[f"N={int(post_vals.loc[l, 'n'])}" if l in post_vals.index else "" for l in levels],
textposition="outside",
offset=0.3,
))
fig2.update_layout(
title="Gravity-Controlled Centrist Support by Material Impact",
xaxis=dict(title="Material Impact Level"),
yaxis=dict(title="Centrist Support", range=[0, 1.1]),
barmode="group",
template="plotly_white", height=450,
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
)
fig2.show()
```
The gravity-controlled chart reveals a critical pattern: the centrist support
shift is real at **every** material impact level. From M=1 (mild procedural
adjustments, +0.292) to M=5 (systemic overhaul, +0.122), centrist support rose
across the board. The largest absolute gains came from the middle range (M=2:
+0.205, M=3: +0.219, M=4: +0.267), where most right-wing motions cluster.
Comparing right-wing motions against all other motions confirms the shift is
specific: right-wing centrist support surged by +0.236, while non-right-wing
motions remained essentially flat (−0.006). This is a right-wing-specific
phenomenon, not a general parliamentary trend.
## Indicator 2: Spatial Divergence
If centrists are voting more with right-wing motions, one might expect
ideological convergence — centrist parties drifting rightward on the SVD
compass. Procrustes-aligned SVD analysis shows the opposite.
```{python}
#| label: chart-3-svd
#| fig-cap: "SVD Trajectories: Centrist vs Right-Wing Centers of Gravity (2016–2026)"
#| column: page
svd = con.execute("""
SELECT * FROM overton_svd_center ORDER BY window_id
""").fetchdf()
fig3 = go.Figure()
fig3.add_trace(go.Scatter(
x=svd["centrist_mean_axis1"], y=svd["centrist_mean_axis2"],
mode="lines+markers+text", name="Centrist center",
line=dict(color="#00A36C", width=2),
marker=dict(size=8, symbol="circle"),
text=svd["window_id"], textposition="top center",
))
fig3.add_trace(go.Scatter(
x=svd["right_mean_axis1"], y=svd["right_mean_axis2"],
mode="lines+markers+text", name="Right-wing center",
line=dict(color="#002366", width=2),
marker=dict(size=8, symbol="square"),
text=svd["window_id"], textposition="bottom center",
))
fig3.update_layout(
title="SVD Party Centers of Gravity Over Time",
xaxis=dict(title="Axis 1 (Economic)"),
yaxis=dict(title="Axis 2 (Cultural)"),
template="plotly_white", height=500,
legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99),
hovermode="closest",
)
fig3.show()
```
Between the first and last annual windows:
- **Centrists moved left on both axes:** −0.223 on the economic axis (more
welfare-oriented) and +0.081 on the cultural axis (more kosmopolitisch).
- **Right-wing parties moved further right culturally:** −0.065 on the cultural
axis (more nationalist).
- **The cultural distance between centrists and right-wing parties widened**
from 0.282 to 0.428 (+0.146).
This is spatial divergence, not convergence. Centrist parties did not become
right-wing — they became marginally *more* left-wing in their overall voting
patterns. The centrist center of gravity moved toward welfare and cosmopolitanism,
while right-wing parties moved further into the nationalist corner.
**Why this makes sense with the voting data:** The SVD captures the *full*
voting landscape — including all motions, not just the ones centrists supported.
Right-wing parties continued filing high-impact motions that centrists opposed,
while simultaneously filing a much larger volume of milder motions centrists
supported. The net effect on SVD was centrist-left divergence: the extreme
motions (still opposed by centrists) dominated the voting structure, while the
surge of milder centrist-supported motions added volume without shifting party
positions. This is "acceptance without conversion" — centrists vote more with
right-wing motions while moving further from them ideologically.
## Indicator 3: Content Moderation
The original single-dimensional extremity score showed no increase post-2024
(d = −0.09, from 2.21 to 2.15). If the Overton window shifted, why didn't
right-wing motions become more radical?
The answer lies in what the single score measured. Two-dimensional rescoring
of all 29,591 motions reveals that stylistic extremity and material impact are
only moderately correlated (r = 0.43). When tracked separately over time, they
tell different stories.
```{python}
#| label: chart-4-2d-extremity
#| fig-cap: "2D Extremity Over Time: Stijl vs Materieel (Right-Wing Motions, 2019–2026)"
#| column: page
extremity_2d = con.execute("""
SELECT
r.year,
AVG(e.stijl_extremiteit) AS mean_stijl,
AVG(e.materiele_impact) AS mean_mat,
COUNT(*) AS n
FROM right_wing_motions r
JOIN extremity_scores_all e ON r.motion_id = e.motion_id
WHERE r.classified = TRUE AND r.year >= 2019
GROUP BY r.year ORDER BY r.year
""").fetchdf()
all_stijl, all_mat = con.execute("""
SELECT AVG(stijl_extremiteit), AVG(materiele_impact)
FROM extremity_scores_all
""").fetchone()
fig4 = make_subplots(
rows=1, cols=2,
subplot_titles=("Stylistic Extremity (Stijl)", "Material Impact (Materieel)"),
shared_yaxes=False,
)
fig4.add_trace(
go.Scatter(
x=extremity_2d["year"], y=extremity_2d["mean_stijl"],
mode="lines+markers", name="Right-wing",
line=dict(color="#6A1B9A", width=3),
marker=dict(size=8),
),
row=1, col=1,
)
fig4.add_hline(
y=all_stijl, line_dash="dot", line_color="#9E9E9E",
annotation_text=f"All motions ({all_stijl:.2f})",
row=1, col=1,
)
fig4.add_trace(
go.Scatter(
x=extremity_2d["year"], y=extremity_2d["mean_mat"],
mode="lines+markers", name="Right-wing",
line=dict(color="#E53935", width=3),
marker=dict(size=8),
),
row=1, col=2,
)
fig4.add_hline(
y=all_mat, line_dash="dot", line_color="#9E9E9E",
annotation_text=f"All motions ({all_mat:.2f})",
row=1, col=2,
)
fig4.update_layout(
title="2D Extremity Decomposition: Stijl vs Materieel",
template="plotly_white", height=400,
showlegend=False,
)
fig4.update_xaxes(title="Year", dtick=1)
fig4.update_yaxes(title="Score (1–5)", range=[0.5, 4])
fig4.show()
```
| Dimension | Pre-2024 Mean | Post-2024 Mean | Δ |
|-----------|--------------|---------------|-----|
| Stylistic extremity | 1.718 | 1.815 | +0.097 |
| Material impact | 2.530 | 2.384 | −0.146 |
| Gap (M−S) | 0.813 | 0.570 | −0.243 |
Material impact *decreased* (−0.146) while stylistic extremity *increased*
(+0.097). A Wilcoxon signed-rank test comparing yearly mean stylistic vs yearly
mean material scores confirms the dimensions systematically differ (W = 0.0,
n = 10 yearly pairs, p = 0.002). The gap between the two dimensions narrowed
from 0.813 to 0.570 — right-wing motions became both less rhetorically hostile
AND less substantively impactful.
Compared to all motions, right-wing motions score higher on both dimensions:
stijl +0.47, materieel +0.54. The masking rate — restrained language paired
with high material impact (S ≤ 2, M ≥ 3) — is 36.1% for right-wing motions
vs 24.0% for all motions. Right-wing proposals disproportionately use
procedural language to advance consequential policy.
## Mechanisms of Influence
If centrists didn't become right-wing, *how* did right-wing motions gain their
support? A systematic classification of 150 post-2024 motions (stratified by
centrist support level) identifies the dominant pathways.
```{python}
#| label: chart-5-mechanisms
#| fig-cap: "Mechanism Distribution: High-Support vs Low-Support Post-2024 Motions"
#| column: page
mechanisms = [
"Procedureel/technisch",
"Consensus framing",
"Gerichte restrictie",
"Institutioneel/rechtsstatelijk",
"Symbolisch/declaratoir",
"Welzijn/dienstverlening",
"Lokaal/regionaal",
"Coalitie-afstemming",
"Crisisrespons",
"Systeemontmanteling",
]
high_support = [24, 18, 13, 7, 4, 3, 3, 2, 1, 0]
low_support = [9, 6, 21, 19, 5, 1, 1, 0, 0, 13]
fig5 = go.Figure()
fig5.add_trace(go.Bar(
name="High-support (CS > 0.5)",
x=mechanisms, y=high_support,
marker_color="#1E88E5",
))
fig5.add_trace(go.Bar(
name="Low-support (CS ≤ 0.5)",
x=mechanisms, y=low_support,
marker_color="#90CAF9",
))
fig5.update_layout(
title="Mechanism Classification: High-Support vs Low-Support Post-2024",
xaxis=dict(title="Mechanism", tickangle=45),
yaxis=dict(title="Count"),
barmode="group",
template="plotly_white", height=450,
legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99),
)
fig5.show()
```
The contrast between high- and low-support post-2024 motions is sharp.
**High-support motions (CS > 0.5)** are dominated by procedural/technical
framing (32%), consensus framing appealing to shared values (24%), and targeted
restriction rather than blanket bans (17%). Institutional challenges and system
dismantling are notably absent.
**Low-support motions (CS ≤ 0.5)** are dominated by targeted restriction (28%),
institutional challenges (25%), and system dismantling (17%). Zero system
dismantling motions achieved high centrist support.
Consensus framing is significantly more common in high-support motions (24%)
than low-support (8%): χ²(1) = 6.00, p = 0.014. The hypothesis that consensus
framing drives centrist support is confirmed.
**Party-level analysis** reveals the shift is not uniform. JA21 is the primary
driver, with a +0.203 CS shift and the only volume + support gains combination.
PVV entered government and filed fewer, milder motions. FVD remains structurally
shunned — its motions consistently fail to gain centrist support regardless of
content.
## Temporal Dynamics
Quarterly analysis across 33 quarters (2016-Q2 through 2026-Q1) replaces the
binary pre/post-2024 comparison with a continuous trajectory that reveals the
exact timing, shape, and sustainability of the shift.
```{python}
#| label: chart-6-quarterly
#| fig-cap: "Quarterly Centrist Support Trajectory (2016–2026)"
#| column: page
quarterly = con.execute("""
SELECT
EXTRACT(YEAR FROM m.date) AS y,
CEIL(EXTRACT(MONTH FROM m.date) / 3.0) AS q,
AVG(r.centrist_support_strict) AS cs,
COUNT(*) AS n,
STDDEV(r.centrist_support_strict) AS std_cs
FROM right_wing_motions r
JOIN motions m ON r.motion_id = m.id
WHERE r.classified = TRUE AND m.date IS NOT NULL
GROUP BY y, q ORDER BY y, q
""").fetchdf()
quarterly["label"] = quarterly["y"].astype(int).astype(str) + "-Q" + quarterly["q"].astype(int).astype(str)
inflection_idx = quarterly[(quarterly["y"].astype(int) == 2024) & (quarterly["q"].astype(int) == 1)].index
peak_idx = quarterly[(quarterly["y"].astype(int) == 2024) & (quarterly["q"].astype(int) == 4)].index
latest_idx = quarterly[(quarterly["y"].astype(int) == 2026) & (quarterly["q"].astype(int) == 1)].index
fig6 = go.Figure()
fig6.add_trace(go.Scatter(
x=quarterly["label"], y=quarterly["cs"],
mode="lines+markers",
line=dict(color="#002366", width=3),
marker=dict(size=6),
error_y=dict(
type="data",
array=1.96 * quarterly["std_cs"] / np.sqrt(quarterly["n"]),
visible=True, thickness=0.6, width=1.5,
),
name="Centrist Support",
))
for idx in [inflection_idx, peak_idx, latest_idx]:
if len(idx) > 0:
i = idx[0]
fig6.add_annotation(
x=quarterly.loc[i, "label"], y=quarterly.loc[i, "cs"],
text=f'{quarterly.loc[i, "cs"]:.3f}',
showarrow=True, arrowhead=1, ax=0, ay=-30,
)
fig6.add_shape(
type="line", x0="2024-Q1", x1="2024-Q1", y0=0, y1=1,
line=dict(dash="dot", color="red", width=1.5),
)
fig6.add_annotation(
x="2024-Q1", y=0.95,
text="PVV election (Nov 2023)",
showarrow=False, textangle=-90,
font=dict(color="red", size=10),
)
fig6.add_shape(
type="line", x0="2024-Q3", x1="2024-Q3", y0=0, y1=1,
line=dict(dash="dot", color="orange", width=1.5),
)
fig6.add_annotation(
x="2024-Q3", y=0.88,
text="Schoof cabinet (Jul 2024)",
showarrow=False, textangle=-90,
font=dict(color="orange", size=10),
)
fig6.update_layout(
title="Quarterly Centrist Support Trajectory",
xaxis=dict(
title="Quarter",
tickangle=45,
tickmode="array",
tickvals=quarterly["label"][::4],
),
yaxis=dict(title="Centrist Support", range=[0, 1.0]),
template="plotly_white", height=450,
)
fig6.show()
```
**Timing.** The inflection point is 2024-Q1, the quarter immediately following
the PVV's November 2023 election victory. Centrist support jumped from 0.321
(2023-Q4) to 0.501 (2024-Q1) — a single-quarter increase of +0.180, roughly
twice the average quarterly change.
**Shape.** Centrist support rose sharply through 2024-Q4, reaching an all-time
peak of 0.648 in the first full quarter of the Schoof cabinet. From that peak,
it declined steadily: 0.598, 0.503, 0.437, 0.450, and 0.334 in 2026-Q1 —
below the 0.4 inflection threshold and approaching pre-shift levels.
**Causal mechanism.** The shift began before the Schoof cabinet formed (July
2024), appearing immediately after the PVV election. This rules out coalition
dynamics as the primary driver. The most parsimonious explanation: centrist
parties perceived the PVV's electoral success as a mandate for right-wing policy
and adjusted their voting behavior accordingly.
**Sustainability.** The 2026-Q1 reversion to 0.334 raises a critical question:
is the centrist support surge a temporary electoral-cycle effect rather than a
permanent Overton window shift? The trajectory resembles an electoral response
function — a rapid jump after the election, a peak during the cabinet honeymoon,
and a gradual decline. The "new normal" may be closer to 0.33 than to 0.65.
| Hypothesis | Evidence | Verdict |
|------------|----------|---------|
| Electoral shock | Jump immediately followed PVV victory (Nov 2023) | **Supported** |
| Coalition dynamics | Shift began 3 quarters before cabinet formed | **Refuted** |
| Gradual learning | Jump was 1.9× average quarterly — discrete, not incremental | **Refuted** |
| European contagion | No Dutch response during 2022–2023 European shift | **Refuted** |
## Verdict: The Window Widened Through Moderation
**The Overton window widened: more right-wing positions became politically
acceptable after 2024. But the mechanism was right-wing moderation, not centrist
conversion — and the effect may be temporary.**
Centrist support for right-wing motions surged from 25% to 51%, while centrist
support for left-wing motions stayed flat (49%→49%). The window of acceptable
debate expanded rightward.
1. **Volume surged, impact declined.** Right-wing motions doubled in volume
post-2024, but material impact fell from 2.78 to 2.43 (Cohen's d = −0.36).
The M ≥ 4 share dropped from 23.7% to 11.3% and continued falling to 2.7%
by 2026.
2. **Centrists did not become more tolerant.** The extremity-stratified
gradient persists — centrists still differentiate between mild and extreme
motions. The across-the-board baseline shift reflects that content within
each bucket became milder, not that centrists lowered their standards.
3. **The mechanism is strategic moderation, systematically confirmed.** Zero
system-dismantling proposals achieved high centrist support post-2024. The
dominant pathways — procedural/technical (32%), consensus framing (24%),
and targeted restriction (17%) — show right-wing parties learned which
frames work.
4. **SVD divergence confirms this.** Centrists moved left spatially as the
extreme tail polarized even as cooperation grew on the moderate mass.
5. **The shift is electorally driven and possibly temporary.** Centrist support
surged immediately after the PVV election, peaked at 0.648 in 2024-Q4, and
has since reverted to 0.334 in 2026-Q1 — approaching pre-shift levels.
**The gateway domain: migration.** Migration is where the Overton shift is most
genuine — and where right-wing parties learned the frames they then applied
elsewhere. Material impact barely declined (−0.13), yet centrist support more
than doubled (0.153 → 0.369). Centrists went from zero support for M = 5
migration motions to nearly 20%. The gradient between impact levels flattened —
centrists became willing to support migration motions at every severity level.
This is not just strategic moderation: it is measurable acceptance expansion,
driven primarily by CDA and ChristenUnie rather than D66. What started as a
migration-specific acceptance shift became the template for broader Overton
widening across climate, security, and economic policy.
### Limitations
- **Small-N time series:** 8 pre-2024 annual windows and 3 post-2024
(2026 is partial). Effect sizes are descriptive Cohen's d, not inferred from
a time-series model.
- **Coalition coding:** 2024 is ambiguous (Rutte IV until July, Schoof
thereafter). Opposition-only analysis and temporal timing mitigate this.
- **Mechanism classification:** Based on 150 post-2024 motions, single-classifier
assignment. Inter-rater agreement is moderate (κ = 0.41).
- **Causal direction:** The timing strongly supports an electoral explanation,
but this remains correlational.
- **Success ceiling:** 96%+ pass rate makes pass rate an insensitive dependent
variable.
### Explore the Data
This article is one surface of a three-tier analysis:
1. **Narrative spine** — you're reading it. The story, with key evidence.
2. **Technical appendices** — detailed markdown reports in `reports/overton_window/`
cover every methodological decision, robustness check, and sensitivity
analysis.
3. **Live exploration** — explore the Stemwijzer Explorer:
- **Kompas tab** — party positions on the SVD axes
- **Trajectories tab** — how parties drifted over time
- **Overton tab** — centrist support trends and right-wing motion browser
**Visit the Explorer** at `localhost:8501` to interact with the compass, plot
your position, and verify these findings against the underlying vote data.
```{python}
#| label: close-connection
#| include: false
con.close()
```