---
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. Excluding ~6,000 placeholder motions scored (1,1) by default, r drops to 0.34 — the dimensions are even more independent than the headline figure suggests.

**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. Exploratory evidence suggests
consensus framing drives centrist support. Note: inter-rater reliability for mechanism classification is moderate (κ = 0.41). These patterns are exploratory and require taxonomy refinement.

**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 is less consistent with
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. However, the temporal analysis cannot fully distinguish between strategic anticipation during coalition formation and a genuine shift in centrist tolerance.

**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? Material moderation persisted (materieel ~2.4)
through the decline, but stylistic extremity reverted from 1.70 to 2.02. CS was
already declining through 2025 (0.648→0.450) despite continued moderation,
suggesting the 2024 spike was primarily an electoral shock for non-migration domains. However, 2026-Q2 shows CS bouncing back to 0.523, driven by the intensifying migration debate. Migration centrist support (0.395) now exceeds non-migration (0.368) for the first time — the shift is domain-specific: temporary for non-migration, durable for migration.

| Hypothesis | Evidence | Verdict |
|------------|----------|---------|
| Electoral shock | Jump immediately followed PVV victory (Nov 2023) | **Supported** |
| Coalition dynamics | Shift began 3 quarters before cabinet formed | **Less consistent with the data** |
| Gradual learning | Jump was 1.9× average quarterly — discrete, not incremental | **Less consistent with the data** |
| European contagion | No Dutch response during 2022–2023 European shift | **Less consistent with the data** |

## 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, with exploratory evidence suggesting this pattern.** Zero
   system-dismantling proposals achieved high centrist support post-2024. The
   dominant pathways — procedural/technical (32%), consensus framing (24%),
   and targeted restriction (17%) — suggest right-wing parties learned which
   frames work, though mechanism classification has moderate reliability (κ = 0.41).

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 domain-specific.** Centrist support
   surged immediately after the PVV election, peaked at 0.648 in 2024-Q4, and
   declined through 2025 to 0.450 despite continued material moderation — then
   hit 0.334 in 2026-Q1. But 2026-Q2 bounced back to 0.523, driven by the
   intensifying migration debate. Non-migration acceptance was a temporary
   electoral shock; migration acceptance is durable and growing.

**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. **As of 2026, migration
centrist support (0.395) exceeds non-migration (0.368) for the first time** —
confirming that migration acceptance is durable while non-migration acceptance
was the temporary component. Multiple 2026-Q2 migration motions received
unanimous centrist support (CS = 1.00), including high-impact items.

### 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()
```
