SY · Privacy

Privacy / plain-English notice

Updated2026-04-19
Retention90 days
GPCHonored

This is a personal site, a place where I write and keep my work in the open. There's nothing to buy here, no ads to dodge, and no account for you to create. Still, every page that loads in a browser leaves some trace behind somewhere; this page is the honest accounting of exactly what trace this one leaves, why I look at it, and how to wipe it.

Part 1 · Collection
What I collect

When you open a page on this site, a small script in the page makes a single request to my own server and tells it the following:

  • Which page you opened (for example /writing/slow-tools), not the URL fragments or query strings.
  • How far you scrolled, rounded into four buckets: 25% / 50% / 75% / 100%. I can see you finished the post; I can't see where your eyes paused.
  • Roughly how long you stayed, bucketed into bands like "under 30 seconds" or "1 to 2 minutes." Never the precise milliseconds.
  • What kind of device you're on: mobile, tablet, or desktop. That's the granularity; not the model, not the OS version.
  • Which category your referrer falls into (search, social, aggregator, direct, or other). Not the exact site. So I'll know "someone came from a search engine," not "someone came from Google with this query."
  • Your country, which Cloudflare derives at the edge from your connection. No city, no IP stored.
  • Whether you clicked any internal links, and which ones, capped at 50 per visit so a stuck button can't fill my database.
  • Which external sites you followed me out to. Helpful for knowing which references readers actually pursue.

That's everything. Each visit lands in my database as a single row, stripped of anything that could link it to another row. No identifier, no session ID, no way for me to tell that "the person who read this article last week" is the same person who's reading this page now. I designed it that way on purpose. I'd rather count silhouettes than follow faces.

Pageview payload · technical fields

For auditors, researchers, or anyone with a healthy suspicion of privacy claims that don't show their work, here is the exact schema of what the client sends. Sixteen fields, no others.

Field Description
path The URL path of the page viewed.
ref_host Referrer hostname, used to derive the referrer category. The raw host is not stored.
viewport Device class: mobile, tablet, or desktop.
scroll_max_bucket Maximum scroll depth rounded to 0/25/50/75/100, or null if the page didn't scroll.
time_on_page_ms Raw millisecond count at send; bucketed server-side before storage, never stored raw.
had_rage_click Boolean. True when three rapid clicks on the same element occurred anywhere on the page.
rage_target_kind Coarse category of the rage-click target: link, button, form, media, or other. Null when no rage-click happened.
internal_clicks Array of same-origin link destinations (paths only, no query), capped at 50 per pageview.
outbound_click_hosts Array of external hostnames clicked through to, capped at 50 per pageview.
audio_play Present only when the article audio player was used. Contains: voice selected (voice_id), playback speed (playback_rate), listening duration bucketed to the same fixed steps as time_on_page_ms (listen_s_bucket), max percent of audio reached rounded to 0/25/50/75/100 (percent_bucket), and whether playback reached the end (ended). No exact position or timestamp is recorded.
visible_ms Time the page was in the foreground (tab active, not backgrounded), in milliseconds. Bucketed server-side to the same fixed steps as time_on_page_ms before storage — never stored raw. Separates true reading time from tab-open-and-forgotten time.
had_copy_event Boolean. True when the reader copied any text from the page. No content, selection range, or position is recorded — only that a copy occurred.
color_scheme dark or light, from the prefers-color-scheme media query. Reflects the OS-level or browser-level setting at the time of the visit.
reduced_motion Boolean. True when prefers-reduced-motion: reduce is active. Reflects the OS accessibility setting for motion sensitivity.
pointer_type coarse (touchscreen) or fine (mouse or trackpad), from the pointer media query. More precise than viewport class for interaction design decisions.
release Short Git commit SHA of the site build that served the page, also visible to anyone in the page source as a build meta tag. Lets a measurement be attributed to the exact deploy that produced it. A public build identifier, not data about you.

Before any of this hits disk, the server runs through a small ritual of forgetting: the timestamp is floored to the nearest minute, the raw ref_host is replaced with one of five categories, time_on_page_ms is snapped to a fixed bucket (5/10/30/60/120/300/600/1800/3600 seconds), and any request that carries Sec-GPC: 1 is dropped on the floor before it touches the database at all.

Security violation reports

Your browser may also send me a Content Security Policy (CSP) violation report when something on the page tries to do something my policy doesn't allow. The usual culprits are a browser extension trying to inject a script, or (more honestly) me shipping code that breaks my own rules. The reports help me catch the second before too many readers see a half-broken page, and the first before it starts doing anything worse than inconveniencing me.

A report carries the following, and nothing else:

  • The path of the page where the browser threw the report. Query strings stripped, same rule as the pageview log.
  • Which CSP directive did the blocking, say script-src or img-src. Useful for me to spot which rule is misbehaving.
  • The origin of the blocked resource: scheme plus host, nothing more. Not the full URL, not any query, not your session.
  • The path of the file that triggered it. No line numbers, no column numbers, just enough to find the file in my repo.

No IP. No user-agent string. No identifier of any kind. Stored in the same database with the same 90-day clock as the pageview data, and honoring GPC the same way: if your browser sends the signal, the report is dropped before it lands.

What I don't collect
  • No cookies. This site never sets one.
  • No IP addresses stored in my analytics database. Cloudflare sees yours at the edge, like every host on the public internet does; I don't keep it.
  • No user IDs, fingerprints, or session identifiers. There's nothing to "log in" to, so nothing to identify you across visits.
  • No form data, no keystrokes, no mouse-movement recordings. (You'd be surprised how many sites do; session replay tooling is everywhere.)
  • No third-party trackers. No Google Analytics, no Meta Pixel, no Hotjar, no anything. The site loads exactly the scripts I wrote, nothing else.
  • No exact timestamps. Visit times are floored to the nearest minute before being written to disk.
Local preferences

The site keeps a handful of your reading preferences in your browser's own localStorage, so the page comes back looking the way you left it. These never leave your machine; my server never sees them. Clearing site data wipes them clean. The keys are:

  • obs-theme, whether you prefer light or dark mode.
  • obs-font, which reading font you picked (the default Instrument Sans, OpenDyslexic, or Atkinson Hyperlegible).
  • obs-reading-size, small, regular, or large text size.
  • obs-leading, your line-spacing preference: tight, comfortable, or loose.

All four are set via the accessibility page.

Part 2 · Reasons & Law
Why

I want to know whether my writing is finding readers and whether the site's design is working. Whether someone bailed out two paragraphs into a long essay, or read it to the end. Whether the navigation surfaces what I think it does. Whether the mobile layout actually holds up at 360px wide, or quietly breaks for half my visitors. The analytics let me ask those questions; that's the whole reason they exist on this site.

Processors

The site itself runs on Cloudflare Pages, and the analytics database is Cloudflare D1. Both are operated by Cloudflare, Inc. (US). Cloudflare sees the connection metadata that flows through any web request, IP addresses included, and uses it at the edge for routing, security, and the kind of DDoS protection that keeps a one-person site online during a Hacker News weekend. They handle this under their standard Data Processing Addendum; EU-to-US transfers are covered by their Standard Contractual Clauses. There are no other processors. No analytics vendors, no marketing platforms, no third hands on the data.

Retention

Rows in my analytics database age out after 90 days and are deleted by a scheduled job. Long enough for me to spot quarterly trends, short enough that the data never feels archival. Cloudflare's own infrastructure logs follow their policy and are typically gone in under thirty days.

Part 3 · Your Rights
Your choices
  • Global Privacy Control
    If your browser sends a Sec-GPC: 1 signal (Firefox, Brave, the DuckDuckGo browser, and a few others do this by default), your visit doesn't get recorded at all. The server drops the request on the welcome mat, before the database knows it happened.
  • Block the script
    uBlock Origin, Firefox Enhanced Tracking Protection, or any content blocker that catches /api/tx on this domain will stop the beacon from firing in the first place. I use uBlock myself; I tested that this site keeps working cleanly with it enabled.
  • Email me
    Drop me a note at the address at the bottom of this page and I'll exclude your traffic going forward by country or referrer category. I can't exclude you individually (there's nothing on the row that identifies you as you), but a coarse exclusion is something I can honor.
GDPR rights

Under GDPR you have rights to access, correct, erase, restrict, or object to the processing of your personal data, and to file a complaint with your local data-protection authority. The honest catch on this site: because I don't store any identifier that links rows to you, I can't actually look up "your data". There's nothing on my end with your name on it to hand back or to delete. The rights haven't gone anywhere; the data they apply to simply isn't here. If you want to stop me counting your visits going forward, see Your choices above.

Changes

If I change what's collected, this page changes with it and I bump the Last updated date at the top. Anything substantive (a new field, a different retention window, a new processor) gets called out in the site footer for thirty days so a returning reader actually has a chance to notice.

Contact

Questions, corrections, or a polite "you missed something"? I'm at hello@animeshdesigns.com. I read everything that lands there.