News — Paywall / Subscribe Gate
A self-contained editorial paywall for the fictional Harbor Review, set in a warm newsprint palette with Playfair Display headlines and Inter meter copy. A waterfront investigation reads freely until its lower paragraphs fade into a gradient veil, where a subscribe gate card takes over — a free-article counter with pip indicator, a Monthly versus Annual billing toggle with a savings badge and live price, a benefits checklist, a sign-in link, and a red subscribe CTA. Vanilla JS tracks the free counter, swaps prices, and a subscribe toast unlocks the rest of the story.
MCP
Kod
:root {
--cream: #f4efe4;
--paper: #faf7f0;
--white: #ffffff;
--newsprint: #efe9da;
--ink: #16130f;
--ink-2: #2b2620;
--ink-3: #4a443b;
--muted: #7a7164;
--red: #b4291f;
--red-d: #8f1f17;
--red-50: #f3dcd9;
--rule: rgba(22, 19, 15, 0.16);
--rule-2: rgba(22, 19, 15, 0.3);
--rule-hair: rgba(22, 19, 15, 0.1);
--ok: #2f7d4f;
--warn: #b67a18;
--danger: #b4291f;
--r-sm: 4px;
--r-md: 8px;
--r-lg: 12px;
--serif: "Playfair Display", "Times New Roman", Georgia, serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
}
* {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
background: var(--cream);
color: var(--ink);
font-family: var(--sans);
font-size: 17px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.skip-link {
position: absolute;
left: -9999px;
top: 0;
background: var(--ink);
color: var(--paper);
padding: 0.6rem 1rem;
z-index: 50;
font-size: 0.85rem;
}
.skip-link:focus {
left: 0.5rem;
top: 0.5rem;
}
a {
color: var(--red);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
:focus-visible {
outline: 2px solid var(--red);
outline-offset: 2px;
}
/* ============ MASTHEAD ============ */
.masthead {
max-width: 1040px;
margin: 0 auto;
padding: 0 1.25rem;
}
.masthead__bar {
display: flex;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
padding: 0.6rem 0;
border-bottom: 1px solid var(--rule-hair);
font-size: 0.72rem;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--muted);
}
.masthead__name {
text-align: center;
padding: 1.4rem 0 1rem;
}
.masthead__title {
margin: 0;
font-family: var(--serif);
font-weight: 900;
font-size: clamp(2.6rem, 8vw, 4.6rem);
letter-spacing: -0.01em;
line-height: 0.95;
color: var(--ink);
}
.masthead__motto {
margin: 0.5rem 0 0;
font-style: italic;
font-family: var(--serif);
color: var(--ink-3);
font-size: 0.95rem;
}
.masthead__nav {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 1.4rem;
padding: 0.65rem 0;
border-top: 3px double var(--rule-2);
border-bottom: 1px solid var(--rule);
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.masthead__nav a {
color: var(--ink-2);
}
.masthead__nav a[aria-current="page"] {
color: var(--red);
}
.masthead__nav a:hover {
color: var(--red);
text-decoration: none;
}
/* ============ METER STRIP ============ */
.meter {
max-width: 1040px;
margin: 0 auto;
padding: 0.7rem 1.25rem;
display: flex;
align-items: center;
gap: 0.6rem;
font-size: 0.8rem;
letter-spacing: 0.02em;
color: var(--ink-3);
border-bottom: 1px solid var(--rule-hair);
background: linear-gradient(var(--newsprint), var(--cream));
}
.meter__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--red);
flex: none;
}
.meter__text strong {
color: var(--ink);
}
.meter__pips {
display: inline-flex;
gap: 4px;
margin-left: auto;
}
.meter__pips i {
width: 22px;
height: 4px;
border-radius: 2px;
background: var(--rule);
display: block;
}
.meter__pips i.used {
background: var(--red);
}
/* ============ STORY ============ */
.story {
max-width: 720px;
margin: 0 auto;
padding: 2.2rem 1.25rem 0;
}
.kicker,
.gate__kicker {
font-size: 0.74rem;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--red);
margin: 0 0 0.7rem;
}
.story__title {
font-family: var(--serif);
font-weight: 800;
font-size: clamp(2.1rem, 6vw, 3.4rem);
line-height: 1.04;
letter-spacing: -0.012em;
margin: 0 0 0.9rem;
}
.story__deck {
font-family: var(--serif);
font-style: italic;
font-size: clamp(1.15rem, 2.6vw, 1.4rem);
line-height: 1.4;
color: var(--ink-3);
margin: 0 0 1.4rem;
}
.byline {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
padding: 0.9rem 0;
border-top: 1px solid var(--rule);
border-bottom: 1px solid var(--rule);
}
.byline__by {
font-size: 0.92rem;
}
.byline__line {
display: block;
font-size: 0.78rem;
color: var(--muted);
margin-top: 0.15rem;
}
.byline__meta {
display: flex;
align-items: center;
gap: 0.7rem;
font-size: 0.74rem;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--muted);
}
.dateline {
font-weight: 700;
color: var(--ink-2);
}
.share {
font: inherit;
font-size: 0.72rem;
letter-spacing: 0.06em;
text-transform: uppercase;
font-weight: 600;
color: var(--ink-2);
background: transparent;
border: 1px solid var(--rule-2);
border-radius: var(--r-sm);
padding: 0.35rem 0.7rem;
cursor: pointer;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.share:hover {
background: var(--ink);
color: var(--paper);
border-color: var(--ink);
}
.share:active {
transform: translateY(1px);
}
/* ============ FIGURES ============ */
.hero {
margin: 1.6rem 0;
}
.hero__img {
aspect-ratio: 16 / 9;
border-radius: var(--r-sm);
border: 1px solid var(--rule);
}
.hero__img--dock {
background:
radial-gradient(120% 80% at 78% 18%, rgba(180, 41, 31, 0.55), transparent 55%),
radial-gradient(100% 120% at 12% 100%, rgba(22, 19, 15, 0.85), transparent 60%),
linear-gradient(160deg, #3a2a26 0%, #5a3530 28%, #2b211d 60%, #14100d 100%);
position: relative;
overflow: hidden;
}
.hero__img--dock::after {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(90deg, transparent 49.4%, rgba(0, 0, 0, 0.4) 49.7%, transparent 50%) 0 0 / 14% 100%,
linear-gradient(0deg, rgba(10, 7, 5, 0.9), transparent 42%);
opacity: 0.5;
}
.hero__img--blueprint {
background:
repeating-linear-gradient(0deg, transparent 0 16px, rgba(255, 255, 255, 0.06) 16px 17px),
repeating-linear-gradient(90deg, transparent 0 16px, rgba(255, 255, 255, 0.06) 16px 17px),
radial-gradient(120% 90% at 30% 20%, rgba(80, 110, 150, 0.5), transparent 60%),
linear-gradient(150deg, #1f3148, #16243a 60%, #0e1726);
}
figcaption {
margin-top: 0.55rem;
font-size: 0.8rem;
line-height: 1.4;
color: var(--muted);
}
figcaption .cap {
font-style: italic;
color: var(--ink-3);
}
figcaption .credit {
display: block;
margin-top: 0.15rem;
font-size: 0.68rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
/* ============ BODY ============ */
.body {
font-size: 1.06rem;
line-height: 1.62;
color: var(--ink-2);
}
.body p {
margin: 0 0 1.05rem;
text-align: justify;
hyphens: auto;
}
.body .lead::first-letter {
font-family: var(--serif);
font-weight: 800;
float: left;
font-size: 3.7rem;
line-height: 0.78;
padding: 0.18rem 0.55rem 0 0;
color: var(--red);
}
.body .lead {
text-align: left;
}
/* fade zone under the gate */
.fadezone {
position: relative;
}
.story[data-locked="true"] .fadezone {
max-height: 7.5em;
overflow: hidden;
-webkit-mask-image: linear-gradient(180deg, #000 0%, #000 25%, transparent 96%);
mask-image: linear-gradient(180deg, #000 0%, #000 25%, transparent 96%);
}
.story[data-locked="false"] .fadezone {
max-height: none;
-webkit-mask-image: none;
mask-image: none;
}
/* ============ GATE ============ */
.gate {
position: relative;
margin: 0 -1.25rem;
padding: 0 1.25rem 3rem;
}
.gate__veil {
position: absolute;
left: 0;
right: 0;
top: -7rem;
height: 7rem;
background: linear-gradient(180deg, rgba(244, 239, 228, 0) 0%, var(--cream) 92%);
pointer-events: none;
}
.story[data-locked="false"] .gate {
display: none;
}
.gate__card {
position: relative;
background: var(--paper);
border: 1px solid var(--rule-2);
border-top: 4px solid var(--red);
border-radius: var(--r-md);
padding: 1.9rem 1.7rem 1.6rem;
box-shadow: 0 1px 0 var(--rule), 0 18px 40px -28px rgba(22, 19, 15, 0.5);
text-align: center;
}
.gate__title {
font-family: var(--serif);
font-weight: 800;
font-size: clamp(1.6rem, 4.5vw, 2.1rem);
line-height: 1.08;
margin: 0 0 0.6rem;
}
.gate__sub {
font-size: 0.95rem;
line-height: 1.5;
color: var(--ink-3);
max-width: 42ch;
margin: 0 auto 1.4rem;
}
/* plan toggle */
.plan {
margin: 0 auto 1.3rem;
max-width: 360px;
}
.plan__toggle {
display: inline-flex;
border: 1px solid var(--rule-2);
border-radius: 999px;
padding: 4px;
background: var(--newsprint);
gap: 4px;
}
.plan__opt {
font: inherit;
font-size: 0.85rem;
font-weight: 600;
letter-spacing: 0.02em;
border: 0;
background: transparent;
color: var(--ink-3);
padding: 0.5rem 1.1rem;
border-radius: 999px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.45rem;
transition: background 0.18s, color 0.18s, box-shadow 0.18s;
}
.plan__opt.is-active {
background: var(--ink);
color: var(--paper);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.plan__save {
font-size: 0.62rem;
letter-spacing: 0.04em;
text-transform: uppercase;
font-weight: 700;
color: var(--white);
background: var(--ok);
padding: 0.12rem 0.4rem;
border-radius: var(--r-sm);
}
.plan__opt.is-active .plan__save {
background: var(--ok);
}
.plan__price {
margin: 1.1rem 0 0.2rem;
display: flex;
align-items: baseline;
justify-content: center;
gap: 0.35rem;
}
.plan__amount {
font-family: var(--serif);
font-weight: 800;
font-size: 2.6rem;
line-height: 1;
color: var(--ink);
}
.plan__per {
font-size: 0.9rem;
color: var(--muted);
}
.plan__note {
margin: 0;
font-size: 0.78rem;
color: var(--muted);
}
/* benefits */
.benefits {
list-style: none;
margin: 0 auto 1.5rem;
padding: 1.2rem 0 0;
max-width: 340px;
text-align: left;
border-top: 1px solid var(--rule-hair);
}
.benefits li {
position: relative;
padding: 0.4rem 0 0.4rem 1.7rem;
font-size: 0.92rem;
color: var(--ink-2);
line-height: 1.4;
}
.benefits li::before {
content: "";
position: absolute;
left: 0;
top: 0.62rem;
width: 14px;
height: 8px;
border-left: 2px solid var(--red);
border-bottom: 2px solid var(--red);
transform: rotate(-45deg);
}
/* CTA */
.cta {
font: inherit;
font-size: 1rem;
font-weight: 700;
letter-spacing: 0.01em;
color: var(--paper);
background: var(--red);
border: 1px solid var(--red-d);
border-radius: var(--r-md);
padding: 0.95rem 1.4rem;
width: 100%;
max-width: 360px;
cursor: pointer;
transition: background 0.15s, transform 0.05s;
}
.cta:hover {
background: var(--red-d);
}
.cta:active {
transform: translateY(1px);
}
.cta[disabled] {
opacity: 0.65;
cursor: default;
}
.gate__signin {
margin: 1.1rem 0 0.4rem;
font-size: 0.9rem;
color: var(--ink-3);
}
.gate__fine {
margin: 0;
font-size: 0.74rem;
color: var(--muted);
}
/* ============ UNLOCKED REMAINDER ============ */
.rule {
border: 0;
border-top: 1px solid var(--rule);
margin: 1.6rem 0;
}
.rule--full {
border-top: 3px double var(--rule-2);
}
.figure-inset {
margin: 1.6rem 0;
}
.figure-inset .hero__img {
aspect-ratio: 3 / 2;
}
.pullquote {
font-family: var(--serif);
font-weight: 600;
font-style: italic;
font-size: clamp(1.45rem, 4vw, 2rem);
line-height: 1.22;
color: var(--ink);
margin: 1.8rem 0;
padding: 0.4rem 0 0.4rem 1.4rem;
border-left: 3px solid var(--red);
}
.pullquote cite {
display: block;
margin-top: 0.7rem;
font-family: var(--sans);
font-style: normal;
font-weight: 600;
font-size: 0.82rem;
letter-spacing: 0.04em;
color: var(--muted);
}
/* ============ FOOTER ============ */
.footer {
max-width: 720px;
margin: 2.5rem auto 0;
padding: 1.4rem 1.25rem 2.4rem;
border-top: 3px double var(--rule-2);
font-size: 0.78rem;
color: var(--muted);
text-align: center;
}
/* ============ TOAST ============ */
.toast {
position: fixed;
left: 50%;
bottom: 1.4rem;
transform: translateX(-50%) translateY(1.2rem);
background: var(--ink);
color: var(--paper);
font-size: 0.88rem;
font-weight: 500;
padding: 0.8rem 1.2rem;
border-radius: var(--r-md);
box-shadow: 0 14px 34px -16px rgba(0, 0, 0, 0.6);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s, transform 0.25s;
z-index: 60;
max-width: calc(100vw - 2rem);
text-align: center;
}
.toast[data-show="true"] {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.toast .toast__accent {
color: #ff9d93;
font-weight: 700;
}
/* ============ RESPONSIVE ============ */
@media (max-width: 720px) {
body {
font-size: 16px;
}
.byline {
flex-direction: column;
align-items: flex-start;
}
.gate__card {
padding: 1.5rem 1.15rem 1.3rem;
}
}
@media (max-width: 380px) {
.masthead__nav {
gap: 0.9rem;
font-size: 0.7rem;
}
.plan__opt {
padding: 0.5rem 0.75rem;
font-size: 0.8rem;
}
.meter__pips {
display: none;
}
}
@media (prefers-reduced-motion: reduce) {
* {
transition: none !important;
}
}(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer = null;
function toast(msg, accent) {
if (!toastEl) return;
toastEl.innerHTML = accent
? msg.replace(accent, '<span class="toast__accent">' + accent + "</span>")
: msg;
toastEl.hidden = false;
// force reflow so the transition runs every time
void toastEl.offsetWidth;
toastEl.setAttribute("data-show", "true");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.setAttribute("data-show", "false");
}, 2600);
}
/* ---------- free-article counter ---------- */
var FREE_LIMIT = 3;
var articlesRead = 1; // this story counts as #1
var meterText = document.getElementById("meterText");
var meterPips = document.getElementById("meterPips");
var gateRead = document.getElementById("gateRead");
function renderMeter() {
var remaining = Math.max(0, FREE_LIMIT - articlesRead);
if (meterText) {
if (remaining > 0) {
meterText.innerHTML =
"You’ve read <strong>" +
articlesRead +
"</strong> of " +
FREE_LIMIT +
" free articles this month · " +
remaining +
(remaining === 1 ? " left." : " left.");
} else {
meterText.innerHTML =
"You’ve used all <strong>" +
FREE_LIMIT +
"</strong> free articles this month.";
}
}
if (gateRead) gateRead.textContent = String(articlesRead);
if (meterPips) {
var html = "";
for (var i = 0; i < FREE_LIMIT; i++) {
html += '<i class="' + (i < articlesRead ? "used" : "") + '"></i>';
}
meterPips.innerHTML = html;
}
}
renderMeter();
/* ---------- plan toggle ---------- */
var PLANS = {
monthly: {
amount: "$8",
per: "/ month",
note: "Billed monthly. Cancel anytime.",
},
annual: {
amount: "$67",
per: "/ year",
note: "Billed annually at $67 — that’s $5.58/month. Save 30%.",
},
};
var currentPlan = "monthly";
var planAmount = document.getElementById("planAmount");
var planPer = document.getElementById("planPer");
var planNote = document.getElementById("planNote");
var planButtons = Array.prototype.slice.call(
document.querySelectorAll(".plan__opt")
);
function setPlan(plan) {
if (!PLANS[plan]) return;
currentPlan = plan;
var p = PLANS[plan];
if (planAmount) planAmount.textContent = p.amount;
if (planPer) planPer.textContent = p.per;
if (planNote) planNote.innerHTML = p.note;
planButtons.forEach(function (btn) {
var active = btn.getAttribute("data-plan") === plan;
btn.classList.toggle("is-active", active);
btn.setAttribute("aria-selected", active ? "true" : "false");
});
}
planButtons.forEach(function (btn) {
btn.addEventListener("click", function () {
setPlan(btn.getAttribute("data-plan"));
});
});
/* ---------- subscribe / unlock ---------- */
var story = document.getElementById("story");
var subscribeBtn = document.getElementById("subscribe");
var rest = document.getElementById("rest");
function unlock() {
if (story.getAttribute("data-locked") === "false") return;
story.setAttribute("data-locked", "false");
if (rest) rest.hidden = false;
var label =
currentPlan === "annual" ? "Annual" : "Monthly";
var price = PLANS[currentPlan].amount;
if (subscribeBtn) {
subscribeBtn.textContent = "Subscribed — enjoy The Harbor Review";
subscribeBtn.disabled = true;
}
// unlimited reading once subscribed
if (meterText)
meterText.innerHTML =
"<strong>Subscriber</strong> · unlimited access unlocked.";
if (meterPips) meterPips.innerHTML = "";
toast("Welcome aboard — " + label + " plan active (" + price + ").", price);
// smooth-scroll to where the gate was so the reader continues
if (rest && rest.scrollIntoView) {
rest.scrollIntoView({ behavior: "smooth", block: "start" });
}
}
if (subscribeBtn) subscribeBtn.addEventListener("click", unlock);
/* sign-in acts as an unlock shortcut for the demo */
var signin = document.getElementById("signin");
if (signin) {
signin.addEventListener("click", function (e) {
e.preventDefault();
toast("Signed in — restoring your subscription.");
setTimeout(unlock, 500);
});
}
/* ---------- share ---------- */
var shareBtn = document.querySelector("[data-share]");
if (shareBtn) {
shareBtn.addEventListener("click", function () {
var url = window.location.href;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(url).then(
function () {
toast("Link copied to clipboard.");
},
function () {
toast("Share this story with a colleague.");
}
);
} else {
toast("Share this story with a colleague.");
}
});
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>The Harbor Review — Paywall / Subscribe Gate</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=Playfair+Display:ital,wght@0,500;0,600;0,700;0,800;0,900;1,500;1,600&family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#story">Skip to story</a>
<!-- ============ MASTHEAD ============ -->
<header class="masthead">
<div class="masthead__bar">
<span class="masthead__date">Tuesday, June 9, 2026 · Late City Edition</span>
<span class="masthead__weather">Harbor 64° · Wind W 11mph · High tide 2:48 PM</span>
</div>
<div class="masthead__name">
<h1 class="masthead__title">The Harbor Review</h1>
<p class="masthead__motto">Founded 1887 · “The Tide Reaches Every Door”</p>
</div>
<nav class="masthead__nav" aria-label="Sections">
<a href="#" aria-current="page">Civic</a>
<a href="#">Harbor</a>
<a href="#">Business</a>
<a href="#">Culture</a>
<a href="#">Opinion</a>
<a href="#">Weather</a>
</nav>
</header>
<!-- ============ FREE-ARTICLE COUNTER STRIP ============ -->
<div class="meter" id="meter" role="status" aria-live="polite">
<span class="meter__dot" aria-hidden="true"></span>
<span class="meter__text" id="meterText">You’ve read 1 of 3 free articles this month.</span>
<span class="meter__pips" id="meterPips" aria-hidden="true"></span>
</div>
<!-- ============ STORY ============ -->
<main>
<article class="story" id="story" data-locked="true">
<header class="story__head">
<p class="kicker">Harbor · Investigation</p>
<h2 class="story__title">As the Tideworks Close, a Working Waterfront Asks What Comes Next</h2>
<p class="story__deck">
For 130 years the dry docks of the Old Mole employed half the Eastside. The last keel
left in April. Now the city must decide whether the basin becomes a park, a port, or
something nobody has named yet.
</p>
<div class="byline">
<div class="byline__who">
<span class="byline__by">By <strong>Marguerite Halloran</strong> & <strong>Desmond Okafor</strong></span>
<span class="byline__line">Harbor Desk · Reporting from the Old Mole</span>
</div>
<div class="byline__meta">
<span class="dateline">EASTSIDE</span>
<span class="readtime">9 min read</span>
<button class="share" type="button" data-share aria-label="Share this story">Share</button>
</div>
</div>
</header>
<figure class="hero">
<div class="hero__img hero__img--dock" role="img"
aria-label="Empty dry dock at dusk, cranes silhouetted against a red-tinted sky"></div>
<figcaption>
<span class="cap">The emptied graving dock at the Old Mole, photographed the evening the
last vessel was floated out.</span>
<span class="credit">Ilse Brandt / The Harbor Review</span>
</figcaption>
</figure>
<!-- READABLE BODY -->
<div class="body" id="body">
<p class="lead">
The water in the No. 3 graving dock has been pumped out so many thousands of times that
the men who did it stopped counting decades ago. On the last Friday in April they let it
come back in for good. There was no ceremony. A foreman in a yellow slicker pulled a lever,
the sluice gates shuddered, and the harbor began to fold itself back over a basin where, for
one hundred and thirty years, ships had stood naked on wooden blocks while the Eastside
made its living on their hulls.
</p>
<p>
The Old Mole Tideworks did not announce its closing the way a factory might, with a date
and a press release. It closed the way a tide goes out — gradually, then all at once.
The contracts thinned. The apprenticeships dried up. By the time the city’s economic
office acknowledged the yard was finished, most of the welders had already drifted to the
container terminal three miles north, or out of the trade entirely.
</p>
<p>
“You can’t teach a graving dock,” said Henrietta Voss, 71, whose father
and grandfather both fitted boilers here. She stood at the chain-link fence on the morning
the gates reopened, watching the brown water climb the limestone steps. “You can teach
a person to weld. But the dock itself knows things. Where it leaks. How it breathes in
winter. That knowledge just went under.”
</p>
<p>
The basin’s future is now the most contested square mile on the waterfront. Three
proposals sit before the Harbor Commission, and each imagines a different city.
</p>
<!-- transitional content that fades under the gate -->
<div class="fadezone" id="fadezone">
<p>
The first, backed by the Eastside Civic Alliance, would fill the dock and crown it with a
tidal park: salt meadows, a boardwalk, an amphitheater pointed at the sunset. Renderings
show children where the keel blocks used to sit. The Alliance calls it restitution —
a green return for a neighborhood that breathed shipyard dust for a century.
</p>
<p>
The second proposal keeps the water working. A consortium of regional freight operators
wants to dredge the basin two metres deeper and reopen it for barge repair, arguing the
coast cannot afford to surrender a single deepwater berth. They promise four hundred jobs,
though they will not say how many will go to people who live within walking distance.
</p>
<p>
The third is harder to summarize, which is perhaps why it has the fewest defenders and the
loudest ones. A design collective calling itself the Mole Assembly proposes leaving the
dock half-flooded and unfinished — part ruin, part laboratory — and letting the
neighborhood decide, slowly, year by year, what it should become.
</p>
</div>
</div>
<!-- ============ PAYWALL GATE ============ -->
<section class="gate" id="gate" aria-labelledby="gateTitle">
<div class="gate__veil" aria-hidden="true"></div>
<div class="gate__card">
<p class="gate__kicker">Subscriber Story</p>
<h3 class="gate__title" id="gateTitle">Keep reading The Harbor Review.</h3>
<p class="gate__sub" id="gateSub">
You’ve read <strong id="gateRead">1</strong> of 3 free articles this month.
Subscribe to finish this investigation and support local reporting from the waterfront.
</p>
<div class="plan" role="group" aria-label="Choose a billing plan">
<div class="plan__toggle" role="tablist" aria-label="Billing period">
<button class="plan__opt is-active" id="planMonthly" type="button"
role="tab" aria-selected="true" data-plan="monthly">Monthly</button>
<button class="plan__opt" id="planAnnual" type="button"
role="tab" aria-selected="false" data-plan="annual">
Annual <span class="plan__save">Save 30%</span>
</button>
</div>
<div class="plan__price" aria-live="polite">
<span class="plan__amount" id="planAmount">$8</span>
<span class="plan__per" id="planPer">/ month</span>
</div>
<p class="plan__note" id="planNote">Billed monthly. Cancel anytime.</p>
</div>
<ul class="benefits" aria-label="What's included">
<li>Unlimited access to every Harbor Review story</li>
<li>The Morning Tide newsletter, six days a week</li>
<li>The full investigations archive, back to 1887</li>
<li>Ad-light reading on every device</li>
</ul>
<button class="cta" id="subscribe" type="button">
Subscribe & keep reading
</button>
<p class="gate__signin">
Already a subscriber?
<a href="#" id="signin">Sign in</a>
</p>
<p class="gate__fine">
Cancel anytime. By subscribing you agree to the (fictional) Terms of Service.
</p>
</div>
</section>
<!-- HIDDEN REMAINDER, revealed on subscribe -->
<div class="body body--rest" id="rest" hidden>
<hr class="rule rule--full" />
<p>
What the three plans share is a quiet anxiety about memory. None of the proposals can quite
decide whether the Tideworks should be remembered as a place of labor or mourned as a place
of loss — and the difference will shape everything from the height of a railing to the
wording on a plaque.
</p>
<figure class="figure-inset">
<div class="hero__img hero__img--blueprint" role="img"
aria-label="Faded harbor survey drawing in ink-blue tones"></div>
<figcaption>
<span class="cap">An 1911 survey of the graving docks, from the Commission archive.</span>
<span class="credit">Harbor Commission / Public Record</span>
</figcaption>
</figure>
<blockquote class="pullquote">
“The dock itself knows things. Where it leaks. How it breathes in winter.
That knowledge just went under.”
<cite>— Henrietta Voss, third-generation shipwright</cite>
</blockquote>
<p>
The Harbor Commission will hold its first public hearing on the basin in September. Until
then the gates stay open, the water stays in, and a neighborhood that spent a century
keeping the sea out of one square mile waits to learn what the sea’s return will mean.
</p>
<p>
Henrietta Voss plans to be at the fence again that morning. “I’ll come,”
she said. “Somebody has to remember what the bottom looked like.”
</p>
</div>
</article>
</main>
<footer class="footer">
<p>The Harbor Review · A fictional broadsheet for layout demonstration only.</p>
</footer>
<!-- toast -->
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
<script src="script.js"></script>
</body>
</html>Paywall / Subscribe Gate
A complete metered-paywall reading view for The Harbor Review, a fictional broadsheet art-directed in a warm newsprint palette with hairline rules and a single red accent. A waterfront investigation opens with a red kicker, an oversized Playfair Display headline, an italic deck, a two-name byline and a duotone CSS hero standing in for a press photograph. The lead paragraph carries a red drop cap and the body is set as justified, hyphenated copy.
Partway down, the transitional paragraphs fade into a gradient veil and a subscribe gate card takes over. The card shows a free-article counter (“You’ve read 1 of 3 free articles”), a Monthly / Annual billing toggle with a green “Save 30%” badge, a live price block, a checkmarked benefits list, a sign-in link and a red subscribe CTA. A slim meter strip under the masthead mirrors the counter with pip indicators.
Three vanilla-JS interactions ship with it: the free-article counter renders remaining reads and pips, the plan toggle swaps the headline price and billing note between Monthly and Annual, and the subscribe button (or the sign-in shortcut) raises a toast, marks the reader a subscriber, and unlocks the hidden remainder of the article — a captioned blueprint figure and an oversized serif pull quote — scrolling the reader on. There are no frameworks, no build step and no network requests beyond the two Google Fonts.
Illustrative UI only — masthead, headlines, bylines, and articles are fictional; not a real news publication.