Paywall — Hard paywall (full block)
A self-contained hard paywall for the fictional Northwind Journal: an editorial article opens with a kicker, headline and byline, then after the first paragraph the entire remainder is replaced by a centered gate card on a soft brand backdrop. The card pairs a lock icon with a Subscribe to keep reading heading, two selectable plan cards (a Most popular Pro tier and a lighter Starter tier) with feature lists and live pricing, a Continue with plan button, and a sign-in link toggling an inline email form. No copy leaks below.
MCP
Code
:root {
--brand: #5b5bf0;
--brand-d: #4646d6;
--brand-700: #3a3ab8;
--brand-50: #eef0ff;
--accent: #00b4a6;
--accent-soft: #d8f5f2;
--ink: #101322;
--ink-2: #3a4060;
--muted: #6c7393;
--bg: #f6f7fb;
--white: #ffffff;
--surface: #ffffff;
--line: rgba(16, 19, 34, 0.1);
--line-2: rgba(16, 19, 34, 0.16);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-sm: 0 1px 2px rgba(16, 19, 34, 0.08);
--sh-lg: 0 8px 24px rgba(16, 19, 34, 0.08);
--sh-xl: 0 24px 60px rgba(16, 19, 34, 0.18);
}
* {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
:focus-visible {
outline: 3px solid color-mix(in srgb, var(--brand) 55%, transparent);
outline-offset: 2px;
border-radius: var(--r-sm);
}
/* ---------- Top bar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 20;
background: color-mix(in srgb, var(--white) 86%, transparent);
backdrop-filter: saturate(1.4) blur(10px);
border-bottom: 1px solid var(--line);
}
.topbar-inner {
max-width: 760px;
margin: 0 auto;
padding: 12px 20px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 9px;
text-decoration: none;
color: var(--ink);
}
.brand-mark {
color: var(--brand);
}
.brand-name {
font-weight: 800;
font-size: 17px;
letter-spacing: -0.01em;
}
.brand-name-2 {
color: var(--muted);
font-weight: 600;
}
.topnav {
display: flex;
align-items: center;
gap: 18px;
}
.topnav a {
text-decoration: none;
color: var(--ink-2);
font-size: 14px;
font-weight: 500;
}
.topnav a:hover {
color: var(--ink);
}
.topnav-cta {
padding: 7px 14px;
border-radius: 999px;
background: var(--brand);
color: var(--white) !important;
font-weight: 600 !important;
box-shadow: var(--sh-sm);
}
.topnav-cta:hover {
background: var(--brand-d);
}
/* ---------- Article ---------- */
.page {
max-width: 760px;
margin: 0 auto;
padding: 40px 20px 80px;
}
.kicker {
margin: 0 0 12px;
font-size: 12.5px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--brand);
}
.headline {
margin: 0 0 14px;
font-size: clamp(30px, 6vw, 44px);
line-height: 1.08;
font-weight: 800;
letter-spacing: -0.025em;
color: var(--ink);
}
.deck {
margin: 0 0 26px;
font-size: clamp(16px, 2.6vw, 19px);
color: var(--ink-2);
max-width: 58ch;
}
.byline {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 0;
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
margin-bottom: 28px;
}
.avatar {
flex: 0 0 auto;
width: 42px;
height: 42px;
border-radius: 50%;
display: grid;
place-items: center;
background: var(--brand-50);
color: var(--brand-700);
font-weight: 700;
font-size: 14px;
}
.byline-meta {
display: flex;
flex-direction: column;
line-height: 1.35;
}
.byline-name {
font-weight: 600;
font-size: 15px;
}
.byline-sub {
font-size: 13px;
color: var(--muted);
}
.lead p {
margin: 0 0 20px;
font-size: 18px;
line-height: 1.7;
color: var(--ink);
}
.lead p::first-letter {
initial-letter: 2;
}
/* ---------- Hard paywall gate ---------- */
.gate-wrap {
position: relative;
margin-top: 8px;
padding-top: 28px;
}
.gate-backdrop {
position: absolute;
inset: -10px -40px -40px;
border-radius: var(--r-lg);
background:
radial-gradient(120% 90% at 50% 0%, color-mix(in srgb, var(--brand-50) 90%, transparent), transparent 70%),
linear-gradient(180deg, color-mix(in srgb, var(--bg) 0%, transparent), var(--bg) 30%);
pointer-events: none;
}
.gate-card {
position: relative;
max-width: 480px;
margin: 0 auto;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-xl);
padding: 30px 28px 26px;
text-align: center;
}
.gate-lock {
width: 60px;
height: 60px;
margin: 0 auto 16px;
border-radius: 16px;
display: grid;
place-items: center;
color: var(--brand);
background: var(--brand-50);
border: 1px solid color-mix(in srgb, var(--brand) 18%, transparent);
}
.gate-eyebrow {
margin: 0 0 6px;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--accent);
}
.gate-title {
margin: 0 0 8px;
font-size: clamp(22px, 4vw, 27px);
font-weight: 800;
letter-spacing: -0.02em;
color: var(--ink);
}
.gate-sub {
margin: 0 auto 22px;
max-width: 40ch;
font-size: 14.5px;
color: var(--ink-2);
}
.gate-sub strong {
color: var(--ink);
font-weight: 700;
}
/* ---------- Plans ---------- */
.plans {
border: 0;
margin: 0 0 18px;
padding: 0;
display: grid;
gap: 12px;
}
.plan {
position: relative;
display: block;
text-align: left;
padding: 16px 18px;
border: 1.5px solid var(--line-2);
border-radius: var(--r-md);
background: var(--white);
cursor: pointer;
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease, background 0.16s ease;
}
.plan:hover {
border-color: color-mix(in srgb, var(--brand) 45%, var(--line-2));
transform: translateY(-1px);
box-shadow: var(--sh-sm);
}
.plan-input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.plan:has(.plan-input:checked) {
border-color: var(--brand);
background: color-mix(in srgb, var(--brand-50) 60%, var(--white));
box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 16%, transparent);
}
.plan:has(.plan-input:focus-visible) {
outline: 3px solid color-mix(in srgb, var(--brand) 55%, transparent);
outline-offset: 2px;
}
.plan-badge {
position: absolute;
top: -10px;
right: 14px;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--white);
background: var(--brand);
padding: 3px 9px;
border-radius: 999px;
box-shadow: var(--sh-sm);
}
.plan-top {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
margin-bottom: 2px;
}
.plan-name {
font-size: 16px;
font-weight: 700;
color: var(--ink);
}
.plan-price {
display: inline-flex;
align-items: baseline;
gap: 1px;
white-space: nowrap;
}
.plan-amt {
font-size: 20px;
font-weight: 800;
letter-spacing: -0.02em;
color: var(--ink);
}
.plan-per {
font-size: 13px;
font-weight: 600;
color: var(--muted);
}
.plan-tagline {
display: block;
font-size: 13px;
color: var(--muted);
margin-bottom: 10px;
}
.plan-feats {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 5px;
}
.plan-feats li {
position: relative;
padding-left: 20px;
font-size: 13px;
color: var(--ink-2);
}
.plan-feats li::before {
content: "";
position: absolute;
left: 0;
top: 5px;
width: 13px;
height: 13px;
border-radius: 50%;
background: var(--accent-soft);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 13l4 4L19 7' fill='none' stroke='%2300b4a6' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-size: 11px 11px;
background-repeat: no-repeat;
background-position: center;
}
.plan-check {
position: absolute;
top: 16px;
right: 16px;
width: 22px;
height: 22px;
border-radius: 50%;
display: grid;
place-items: center;
color: var(--white);
background: var(--brand);
opacity: 0;
transform: scale(0.6);
transition: opacity 0.16s ease, transform 0.16s ease;
}
.plan:has(.plan-badge) .plan-check {
top: 16px;
}
.plan:has(.plan-input:checked) .plan-check {
opacity: 1;
transform: scale(1);
}
/* ---------- Continue button ---------- */
.gate-continue {
width: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 13px 18px;
border: 0;
border-radius: var(--r-md);
background: var(--brand);
color: var(--white);
font-family: inherit;
font-size: 15px;
font-weight: 700;
cursor: pointer;
box-shadow: var(--sh-lg);
transition: background 0.16s ease, transform 0.12s ease;
}
.gate-continue:hover {
background: var(--brand-d);
}
.gate-continue:active {
transform: translateY(1px);
}
.gate-continue svg {
transition: transform 0.16s ease;
}
.gate-continue:hover svg {
transform: translateX(3px);
}
.gate-fineprint {
margin: 12px 0 0;
font-size: 12px;
color: var(--muted);
}
.gate-signin-row {
margin: 16px 0 0;
padding-top: 16px;
border-top: 1px solid var(--line);
font-size: 14px;
color: var(--ink-2);
}
.gate-signin-link {
border: 0;
background: none;
padding: 0 2px;
font-family: inherit;
font-size: 14px;
font-weight: 700;
color: var(--brand);
cursor: pointer;
text-decoration: underline;
text-underline-offset: 2px;
}
.gate-signin-link:hover {
color: var(--brand-d);
}
/* ---------- Inline sign-in form ---------- */
.signin-form {
margin-top: 14px;
padding: 14px;
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--r-md);
text-align: left;
animation: gate-pop 0.18s ease;
}
@keyframes gate-pop {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.signin-label {
display: block;
font-size: 12.5px;
font-weight: 600;
color: var(--ink-2);
margin-bottom: 6px;
}
.signin-row {
display: flex;
gap: 8px;
}
.signin-input {
flex: 1 1 auto;
min-width: 0;
padding: 10px 12px;
border: 1.5px solid var(--line-2);
border-radius: var(--r-sm);
font-family: inherit;
font-size: 14px;
color: var(--ink);
background: var(--white);
}
.signin-input::placeholder {
color: var(--muted);
}
.signin-input:focus-visible {
outline: none;
border-color: var(--brand);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent);
}
.signin-submit {
flex: 0 0 auto;
padding: 10px 16px;
border: 0;
border-radius: var(--r-sm);
background: var(--ink);
color: var(--white);
font-family: inherit;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background 0.16s ease;
}
.signin-submit:hover {
background: var(--ink-2);
}
.signin-hint {
margin: 8px 0 0;
min-height: 16px;
font-size: 12.5px;
color: var(--muted);
}
.signin-hint.is-error {
color: var(--danger);
}
.signin-hint.is-ok {
color: var(--ok);
}
/* ---------- Toasts ---------- */
.toast-stack {
position: fixed;
left: 50%;
bottom: 24px;
transform: translateX(-50%);
z-index: 60;
display: flex;
flex-direction: column;
gap: 8px;
width: max-content;
max-width: calc(100vw - 32px);
}
.toast {
display: flex;
align-items: center;
gap: 9px;
padding: 11px 16px;
border-radius: 999px;
background: var(--ink);
color: var(--white);
font-size: 13.5px;
font-weight: 600;
box-shadow: var(--sh-xl);
opacity: 0;
transform: translateY(10px);
transition: opacity 0.22s ease, transform 0.22s ease;
}
.toast.is-in {
opacity: 1;
transform: translateY(0);
}
.toast .toast-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent);
flex: 0 0 auto;
}
/* ---------- Responsive ---------- */
@media (max-width: 520px) {
.topnav {
gap: 12px;
}
.topnav a:not(.topnav-cta) {
display: none;
}
.page {
padding: 28px 16px 64px;
}
.gate-card {
padding: 24px 18px 22px;
}
.gate-backdrop {
inset: -10px -16px -40px;
}
.signin-row {
flex-direction: column;
}
.signin-submit {
width: 100%;
}
.toast-stack {
bottom: 16px;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastStack = document.getElementById("toast-stack");
function toast(msg) {
if (!toastStack) return;
var el = document.createElement("div");
el.className = "toast";
el.setAttribute("role", "status");
var dot = document.createElement("span");
dot.className = "toast-dot";
el.appendChild(dot);
var text = document.createElement("span");
text.textContent = msg;
el.appendChild(text);
toastStack.appendChild(el);
// Force reflow so the entrance transition runs.
void el.offsetWidth;
el.classList.add("is-in");
window.setTimeout(function () {
el.classList.remove("is-in");
window.setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 260);
}, 2600);
}
/* ---------- Plan selection ---------- */
var planInputs = Array.prototype.slice.call(
document.querySelectorAll(".plan-input")
);
var continueBtn = document.querySelector("[data-continue]");
var continueLabel = document.querySelector("[data-continue-label]");
var yearlyNote = document.querySelector("[data-yearly-note]");
function selectedPlan() {
var checked = document.querySelector(".plan-input:checked");
if (!checked) return null;
return checked.closest(".plan");
}
function syncPlan() {
var plan = selectedPlan();
if (!plan) return;
var label = plan.getAttribute("data-label") || "plan";
var price = parseInt(plan.getAttribute("data-price"), 10) || 0;
if (continueLabel) continueLabel.textContent = "Continue with " + label;
if (yearlyNote) {
// ~2 months free when paid annually.
var annual = price * 10;
yearlyNote.textContent = "$" + annual + "/yr if paid annually";
}
}
planInputs.forEach(function (input) {
input.addEventListener("change", function () {
if (input.checked) {
syncPlan();
var plan = input.closest(".plan");
toast(
(plan && plan.getAttribute("data-label") ? plan.getAttribute("data-label") : "Plan") +
" plan selected"
);
}
});
});
// Allow clicking anywhere on a plan card to select it (label already does,
// but keep keyboard arrow behavior consistent + initialise).
syncPlan();
if (continueBtn) {
continueBtn.addEventListener("click", function () {
var plan = selectedPlan();
var label = plan ? plan.getAttribute("data-label") : "your plan";
toast("Starting checkout for the " + label + " plan…");
});
}
/* ---------- Inline sign-in form toggle ---------- */
var signinForm = document.getElementById("signin-form");
var signinTriggers = Array.prototype.slice.call(
document.querySelectorAll("[data-open-signin]")
);
var signinInput = document.getElementById("signin-email");
var signinHint = document.querySelector("[data-signin-hint]");
var signinLinkBtn = document.querySelector(".gate-signin-link");
function setSigninExpanded(open) {
if (signinLinkBtn) signinLinkBtn.setAttribute("aria-expanded", String(open));
}
function openSignin() {
if (!signinForm) return;
signinForm.hidden = false;
setSigninExpanded(true);
if (signinInput) {
window.requestAnimationFrame(function () {
try {
signinInput.focus();
} catch (e) {}
});
}
}
function closeSignin() {
if (!signinForm) return;
signinForm.hidden = true;
setSigninExpanded(false);
if (signinHint) {
signinHint.textContent = "";
signinHint.className = "signin-hint";
}
}
function toggleSignin() {
if (!signinForm) return;
if (signinForm.hidden) {
openSignin();
} else {
closeSignin();
}
}
signinTriggers.forEach(function (trigger) {
trigger.addEventListener("click", function (ev) {
// Top-bar / inline links may be anchors — stop them navigating.
if (trigger.tagName === "A") ev.preventDefault();
toggleSignin();
if (!signinForm.hidden) {
signinForm.scrollIntoView({ behavior: "smooth", block: "nearest" });
}
});
});
if (signinForm) {
signinForm.addEventListener("submit", function (ev) {
ev.preventDefault();
var value = signinInput ? signinInput.value.trim() : "";
var valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
if (!valid) {
if (signinHint) {
signinHint.textContent = "Enter a valid email address to continue.";
signinHint.className = "signin-hint is-error";
}
if (signinInput) signinInput.focus();
return;
}
if (signinHint) {
signinHint.textContent = "Sending a secure login link to " + value + "…";
signinHint.className = "signin-hint is-ok";
}
toast("Login link sent to " + value);
});
}
/* ---------- Esc closes the inline form ---------- */
document.addEventListener("keydown", function (ev) {
if (ev.key === "Escape" && signinForm && !signinForm.hidden) {
closeSignin();
if (signinLinkBtn) signinLinkBtn.focus();
}
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hard Paywall — Northwind Journal</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="topbar">
<div class="topbar-inner">
<a class="brand" href="#" aria-label="Northwind Journal home">
<svg class="brand-mark" width="26" height="26" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M4 19V5l8 6 8-6v14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="brand-name">Northwind <span class="brand-name-2">Journal</span></span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#">Markets</a>
<a href="#">Tech</a>
<a href="#">Opinion</a>
<a class="topnav-cta" href="#" data-open-signin>Sign in</a>
</nav>
</div>
</header>
<main class="page">
<article class="article" aria-labelledby="headline">
<p class="kicker">Deep Read · Cloud Infrastructure</p>
<h1 id="headline" class="headline">Inside the quiet race to make data centers disappear</h1>
<p class="deck">How a wave of fictional operators is rebuilding the backbone of the internet — and why the next decade of compute may be decided underground.</p>
<div class="byline">
<span class="avatar" aria-hidden="true">MR</span>
<div class="byline-meta">
<span class="byline-name">By Mara Ellison</span>
<span class="byline-sub">Senior Infrastructure Correspondent · 9 min read</span>
</div>
</div>
<div class="lead">
<p>The first thing you notice at a hyperscale facility is the silence. There are no humming crowds, no rows of glowing screens — only a low mechanical breath that never stops. For years this hidden layer of the economy grew in plain sight, and almost nobody outside the industry paid attention. That is starting to change, and the operators racing to build the next generation of compute know it.</p>
</div>
<!-- Gated region: replaced entirely by the hard paywall. No teaser content leaks. -->
<section class="gate-wrap" aria-label="Subscription required to continue reading">
<div class="gate-backdrop" aria-hidden="true"></div>
<div class="gate-card" role="region" aria-labelledby="gate-title">
<div class="gate-lock" aria-hidden="true">
<svg width="30" height="30" viewBox="0 0 24 24" focusable="false">
<rect x="4.5" y="10.5" width="15" height="10.5" rx="2.4" fill="none" stroke="currentColor" stroke-width="2"/>
<path d="M8 10.5V8a4 4 0 0 1 8 0v2.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<circle cx="12" cy="15.4" r="1.5" fill="currentColor"/>
<path d="M12 16.6v2.1" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<p class="gate-eyebrow">Subscriber exclusive</p>
<h2 id="gate-title" class="gate-title">Subscribe to keep reading</h2>
<p class="gate-sub">This story is part of <strong>Northwind Pro</strong>. Pick a plan to unlock the full report plus every premium briefing, archive and newsletter.</p>
<fieldset class="plans" role="radiogroup" aria-label="Choose a subscription plan">
<legend class="sr-only">Choose a subscription plan</legend>
<label class="plan" data-plan="pro" data-label="Pro" data-price="14">
<input type="radio" name="plan" value="pro" class="plan-input" checked />
<span class="plan-badge">Most popular</span>
<span class="plan-top">
<span class="plan-name">Pro</span>
<span class="plan-price"><span class="plan-amt">$14</span><span class="plan-per">/mo</span></span>
</span>
<span class="plan-tagline">Everything you need to read deeply.</span>
<ul class="plan-feats">
<li>Unlimited premium articles</li>
<li>Full 20-year archive</li>
<li>Daily Markets briefing</li>
<li>Ad-free reading</li>
</ul>
<span class="plan-check" aria-hidden="true">
<svg width="14" height="14" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
</label>
<label class="plan" data-plan="starter" data-label="Starter" data-price="6">
<input type="radio" name="plan" value="starter" class="plan-input" />
<span class="plan-top">
<span class="plan-name">Starter</span>
<span class="plan-price"><span class="plan-amt">$6</span><span class="plan-per">/mo</span></span>
</span>
<span class="plan-tagline">The essentials, lightly priced.</span>
<ul class="plan-feats">
<li>10 premium articles / month</li>
<li>Weekly digest newsletter</li>
<li>Save & bookmark stories</li>
</ul>
<span class="plan-check" aria-hidden="true">
<svg width="14" height="14" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
</span>
</label>
</fieldset>
<button type="button" class="gate-continue" data-continue>
<span data-continue-label>Continue with Pro</span>
<svg width="18" height="18" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<p class="gate-fineprint">Cancel anytime. Billed monthly · <span data-yearly-note>$140/yr if paid annually</span>.</p>
<p class="gate-signin-row">
Already a subscriber?
<button type="button" class="gate-signin-link" data-open-signin aria-expanded="false" aria-controls="signin-form">Sign in</button>
</p>
<form class="signin-form" id="signin-form" hidden novalidate>
<label class="signin-label" for="signin-email">Email address</label>
<div class="signin-row">
<input type="email" id="signin-email" class="signin-input" placeholder="[email protected]" autocomplete="email" required />
<button type="submit" class="signin-submit">Log in</button>
</div>
<p class="signin-hint" data-signin-hint aria-live="polite"></p>
</form>
</div>
</section>
</article>
</main>
<div class="toast-stack" id="toast-stack" aria-live="polite" aria-atomic="false"></div>
<script src="script.js"></script>
</body>
</html>Hard paywall (full block)
A complete hard-paywall reading view for the fictional Northwind Journal. The article opens normally — a brand kicker, an oversized headline, an italic-feeling deck, a byline with an avatar and read time, and a single lead paragraph. Where the rest of the story would continue, a centered gate card takes over on a soft radial-brand backdrop. Unlike a metered or fade paywall, nothing leaks below: the gated region is fully replaced, so there are no teaser sentences to scrape.
The gate card leads with a lock badge and a “Subscribe to keep reading” heading, then offers two
selectable plan cards built as accessible radio options. The highlighted Pro tier carries a “Most
popular” badge and a four-item feature list; the lighter Starter tier sits below it. Selecting a plan
ringed the card in brand color, reveals a check, and the JS rewrites the “Continue with {plan}” button
label, swaps the annual-price fine print, and raises a confirmation toast.
A “Sign in” link — in both the top bar and inside the card — toggles a tiny inline email form with
validation and an aria-live hint; submitting a valid address fires a “login link sent” toast, while
Escape closes the form and returns focus to the trigger. Everything is vanilla JS with no build step,
focus-visible rings, reduced-motion support, and a layout that holds together down to 360px.
Illustrative UI only — Northwind Journal, its plans, prices and bylines are fictional and not a real publication or subscription product.