mirror of
https://github.com/apophenia-news/apophenia.news.git
synced 2026-10-08 21:35:23 +00:00
148 lines
7.8 KiB
HTML
148 lines
7.8 KiB
HTML
|
|
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Unsubscribe — apophenia.news</title>
|
|
<meta name="description" content="Unsubscribe from the apophenia.news newsletter." />
|
|
<meta property="og:title" content="Unsubscribe — apophenia.news" />
|
|
<meta property="og:description" content="Unsubscribe from the apophenia.news newsletter." />
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:url" content="https://apophenia.news/newsletter/unsubscribe" />
|
|
|
|
<meta name="theme-color" content="#fcfcfa" />
|
|
<link rel="canonical" href="https://apophenia.news/newsletter/unsubscribe" />
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
<link rel="alternate" type="application/rss+xml" title="apophenia.news RSS" href="https://apophenia.news/rss.xml" />
|
|
<script data-goatcounter="https://apophenia.goatcounter.com/count" async src="//gc.zgo.at/count.js"></script>
|
|
<script type="module" crossorigin src="/assets/main-FLTSWrzJ.js"></script>
|
|
<link rel="stylesheet" crossorigin href="/assets/main-BPy5v_d0.css">
|
|
</head>
|
|
<body>
|
|
|
|
|
|
<header class="sticky top-0 z-40 border-b border-zinc-200/80 bg-paper/95 backdrop-blur">
|
|
<div class="shell py-3 flex items-center justify-between gap-4" x-data="{open:false}">
|
|
<a href="/" class="inline-flex items-center gap-3">
|
|
|
|
<svg class="h-10 w-10 brand-glow" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Apophenia News logo">
|
|
<defs>
|
|
<linearGradient id="ap-grad" x1="0" y1="0" x2="1" y2="1">
|
|
<stop offset="0%" stop-color="#4f46e5"/>
|
|
<stop offset="100%" stop-color="#0ea5e9"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<rect x="6" y="6" width="108" height="108" rx="28" fill="#ffffff" stroke="url(#ap-grad)" stroke-width="8"/>
|
|
<circle cx="36" cy="36" r="8" fill="#4f46e5"/>
|
|
<circle cx="84" cy="36" r="8" fill="#0ea5e9"/>
|
|
<circle cx="36" cy="84" r="8" fill="#0ea5e9"/>
|
|
<circle cx="84" cy="84" r="8" fill="#4f46e5"/>
|
|
<path d="M36 36L84 84M84 36L36 84M36 36H84M36 84H84" stroke="#111827" stroke-opacity=".6" stroke-width="4" stroke-linecap="round"/>
|
|
<circle cx="60" cy="60" r="10" fill="#111827"/>
|
|
</svg>
|
|
<div>
|
|
<p class="text-lg leading-none font-bold">apophenia.news</p>
|
|
<p class="text-xs text-zinc-500">for pattern seekers</p>
|
|
</div>
|
|
</a>
|
|
<button class="sm:hidden p-2 rounded-lg border border-zinc-200 bg-white" @click="open=!open" aria-label="Toggle menu">
|
|
<i data-lucide="menu" class="h-4 w-4"></i>
|
|
</button>
|
|
<nav class="hidden sm:flex items-center gap-6 text-sm">
|
|
<a href="/">Home</a>
|
|
<a href="/newsletter/">Newsletter</a>
|
|
<a href="https://store.apophenia.news" target="_blank" rel="noopener">Store</a>
|
|
<a href="/write/">Become a writer</a>
|
|
<a href="/rss.xml">RSS</a>
|
|
</nav>
|
|
<nav class="sm:hidden absolute left-4 right-4 top-[70px] card p-3" x-show="open" x-transition>
|
|
<a class="block py-1.5" href="/">Home</a>
|
|
<a class="block py-1.5" href="/newsletter/">Newsletter</a>
|
|
<a class="block py-1.5" href="https://store.apophenia.news" target="_blank" rel="noopener">Store</a>
|
|
<a class="block py-1.5" href="/write/">Become a writer</a>
|
|
<a class="block py-1.5" href="/rss.xml">RSS</a>
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="shell py-10">
|
|
<section class="card max-w-md mx-auto p-6 sm:p-8" x-data="{
|
|
email: '', done: '', auto: false, loading: false, ok: false, error: '',
|
|
init() {
|
|
// ?email=a+b@x.com -> URLSearchParams turns '+' into ' ', and emails never contain spaces
|
|
const e = new URLSearchParams(location.search).get('email')?.trim().replace(/ /g, '+');
|
|
if (e) (this.email = e, this.auto = true, this.submit());
|
|
},
|
|
async submit() {
|
|
if (this.loading) return;
|
|
this.loading = true;
|
|
this.ok = false;
|
|
this.error = '';
|
|
try {
|
|
const res = await fetch('https://newsletter.planetrenox.com/api/unsub', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ pool: 'apophenia', email: this.email.trim() })
|
|
});
|
|
const data = await res.json();
|
|
if (res.ok && data.ok) {
|
|
this.done = data.email;
|
|
this.ok = true;
|
|
this.email = '';
|
|
history.replaceState(null, '', location.pathname);
|
|
} else {
|
|
this.error = data.error === 'invalid email' ? 'Please enter a valid email address.' : 'Something went wrong. Please try again.';
|
|
}
|
|
} catch {
|
|
this.error = 'Could not connect. Please try again.';
|
|
} finally {
|
|
this.loading = false;
|
|
this.auto = false;
|
|
}
|
|
}
|
|
}">
|
|
<div x-show="ok" x-cloak role="status" class="text-center py-2">
|
|
<svg class="unsub-check mx-auto h-32 w-32" viewBox="0 0 120 120" aria-hidden="true">
|
|
<circle class="uc-wave" cx="60" cy="60" r="44"/>
|
|
<g class="uc-burst"><line x1="60" y1="9" x2="60" y2="2" transform="rotate(0 60 60)"/><line x1="60" y1="9" x2="60" y2="2" transform="rotate(45 60 60)"/><line x1="60" y1="9" x2="60" y2="2" transform="rotate(90 60 60)"/><line x1="60" y1="9" x2="60" y2="2" transform="rotate(135 60 60)"/><line x1="60" y1="9" x2="60" y2="2" transform="rotate(180 60 60)"/><line x1="60" y1="9" x2="60" y2="2" transform="rotate(225 60 60)"/><line x1="60" y1="9" x2="60" y2="2" transform="rotate(270 60 60)"/><line x1="60" y1="9" x2="60" y2="2" transform="rotate(315 60 60)"/></g>
|
|
<circle class="uc-disc" cx="60" cy="60" r="44"/>
|
|
<circle class="uc-ring" cx="60" cy="60" r="44" pathLength="1"/>
|
|
<path class="uc-tick" d="M39 61.5l14 14 28-29" pathLength="1"/>
|
|
</svg>
|
|
<h1 class="uc-text mt-5 text-3xl font-bold">You're unsubscribed</h1>
|
|
<p class="uc-text mt-3 text-zinc-600"><b class="text-zinc-900 break-all" x-text="done"></b> won't receive Apophenia emails anymore.</p>
|
|
<a href="/newsletter/" class="uc-text mt-6 inline-block text-sm">Changed your mind? Resubscribe</a>
|
|
</div>
|
|
|
|
<div x-show="auto && !ok" x-cloak class="py-10 text-center text-zinc-600">
|
|
<span class="mx-auto mb-4 block h-10 w-10 animate-spin rounded-full border-4 border-emerald-100 border-t-emerald-500"></span>
|
|
Unsubscribing <b class="text-zinc-900 break-all" x-text="email"></b>…
|
|
</div>
|
|
|
|
<div x-show="!ok && !auto">
|
|
<h1 class="text-3xl font-bold">Unsubscribe</h1>
|
|
<p class="mt-3 text-zinc-600">Enter your email to stop receiving our newsletter.</p>
|
|
<form class="mt-6" novalidate @submit.prevent="submit">
|
|
<label for="unsubscribe-email" class="block text-sm font-medium">Email address</label>
|
|
<input id="unsubscribe-email" name="email" type="email" autocomplete="email" required x-model="email" :disabled="loading"
|
|
placeholder="you@example.com" class="mt-2 w-full rounded-xl border border-zinc-300 px-4 py-3 focus:outline-none focus:ring-2 focus:ring-indigo-400" />
|
|
<button type="submit" :disabled="loading"
|
|
class="mt-3 w-full rounded-xl bg-accent px-4 py-3 font-medium text-white transition hover:bg-indigo-700 disabled:opacity-60"
|
|
x-text="loading ? 'Unsubscribing…' : 'Unsubscribe'">Unsubscribe</button>
|
|
<p x-show="error" x-cloak role="alert" x-text="error" class="mt-4 text-sm text-rose-700"></p>
|
|
</form>
|
|
<a href="/newsletter/" class="mt-6 inline-block text-sm">Back to newsletter</a>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<footer class="mt-16 border-t border-zinc-200 bg-white/70">
|
|
<div class="shell py-8 text-sm text-zinc-600 flex flex-col sm:flex-row justify-between gap-2">
|
|
<p>Apophenia News — finding patterns in the noise since 2026</p>
|
|
<p><a href="https://www.youtube.com/@apophenia-loop" target="_blank" rel="noopener" class="hover:text-zinc-900 hover:underline">YouTube</a> · © 2026 apophenia.news</p>
|
|
</div>
|
|
</footer>
|
|
</body>
|
|
</html>
|