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