Security Status Documentation
A production-grade, static documentation template built for the Cloudflare edge: glassmorphic dark UI, a scroll-spy navigation tree, syntax-highlighted command regions, and a built-in bot-protection slot. Everything below is real, working content — copy the file, push it to a Pages project, and it renders on the first try.
Zero build step
One HTML file with inlined Tailwind config and vanilla JS. No bundler, no framework hydration, no node_modules on deploy.
Edge-ready security
A Turnstile validation pill, CSP-ready header table, and middleware snippet wired for Cloudflare Pages Functions.
Responsive dual column
Sticky scrollable sidebar on desktop, slide-in drawer under 1024px, and typography tuned for long-form reading.
41
Sections
0
Build deps
100
Lighthouse target
A11y
Focus & motion
Getting Started
Quick Start
Three commands stand up a local preview. The fourth publishes the folder to a Cloudflare Pages project. There is no compile phase — the browser receives exactly the bytes you write.
-
1
Create the project folder
Drop
index.htmlinto an empty directory. That single file is the entire site. -
2
Serve it locally
Any static file server works. Use
npx serve,python -m http.server, or the VS Code Live Server extension. -
3
Authenticate Wrangler once
npx wrangler loginopens a browser OAuth flow and stores the token in~/.wrangler/config. -
4
Deploy
Create the project once, then push the directory on every change.
# 1 — local preview
$ npx serve .
# 2 — link this machine to your Cloudflare account
$ npx wrangler login
# 3 — create the Pages project exactly once
$ npx wrangler pages project create security-status --production-branch=main
# 4 — ship the folder (repeat freely)
$ npx wrangler pages deploy . --project-name=security-status
✨ Deployment complete. Take a sip of coffee, it's already live.
Getting Started
Installation
The template has no package dependency, but you may want tooling around it. Pick the tab that matches your environment.
$ mkdir security-status && cd security-status
$ npm create quickdocs@latest
$ npm i -D wrangler serve
$ mkdir security-status && cd security-status
$ pnpm dlx quickdocs create
$ pnpm add -D wrangler serve
$ mkdir security-status && cd security-status
$ bun create quickdocs@latest
$ bun add -d wrangler
No tooling required. Save this page as index.html, put it in an empty folder, and upload that folder in the Cloudflare dashboard under Workers & Pages → Create → Pages → Upload assets. The only external requests are the Tailwind CDN script and Google Fonts; everything else is inline.
Requirements
| Component | Minimum | Recommended | Notes |
|---|---|---|---|
| Node.js | 18.x | 22.x LTS | Only needed for Wrangler and a local server |
| Wrangler | 3.x | 4.x | CLI used for Pages deploys |
| Browser | Chrome 90+ | Latest evergreen | Uses backdrop-filter and IntersectionObserver |
| Account | Free plan | Pro | 500 build minutes/mo and unlimited static requests on Free |
Getting Started
Deploy to Cloudflare Pages
Two paths exist: upload a folder directly from the dashboard, or push to Git and let Cloudflare build. For a single static file, direct upload is faster and has no build queue.
Direct upload
- 1.Dashboard → Workers & Pages → Create application → Pages → Upload assets.
- 2.Name the project (this becomes project.pages.dev).
- 3.Drag the folder containing index.html into the drop zone.
- 4.Attach a custom domain under the Custom domains tab.
Git integration
- 1.Push the folder to GitHub, GitLab, or a public repo.
- 2.Dashboard → Create application → Pages → Connect to Git.
- 3.Set Build command = (empty) and Build output directory = docs-template.
- 4.Every commit to main produces a new deployment automatically.
Wrangler deploy reference
| Command | Purpose | Run when |
|---|---|---|
| wrangler login | Stores an OAuth token locally | Once per machine |
| wrangler pages project create NAME | Provisions a new Pages project | Once per project |
| wrangler pages deploy DIR | Uploads DIR as a new deployment | Every content change |
| wrangler pages deployment list | Shows rollbacks available | When verifying or recovering |
| wrangler pages deployment rollback | Promotes the previous deployment | When a release goes wrong |
Never commit secrets to a static site
Anything in the deployed folder is world-readable. API tokens, private keys, and Turnstile secret keys belong in Pages environment variables consumed by a Function, never in index.html.
Core Concepts
Configuration
One tailwind.config object inside the <head> drives every color, font, and animation on the page. Edit it in place; the Play CDN recompiles on the next paint.
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
ink: { 950: '#050510', 850: '#10111f', 700: '#1f2147' },
indigo: { '300': '#67e8f9', '400': '#22d3ee', '500': '#06b6d4' },
},
fontFamily: {
sans: ['Inter', 'system-ui'],
display: ['Chakra Petch', 'Inter'],
mono: ['JetBrains Mono', 'monospace']
}
}
}
}
bg-ink-950
Page canvas — the deepest layer, never used for cards.
glass / glass-strong
Translucent surfaces at 62% and 86% opacity with blur.
border-ink-700
The #1f2147 hairline that defines every container.
Core Concepts
Theming & Tokens
The palette is deliberately narrow: one near-black canvas, two glass surfaces, one border hue, and three signal colors. Signal colors carry meaning — indigo for navigation, violet for versioning and metadata, emerald for healthy states.
Type scale
Core Concepts
Layout & Routing
The shell is a flex row under a sticky header. The sidebar is sticky top-16 with its own scroll context; the main column is min-w-0 so long code blocks never blow out the grid.
aside
w-72 sticky
header h-16 sticky top-0 z-50
main.max-w-4xl
prose column
right rail
optional
| Breakpoint | Sidebar | Header nav | Grids |
|---|---|---|---|
| < 640px | Hidden drawer | Icon-only search | Single column |
| 640 – 1023px | Hidden drawer | Full links | 2 columns |
| ≥ 1024px | Sticky, scrollable | Full links + status | 2–3 columns |
Anchors are the router. Every section carries id plus scroll-mt-24, so in-page links land clear of the sticky header. The IntersectionObserver in the script block keeps the sidebar highlight in sync as you scroll — no history API needed for a single-page doc.
Features
Command Tables
Reference tables pair a monospace command column with a plain-language description. Rows highlight on hover with a duration-300 fade so dense grids still feel alive.
| Flag | Type | Default | Description |
|---|---|---|---|
| --branch | string | main | Git branch to associate with the deployment |
| --project-name | string | — | Target Pages project; required when no config file exists |
| --commit-dirty | boolean | false | Allow deploying with uncommitted local changes |
| --preview | boolean | false | Publish to a preview alias instead of production |
| --env | enum | production | Bind production, preview, or a named environment |
Features
Code & Syntax Blocks
Dark syntax regions use hand-rolled token spans (.tok-k keys, .tok-s strings, .tok-c comments) instead of a highlighting library — the whole palette ships in about twenty lines of CSS.
export type Probe = {
id: string;
url: string;
intervalMs: number;
};
export async function probe(p: Probe, signal?: AbortSignal) {
const started = performance.now();
const res = await fetch(p.url, { signal, redirect: 'follow' });
return {
id: p.id,
ok: res.ok,
status: res.status,
latency: Math.round(performance.now() - started)
};
}
Features
Status Indicators
Status is communicated three times over — color, motion, and text — so it survives color-blindness and print. Dots use animate-pulse for ambient life and an expanding ring for attention states.
Operational pills
Ring + pulse
Component checklist
- ✓Never rely on hue alone — always pair with a label
- ✓Keep pulse loops at 1.5s or slower to avoid distraction
- ✓Honor
prefers-reduced-motion(this file does) - ✓Add
aria-livewhen status updates without navigation
Features
Responsive & Device Support
The layout is fluid from 320px phones to ultrawide monitors. Nothing requires a mouse: every control is reachable by keyboard, every target is at least 44px on touch screens, and text stays legible when users zoom the page to 200%.
Phone
320 – 639px
Single column, drawer navigation, icon-only search, tables scroll horizontally inside their card.
Tablet
640 – 1023px
Two-up card grids, full header links, drawer navigation, touch-friendly tab strip.
Laptop
1024 – 1279px
Sticky sidebar appears, three-up grids, status pill visible in the header.
Desktop
1280px and up
Full three-column shell: sidebar, document body, and the “On this page” rail.
Tested platforms
| Platform | Minimum version | Input modes | Notes |
|---|---|---|---|
| iOS / iPadOS Safari | 15.4+ | Touch, keyboard | Backdrop blur prefixed for WebKit |
| Android Chrome | 100+ | Touch, stylus | Stable scrolling with the fixed header |
| Windows Chrome / Edge | 100+ | Mouse, touch, pen | Snap layouts friendly (edge-to-edge panels) |
| macOS Safari / Chrome | 15+ | Trackpad, keyboard | Smooth-scroll respects reduced motion |
| Linux Firefox / Chromium | 115+ | Mouse, keyboard | Custom scrollbar styling applied |
| Screen readers | NVDA / VoiceOver | AT navigation | Landmarks, skip link, aria-labels on all icon buttons |
Touch targets
Buttons and links pad out to roughly 44×44px on small screens so taps never miss.
Reflow & zoom
No horizontal page scrolling at 320px width; 200% zoom reflows the grids without clipping.
Orientation
Portrait and landscape both work; the sidebar collapses before content would ever collide.
Features
SEO & Discoverability
Discoverability starts in the <head>. This file ships a keyword-rich meta description, a keyword list, Open Graph and Twitter cards, and two JSON-LD blocks (a TechArticle and the FAQ) so search engines can render rich results without guessing.
<meta name="description"
content="Complete documentation for Security Status: deployment on
Cloudflare Pages, security hardening guides and bot protection.">
<meta name="keywords"
content="documentation template, web security guide, phishing
prevention, turnstile bot protection, responsive design">
<meta property="og:title" content="Security Status — Documentation">
<meta name="twitter:card" content="summary_large_image">
<script type="application/ld+json">
{ "@type": "TechArticle", "headline": "...", "dateModified": "2026-10-07" }
</script>
Keyword placement checklist
| Location | Where it lives here | Weight |
|---|---|---|
| Title tag | Security Status — Documentation | Highest |
| H1 + first paragraph | Introduction section | Highest |
| Meta description | Head block, ~155 characters | High (CTR) |
| H2 headings | 41 section titles, no duplicates | High |
| JSON-LD keywords | TechArticle + FAQPage blocks | Rich results |
| Anchor text | Sidebar and footer links use human labels | Internal linking |
After you deploy: submit /sitemap.xml in Google Search Console and Bing Webmaster Tools, add a real og:image (1200×630) so link previews render, and keep dateModified in the JSON-LD in sync with your changelog.
Security & Safety
Security Overview
Security Status treats security as layered depth, not a single wall. The guides in this chapter cover both halves of the problem: how to keep the deployed site hardened at the edge, and how to keep yourself safe as the operator — accounts, browser, network, and device.
Site hardening
- •Content-Security-Policy and strict transport headers
- •Turnstile bot checks on any write endpoint
- •Secrets kept in encrypted Pages environment variables
- •Dependency-free HTML to shrink supply-chain risk
Personal hardening
- •Unique passwords in a reputable password manager
- •Phishing-resistant 2FA (passkeys or hardware keys)
- •Prompt updates, full-disk encryption, verified backups
- •Suspicious-link discipline and breach monitoring
The five-layer model
Identity
Who is asking? Passwords, passkeys, hardware keys, and session revocation.
Edge filtering
Is the request human and sane? Turnstile, rate limits, WAF rules, bot scores.
Transport & headers
TLS only, HSTS pinned, CSP locked down, no sniffing, no framing abuse.
Least privilege
API tokens scoped to one project, one action, with expiration dates.
Recovery
Backups you have tested, rollback plans, and an incident checklist you have read before you need it.
Security & Safety
Password Hygiene
The goal is not a password you can remember — it is one you never had to. A manager generating and storing 20+ character random strings per site removes the two failure modes that cause most account takeovers: reuse and shoulder-surfed secrets.
| Do | Why it works |
|---|---|
| Use a password manager | One strong master secret unlocks unique random credentials everywhere |
| Generate 16–32 character random strings | Randomness beats memorability; length beats clever substitutions |
| Change a password after a breach notice | Invalidates the leaked secret before it gets sprayed at other sites |
| Protect the manager with a passphrase + 2FA | Four random words plus a second factor is a defensible single door |
| Don't | Real risk |
|---|---|
| Reuse a work password on personal accounts | One breached site unlocks your inbox, then everything else |
| Store passwords in browser-synced plain notes | No encryption at rest, trivially exfiltrated by malware or a shared device |
| Send them over chat, email, or SMS | Those channels are logged, backed up, and often forwarded |
| Fall for "your account expires" pressure | Urgency is the #1 lever of credential phishing |
Check whether you are already exposed. Put your addresses into haveibeenpwned.com (it supports partial-matching so your full address is never sent) and enable the free breach alerts. If a site you use appears, rotate that password everywhere you reused it.
Security & Safety
Two-Factor Authentication
Not all second factors are equal. SMS codes stop casual attackers but fall to SIM-swapping and relay kits. The ranking below is the consensus order — pick the strongest method every critical account will accept.
| Method | Strength | Phishable | Notes |
|---|---|---|---|
| Passkey / WebAuthn | Excellent | No | Bound to the origin — a fake site cannot elicit a signature |
| Hardware security key | Excellent | No | FIDO2 key; carry a spare for recovery |
| Authenticator app (TOTP) | Good | Yes, via relay | Time-based codes; back up the setup seeds |
| Push approval | Good | Yes, with fatigue | Never approve a prompt you did not trigger |
| SMS code | Fair | Yes | Better than nothing; vulnerable to SIM swap |
Rollout order
- 1. Email account — the reset key for everything else
- 2. Password manager
- 3. Cloud, code hosting, and financial accounts
- 4. Social and shopping accounts
Recovery hygiene
- → Store backup codes in your manager, not a screenshot
- → Register two devices so one loss is not a lockout
- → Keep a printout of recovery codes in a sealed envelope
Security & Safety
Phishing Defense
Phishing has converged on one pattern: manufactured urgency plus a familiar-looking login. Modern kits replay the real page and only swap the form target, so visual inspection alone is no longer enough — verify the origin, not the artwork.
Red flags
- ✕Deadline threats: "verify within 24 hours or lose access"
- ✕A login reached from an email, DM, or QR code
- ✕Look-alike domains (extra hyphens, wrong TLD, homoglyphs)
- ✕Unexpected attachments — invoices, scan files, "documents"
- ✕Requests to disable 2FA "for troubleshooting"
- ✕Crypto or gift-card payment demands from support staff
Safe habits
- ✓Navigate from a bookmark or type the address yourself
- ✓Let the manager autofill — it refuses on wrong origins
- ✓Confirm sensitive requests through a second channel
- ✓Hover before clicking; read the whole domain, right to left
- ✓Report spoofs to your provider and the real brand
- ✓If you already typed credentials: rotate immediately
https://
cloudflare-login.verify-secure.co.attacker.tld/callback?next=cloudflare.com
▸ The host is everything after https:// up to the first /
▸ The registrable domain is the last two labels: attacker.tld
▸ Anything before that is subdomain decoration — it can say whatever it likes
Security & Safety
Browser Hardening
The browser is the most attacked piece of software on your machine. These settings cost nothing, take ten minutes, and close entire categories of drive-by compromise.
| Setting | Where | Recommended |
|---|---|---|
| Safe Browsing | Privacy & security | Standard or Enhanced |
| Third-party cookies | Site settings | Block in normal browsing |
| Pop-ups | Site settings | Blocked (default) |
| Push notifications | Site settings | Ask — deny unknown sites |
| Automatic downloads | Site settings | Block second requests |
| OS-level clipboard | Site permissions | Block by default |
| Extensions | Extensions page | Fewer is safer — audit monthly |
Separate profiles
Keep work, personal, and high-risk browsing in different profiles so cookies and permissions never bleed across.
Update on release day
Browser zero-days get weaponized within days. Enable background updates and restart when prompted.
Lockdown mode when targeted
If you face sophisticated risk, enable Lockdown/Safe Mode — it disables the features attackers lean on most.
Security & Safety
Network Privacy
Your DNS resolver sees every domain you visit, and public Wi-Fi lets strangers on the same segment probe your machine. Both problems have cheap, well-understood fixes.
Encrypted DNS
Switch to DoH or DoT so your ISP cannot log or hijack lookups (a common malware-injection trick). Use a resolver that publishes a no-logs policy.
VPN vs Tor
A VPN relocates trust to your provider and hides traffic from your ISP. Tor distributes trust across three relays and resists most traffic analysis, at real speed cost.
Public Wi-Fi
Verify the SSID with staff, forget networks after travel, disable file sharing, and prefer your phone's tethered connection for anything sensitive.
| Threat | Control | Effort |
|---|---|---|
| DNS eavesdropping | Enable DNS-over-HTTPS in the OS or browser | 2 minutes |
| Rogue Wi-Fi access point | Tethering, VPN, and always-on TLS | 5 minutes |
| Local network probing | OS firewall on, sharing off, guest network for IoT | 15 minutes |
| ISP behavior profiling | Reputable no-logs VPN or Tor | 30 minutes |
| Router firmware holes | Auto-update enabled, admin password changed, remote admin off | 10 minutes |
Security & Safety
Device Safety
Three controls carry most of the weight on the endpoint: keep it patched, keep it encrypted, and keep a copy of the data you would grieve losing.
Patch promptly
Turn on automatic updates for OS, browser, and apps. Monthly OS point releases routinely fix exploited privilege-escalation bugs.
Encrypt everything
FileVault, BitLocker, or LUKS. Full-disk encryption turns a lost laptop from a breach report into a paperweight.
Back up 3-2-1
Three copies, two media types, one offsite. Ransomware cannot hold data hostage if a clean copy lives elsewhere.
Backup verification ritual — run it quarterly
- ✓Restore one random file from every backup target
- ✓Confirm the backup job actually ran (not just scheduled)
- ✓Check free space — silent quota failures are common
- ✓Rotate recovery keys and re-verify printed copies
Security & Safety
Incident Response
When something goes wrong, speed matters more than sophistication. This six-phase checklist is a condensed version of established incident-response practice — print it, because the moment you need it your search engine is not your friend.
Contain first
Disconnect the affected device from the network (do not shut it down — volatile memory holds evidence). Revoke active sessions, rotate credentials from a clean device, and disable compromised API tokens.
Assess the scope
What account, data, and devices are involved? Check sign-in history, mail rules, forwarded addresses, OAuth app grants, and recently changed recovery options.
Eradicate
Remove malware or the attacker's persistence: unknown sessions, added SSH keys, cron jobs, browser extensions, recovery emails, and forwarding rules. When in doubt, rebuild from a known-good image.
Recover
Return to normal operation in stages: single account first, then devices, then integrations. Keep new passwords and tokens under heightened watch for at least two weeks.
Notify
Tell the people who need to know: your employer for company data, your bank for financial fraud, your provider for account takeover, and affected contacts if their data was exposed.
Learn
Write down the timeline, the entry point, and the control that would have stopped it. Convert each lesson into a concrete change — a new 2FA method, a blocked domain, an alert you now have.
Security & Safety
Mobile & Tablet Safety
Phones hold more personal data than laptops: messages, photos, banking apps, and your entire contact graph. The controls below take about twenty minutes and cover almost every realistic mobile threat.
Device settings
- ✓Six-digit passcode or longer — not a 4-digit PIN, not a pattern
- ✓Biometrics for daily unlock, passcode as the fallback
- ✓Auto-lock at 30–60 seconds; hide notification previews on the lock screen
- ✓Full-disk encryption is on by default once a passcode exists — keep it
- ✓Install OS updates the day they land; enable automatic updates
- ✓Enable Find My / Find My Device — it is your remote-wipe lifeline
Account & SIM
- ✓Set a carrier account PIN/passphrase — it is the main defense against SIM swapping
- ✓Move important accounts off SMS codes toward an authenticator app or passkey
- ✓Register two hardware keys or passkeys so one loss is never a lockout
- ✓Review app sign-in listings (Google, Apple, Microsoft) and revoke what you no longer use
- ✓Back up photos to an end-to-end encrypted backup, not only the default cloud roll
| Permission | Default posture | Red flag |
|---|---|---|
| Location | While using the app, or off | Always-on for a flashlight or calculator app |
| Contacts | Deny unless the app is a messenger | Games or wallpaper apps uploading your address book |
| Microphone / Camera | Per-session grant | Orange/green privacy indicators lighting up unprompted |
| SMS / Call log access | Deny — rarely legitimate | Any non-dialer app requesting it |
| Accessibility service | Deny for third-party apps | Sideloaded apps wanting to read every screen |
If the phone is lost or stolen: lock and locate it remotely first, then revoke active sessions from a trusted machine, change the email and password protecting the account, notify the carrier to suspend the SIM, and watch for SIM-swap attempts over the next few weeks.
Security & Safety
Safe Downloads & Software
Malware overwhelmingly arrives through the front door: a download the user chose to run. Verify what you install, get it from the source that wrote it, and never trade software integrity for a free copy.
Never
- • Cracked, patched, or “activation tool” software
- • Download buttons on video, torrent, and file-host mirrors
- • Toolbars, “driver updaters”, and free VPN bundles
- • Attachments claiming to be invoices or scan files
- • Software a stranger DM’d you “because it fixes the issue”
Always
- • Use the vendor’s own domain or an official package registry
- • Prefer the OS store or your distro’s package manager
- • Check the publisher name before clicking Run
- • Keep automatic updates on for everything installed
- • Uninstall what you no longer use — fewer apps, smaller surface
Watch for typosquatting
Lookalike package names thrive in every registry. Read character by character: a single swapped letter, hyphen, or extra dot is a different — hostile — package.
# Windows PowerShell
PS> Get-FileHash .\installer.exe -Algorithm SHA256
# macOS / Linux
$ shasum -a 256 installer.iso
# compare — character for character — against the hash the vendor published
a3f1...9c2e installer.iso ← must match exactly
Handling something truly untrusted? Run it in a throwaway environment: a VM with no shared folders, a live USB, or at minimum an account with no saved passwords and no access to your main files. When the analysis is done, discard the snapshot rather than “cleaning up” afterwards.
Security & Safety
Scam Playbook
Scams change costume constantly but reuse the same handful of scripts. Learn the pattern rather than the latest branding: an unexpected contact, a story that creates pressure, and a payment method that cannot be reversed.
| Scam | The hook | The tell | Your move |
|---|---|---|---|
| Tech support | “Your PC is infected, call this number” | Pop-ups that lock the browser or unsolicited calls | Hang up; contact the vendor through their real site |
| Romance / pig-butchering | Long-term affection, then an “investment tip” | Moves chat to an encrypted app fast; never meets | Never send money to someone you have not met in person |
| Sextortion | “Pay or your private media gets posted” | Threats with a countdown, often from a spoofed address | Do not pay; preserve evidence; report to local cybercrime units |
| Refund / parcel / tax | A refund, fine, or overpayment you never requested | Asks you to pay back money “by mistake” | Verify through the official app or statement, not their link |
| Job offer | Easy remote work, quick sign-up bonus | Pay first to “unlock tasks”; payment in crypto | Legit employers never charge to employ you |
| Family emergency | “It’s me, I’m in trouble, don’t tell anyone” | New number, refuses a voice call-back to a known number | Call the person back on the number you already have |
| Crypto drainer | Airdrop or mint that needs your wallet signature | Approves unlimited token spend, not a fixed amount | Revoke approvals; treat every new dApp as hostile |
Pressure is the tell
Real institutions give you time. A countdown clock is a manipulation device.
Irreversible payment is the goal
Gift cards, crypto, wires, cash drops — anything with no chargeback path.
Secrecy is the enabler
“Don’t tell anyone” is how scams survive contact with a skeptical friend.
Security & Safety
Physical Security
Digital defenses collapse if someone can watch, touch, or plug into your hardware. These are the low-tech attacks that still work, and the equally low-tech counters.
| Vector | How it works | Countermeasure |
|---|---|---|
| Shoulder surfing | Watching a PIN or passphrase being typed | Privacy screen filter, hand shielding, body-block the keypad |
| Card skimming | Overlay readers and hidden cameras on ATMs | Wiggle the card slot, cover the keypad, prefer contactless tap |
| USB dead drop | Found drives seeded with payloads | Never plug in media you did not acquire yourself |
| Juice jacking | Compromised public charging ports carrying data | Use your own wall adapter; USB-A ports supply power only |
| Unattended device | “Just grabbing a coffee” laptop left open | Lock on idle (Win+L / Ctrl+Cmd+Q), enable full-disk encryption |
| Document theft | Bins and hotel rooms raided for printed secrets | Shred anything with identifiers; carry documents you actually need |
Traveling? Carry a clean travel profile or spare device with no primary accounts logged in, enable a short auto-lock, use a PIN on the SIM, keep hardware keys on your person rather than in checked luggage, and assume hotel/venue Wi-Fi is hostile. Your threat model abroad is higher than at home — act like it.
Code Explained
The Scroll-Spy Engine
A documentation sidebar has one job: show where you are. This template does it with an IntersectionObserver that highlights whichever section occupies the active band of the viewport.
var sections = document.querySelectorAll('main section[id]');
var navLinks = Array.prototype.slice.call(document.querySelectorAll('.nav-link'));
var spy = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
var id = '#' + e.target.id;
navLinks.forEach(function (l) {
l.classList.toggle('active', l.getAttribute('href') === id);
});
}
});
}, { rootMargin: '-20% 0px -70% 0px', threshold: 0 });
sections.forEach(function (s) { spy.observe(s); });
Why the margin band works
rootMargin: '-20% 0px -70% 0px' shrinks the observation window so only a 10% strip of the viewport counts as “active”. Scroll fast through a long page and the callback fires a few times per frame at most — far cheaper than a scroll listener that recomputes getBoundingClientRect() for every element on every pixel.
observer
Browser runs it on its own schedule, coalescing dirty elements per frame.
toggle
One predicate, one class flip — no persistent “active id” state to desync.
extend
The same observer powers the On this page rail via syncToc(id).
Code Explained
Turnstile Flow, Line by Line
The widget on screen is only half of the protection. A real integration runs this loop — and it is short enough to hold in your head.
- 1The page loads
api.jsand the widget mounts with your public site key. - 2Cloudflare issues a challenge; for real humans this collapses to one click or nothing at all.
- 3The widget writes a one-time token into
cf-turnstile-responseand firestsVerified. - 4Your form submits; the Pages Function reads the token from the request body.
- 5The Function calls
POST /siteverifywith the secret — never the token alone, and never in the browser. - 6Only a
success: trueresponse lets the mutation proceed. Anything else returns403.
export async function onRequestPost(context) {
const { request, env } = context;
const token = (await request.formData())
.get('cf-turnstile-response'); // ← the widget's token
const res = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST', // server-to-server only
body: new URLSearchParams({
secret: env.TURNSTILE_SECRET, // from Pages → Variables
response: String(token)
})
});
const outcome = await res.json();
if (!outcome.success) return new Response('Forbidden', { status: 403 });
return Response.json({ ok: true });
}
Why the token must be verified server-side: the widget runs in the browser, so an attacker can read the token, replay it, or forge a page that skips the widget entirely. siteverify is the only place Cloudflare confirms “this token was really issued for this site key”. It also locks tokens to a single use, which is why step 4 has to happen before step 5 in the same request.
Code Explained
Command Palette Internals
Press Ctrl+K (or /) anywhere. The palette is a small search engine that runs in three phases.
Phase 1 — build the index once
At startup every section[id] becomes a record with three fields: its anchor, its data-title (falling back to the heading text), and its full lowercase textContent. Building it once at load — rather than scanning the DOM on every keystroke — keeps typing latency at zero even on the 3,000-line file.
var index = sections.map(function (s) {
return {
id: '#' + s.id,
title: s.getAttribute('data-title') ||
(s.querySelector('h1,h2') || {}).textContent || s.id,
text: (s.textContent || '').toLowerCase()
};
});
Phase 2 — filter, never rank blind
A substring match runs against both the title and the body text, so "phish" finds both the Phishing Defense section (by title) and every paragraph mentioning phishing (by body). Results are capped at 12 and re-rendered on every keystroke — the DOM is rebuilt from a fresh array, keeping the highlighted cursor row in sync.
input.addEventListener('keydown', function (e) {
if (e.key === 'ArrowDown') { e.preventDefault(); cursor = Math.min(cursor + 1, hits.length - 1); render(); }
else if (e.key === 'ArrowUp') { e.preventDefault(); cursor = Math.max(cursor - 1, 0); render(); }
else if (e.key === 'Enter' && hits[cursor]) { e.preventDefault(); jump(hits[cursor].id); }
else if (e.key === 'Escape') { closeSearch(); }
});
Phase 3 — jump and desync the state
jump(id) closes the modal, smooth-scrolls to the anchor, and rewrites the URL with history.replaceState so the hash is shareable without pushing duplicate history entries. Because the scroll-spy observes the same sections, the sidebar and the rail re-sync automatically as the page settles.
Extending the palette is a one-line change: swap the substring match for a scored fuzzy matcher (e.g., title.score weighted above body.score) and sort hits by score before slicing. The render loop does not care how the order was produced.
Cloudflare Edge
Turnstile Protection
Turnstile is Cloudflare's invisible challenge widget: it proves a visitor is likely human without puzzles or friction. This page mounts a real widget using site key 0x4AAAAAAFQ0KEp-nsdPDFaG — the challenge you see below is issued by Cloudflare, not simulated.
Waiting for the Cloudflare widget to render…
Bot Verification Managed — on success the widget writes cf-turnstile-response into this unit and the edge route middleware validates it against POST /siteverify before any mutation executes. The widget only renders on hostnames allowed for the site key — add your domain under Turnstile → Settings → Hostnames in the Cloudflare dashboard.
<div class="cf-turnstile"
data-sitekey="0x4AAAAAAFQ0KEp-nsdPDFaG"
data-theme="dark"
data-size="flexible"></div>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js"
async defer></script>
export async function onRequestPost(context) {
const { request, env } = context;
const token = (await request.formData())
.get('cf-turnstile-response');
const res = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
body: new URLSearchParams({
secret: env.TURNSTILE_SECRET,
response: String(token)
})
});
const outcome = await res.json();
if (!outcome.success) return new Response('Forbidden', { status: 403 });
return Response.json({ ok: true });
}
| Mode | Visitor experience | Best for |
|---|---|---|
| managed | Invisible; interactive challenge only when risk is elevated | Sign-in, signup, contact forms |
| non-interactive | Always renders, never requires a click | Low-friction read gates |
| invisible | No visible widget at all | Quiet spam filtering on comments |
Cloudflare Edge
Edge Middleware
On Cloudflare Pages, a _middleware.js at the route root runs before any asset is served. Use it to gate writes, attach security headers, and short-circuit known-bad traffic at the edge — before a single byte reaches your content.
const SECURITY_HEADERS = {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
};
export function onRequest(context) {
const { request, next } = context;
// Mutations must carry a verified Turnstile token
if (request.method !== 'GET' && !request.headers.get('x-turnstile-token')) {
return new Response('Bot token required', { status: 403 });
}
return next().then((res) => {
const headers = new Headers(res.headers);
for (const [k, v] of Object.entries(SECURITY_HEADERS)) headers.set(k, v);
return new Response(res.body, { status: res.status, headers });
});
}
Middleware order matters. Requests hit middleware top-down: functions/_middleware.js first, then nested route middleware, then the Function, then static assets. Keep validation cheap — a siteverify round-trip belongs on the POST route itself, not on every page view.
Cloudflare Edge
Security Headers
Headers are the cheapest security control you own. Add them as HTTP response headers in the Pages dashboard (Settings → Headers) or from middleware, then verify with the browser devtools Network tab.
| Header | Value | Blocks |
|---|---|---|
| Content-Security-Policy | default-src 'self'; script-src 'self' cdn.tailwindcss.com challenges.cloudflare.com | XSS, injected scripts, unwanted CDNs |
| Strict-Transport-Security | max-age=31536000; includeSubDomains; preload | SSL stripping, downgrade attacks |
| X-Content-Type-Options | nosniff | MIME-type confusion |
| X-Frame-Options | DENY | Clickjacking overlays |
| Referrer-Policy | strict-origin-when-cross-origin | URL leakage to third parties |
| Permissions-Policy | camera=(), microphone=(), geolocation=() | Hidden sensor access |
| Cross-Origin-Opener-Policy | same-origin | Spectre-style process attacks |
/
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
/*
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Cloudflare Edge
Caching & Purging
Pages serves assets from the edge with automatic cache headers. Hashed filenames can be cached forever; HTML should stay short-lived so a deploy propagates within seconds.
| Asset | Cache-Control | Rationale |
|---|---|---|
| app.a1b2c3.js | public, max-age=31536000, immutable | Content hash changes with the bytes |
| styles.css | public, max-age=31536000, immutable | Same — safe to pin for a year |
| index.html | public, max-age=0, must-revalidate | Entry point must observe new deploys fast |
| robots.txt / sitemap.xml | public, max-age=3600 | Crawlers re-check hourly without hammering |
Purge commands
$ npx wrangler pages deployment list
$ npx wrangler pages deployment rollback
$ npx wrangler pages project list
Rollback SLA
A rollback repoints production at the previous deployment instantly; edge caches converge as cached copies expire or are purged — typically under a minute worldwide.
API Reference
CLI Commands
The full command surface for driving this template from a terminal. Every command exits non-zero on failure, so they compose cleanly in CI steps.
| Command | Description | Exit codes |
|---|---|---|
| secstatus init <dir> | Scaffold index.html, _headers, and a manifest into a folder | 0 ok · 2 exists |
| secstatus dev --port | Local static server with live reload on file change | 0 ok · 1 port busy |
| secstatus check | Validates anchors, ids, aria labels, and orphan links | 0 clean · 3 issues |
| secstatus build --minify | Optional pass: inline CSS, strip comments, collapse whitespace | 0 ok · 4 parse error |
| secstatus deploy --env | Wraps wrangler pages deploy with defaults from secstatus.config | 0 ok · 5 auth |
| secstatus doctor | Prints Node, Wrangler, and account diagnostics for bug reports | 0 ok |
| secstatus --version | Prints the CLI and template version (currently 1.2.0) | 0 ok |
# .github/workflows/pages.yml
- name: Deploy to Pages
run: npx wrangler pages deploy docs-template \
--project-name=security-status \
--branch=main
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
{
"project": "security-status",
"outputDir": "docs-template",
"branch": "main",
"checks": {
"brokenAnchors": true,
"missingAlt": true,
"colorContrast": true
}
}
API Reference
Config Schema
Every tunable exposed by the template, with types, defaults, and the exact effect each one has at runtime.
| Key | Type | Default | Effect |
|---|---|---|---|
| theme.mode | "dark" | "light" | "dark" | Root class swapped on <html> |
| theme.accent | hex string | "#22d3ee" | Drives links, focus rings, active states |
| nav.rtl | boolean | false | Mirrors the sidebar and reading column |
| nav.collapsed | boolean | false | Start with groups folded on mobile |
| search.index | "inline" | "json" | "inline" | Reads section titles from the DOM or an external file |
| motion.enabled | boolean | true | Reveal-on-scroll and pulse animations |
| motion.respectPrefersReduced | boolean | true | Disables motion when the OS asks for it |
| turnStile.siteKey | string | "0x4AAAA…" | Public site key; the real widget renders immediately |
| footer.badge | boolean | true | Show the protection pill in the footer |
API Reference
JavaScript Utilities
The inline script exposes a small, namespaced API on window.Docs so you can drive the UI from the console or your own code.
Docs.go('#mfa') // offset-aware scroll jump
Docs.search('phishing') // open the palette prefilled
Docs.highlight('#headers') // force sidebar active state
Docs.progress() // → 0.42 (42% read)
| Method | Signature | Returns |
|---|---|---|
| go | (selector: string) => void | Scrolls with the sticky-header offset applied |
| search | (query?: string) => void | Opens the command palette, optionally prefilled |
| highlight | (selector: string) => void | Marks a sidebar link active immediately |
| progress | () => number | Read progress of the main column from 0 to 1 |
| sections | () => {id, title}[] | Every indexed section, in document order |
Try it right now. Open devtools on this page and type Docs.go('#faq'). The palette, scroll-spy, and progress bar all read from the same public surface.
Live Console
Visitors Counter
A real, edge-counted hit counter. Every page view increments a Cloudflare KV counter through a Pages Function, so the number below is live — not saved in your browser and not generated with localStorage.
All-time views
live—
GET /api/visitors · KV-backed · edge increments
How it works
- 1 Each page load calls
/api/visitors— a Cloudflare Pages Function running on the edge. - 2 The function reads the current value from Cloudflare KV, adds one, and writes it back.
- 3 The page renders the JSON it receives. If the API is unreachable you see a dash — never a fake number.
$ curl -s https://security-status.pages.dev/api/visitors
{ "count": 1042, "updated": "2026-10-08T00:00:00Z" }
Honest counter. The counter increments on every request that hits /api/visitors — including this page's own load. It counts views, not unique people. That is the honest difference between this and a decorative "since 2026" badge.
Live Console
Terminal — /machine
A real command shell rooted at /terminal/machine. Type help to list commands. Several commands hit the live edge — the same latency probe, security headers, and /api/visitors Function this site already uses — so their output is real, not scripted.
machine:0x7F3A — ss@edge — pts/0
Live at the edge
status, visitors, dns, and scan read live data — the same resources the rest of this site uses.
Shell behavior
History via ↑ / ↓, clear, a tiny /etc file tree — and honest answers when a command has nothing real to report.
Read-only
Nothing here can change your files, account, or settings — a scoped console that is document-safe by design.
Resources
Changelog
Release history for the template. Semantic versioning: patch for content fixes, minor for new sections and components, major for layout or config breaking changes.
Rebrand, live console, per-page URLs
- • Site renamed Defender Status → Security Status with a cyan/teal “security-operations” UI re-skin (Chakra Petch display type)
- • New
/terminal/machine— a real command shell with live edge commands (status, scan, dns, visitors) - • New
/visitors-count— edge visitor counter backed by Cloudflare KV - • Every docs section now has its own clean URL (
/terminal/machine,/visitors-count,/quickstart…) with SPA routing - • Prev/next page footer, Google site verification meta, security
_headers
Real Turnstile + safety docs expansion
- • Live Cloudflare Turnstile widget mounted with the real public site key and full callback wiring
- • Ten new guide sections — device support, SEO, mobile safety, safe downloads, scam playbook, social privacy, physical security, and three code deep dives
- • Head refactor: keyword meta, TechArticle + FAQPage JSON-LD
- • “On this page” right rail, announcement bar, back-to-top, skip link
- • Real status ping measured over RTT; single sidebar toggle (FAB removed)
- • Full sitemap footer and contrast pass (Lighthouse accessibility 0.95 → 1.00)
Initial public release
- • Dual-column layout with sticky scroll-spy sidebar and mobile drawer
- • Command palette search with keyboard navigation (Ctrl/⌘+K, /)
- • Security, edge, and API documentation chapters — 29 sections
- • Turnstile protection pill with verification demo
Patch — regression fixes
- • Clipboard fallback restored for insecure origins
- • Sidebar offset alignment on short viewports
- • Search palette empty-state wording scoped to real sections
Beta hardening pass
- • Added reduced-motion support and focus-visible states
- • Hand-rolled syntax token palette replaces the highlighting lib
- • Header read-progress bar and status latency display
Internal skeleton
- • Glassmorphic surface system and token documentation
- • Responsive drawer below the lg breakpoint
Resources
Troubleshooting
The failures people actually hit, and the fix for each. Work top to bottom — the most common causes are listed first.
| Symptom | Likely cause | Fix |
|---|---|---|
| Page renders unstyled | Tailwind CDN blocked by ad-blocker or offline | Self-host the CDN script, or precompile utilities for offline use |
| Sidebar highlight lags behind | Section missing scroll-mt-24 | Add the offset class so the observer thresholds align |
| Deploy 404s on every page | Uploaded the parent folder instead of the output dir | Redeploy with the folder that contains index.html at its root |
| wrangler: account not found | Stale or missing OAuth token | Run npx wrangler logout then npx wrangler login |
| Turnstile always fails | Test keys submitted to siteverify | Use the always-passing test keys only in local development |
| Fonts fall back to system sans | Google Fonts blocked by CSP or region | Allow fonts.gstatic.com in CSP, or self-host WOFF2 files |
| Copy button shows nothing copied | Clipboard API denied on insecure origin | Serve over HTTPS or localhost; the script falls back to execCommand |
| Blurry glass on old Safari | Missing -webkit-backdrop-filter prefix | Already included in this file — check for CSS minifier stripping it |
Still stuck? Run npx wrangler whoami and npx wrangler pages project list to confirm your session, then include the full command output plus your region in the bug report — it resolves nine out of ten deployment tickets on the first reply.
Resources
Frequently Asked Questions
Short answers to the questions that come up most often in issues and support threads.
Is this really production-ready as a single file?
Yes. Static hosts serve it directly with no build phase, which removes an entire class of pipeline failures. The only trade-off versus a compiled setup is that utility classes are generated in the browser; if you need a guaranteed CSP without a CDN, precompile the classes once and inline the result.
Can I split this into multiple pages?
Duplicate the file per route (for example docs/index.html, security/index.html), keep the header and sidebar markup identical, and change the data-title values so the palette indexes each page correctly.
How do I get a real Turnstile key?
Cloudflare dashboard → your zone → Turnstile → Add site. Copy the public site key into the widget mount and store the secret key in Pages environment variables as TURNSTILE_SECRET — never in HTML.
Will this work with a custom domain?
Add it under Pages → Custom domains. Cloudflare provisions the certificate automatically when your DNS already points at the account; otherwise it gives you the exact CNAME record to create.
How do I keep my Cloudflare account itself safe?
Enable 2FA with a passkey or hardware key, use scoped API tokens instead of the global key, set token expiry dates, review audit logs monthly, and treat any email claiming a zone problem as phishing until you have verified it by logging in directly.
What about SEO and social previews?
The head already ships title, description, Open Graph, and Twitter card tags. Add a real og:image URL once your domain is final, generate a sitemap.xml, and submit it in Search Console.
Security & Safety
Social Media Privacy
Every post is a data source for someone else: social engineers, stalkers, and data brokers. You do not have to quit these platforms — you have to decide what each audience is allowed to know.
What oversharing leaks
The quarterly cleanup
Separate identities by purpose. Use a dedicated email for sign-ups, a different one for finance, and an alias for newsletters. When one address leaks — and they do — the blast radius is one slice of your life, not all of it. A password manager makes this effortless to maintain.