Go to Home Page

what this site knows

This page was written by reading the source of this site, not by filling in a template. Everything below names the file it came from, so you can check any claim on it against the code rather than taking my word for it.

The short version

  • I sell nothing, share nothing with advertisers, and run no third-party profiling of any kind.
  • Analytics are off until you switch them on, and nothing is even downloaded before you do.
  • The only cookies here are the guestbook sign-in, set if you choose to sign in and not otherwise.
  • Your settings, drafts and dismissed hints stay on your own device and are never sent to me.
  • What you choose to send leaves your device: a guestbook entry is stored, and a contact message is delivered to my inbox (one written offline waits on your device until you are back online). The guestbook page also sends a random per-tab ID to count who is here now. What is kept, and for how long.

What is measured, if you allow it

Three scripts, treated as one decision because they answer one question between them: which pages are worth keeping. If you have not agreed, none of them is loaded — not loaded and idle, not loaded and denied. Your browser makes no request to any of them at all.

Vercel Web Analytics

Processor
Vercel Inc.
Collects
Page path, referrer, country-level location, device class. Cookieless — no identifier is stored on your device.
Source
src/components/privacy/ConsentedAnalytics.jsx

Vercel Speed Insights

Processor
Vercel Inc.
Collects
Core Web Vitals samples (how fast a page painted, how much it shifted, how quickly it answered your first tap) attached to a route, not a person.
Source
src/components/privacy/ConsentedAnalytics.jsx

Google Analytics 4

Processor
Google LLC
Collects
Cookieless pings under Consent Mode v2 with all four consent signals denied: page path, referrer, coarse geography, device class, and a fixed list of interaction events. Because nothing is stored on your device, there is no identifier tying two visits together — which costs me the ability to count returning readers, and is the right trade.
Source
src/components/analytics/GoogleAnalytics.jsx

Cookies

Five, all from the guestbook sign-in, and none of them set unless you choose to sign in. None is an analytics or advertising cookie, and none needs your consent — each is strictly necessary for the feature you deliberately asked for. They are listed anyway, because a disclosure you only make when legally forced to is not much of a disclosure.

authjs.session-token

Set when
You sign in to the guestbook
Purpose
Holds your session as an encrypted JSON Web Token. It carries your display name, avatar URL and an account identifier, and it is what lets the wall know an entry is yours.
Lifetime
Until it expires or you sign out

authjs.csrf-token

Set when
You sign in to the guestbook
Purpose
Stops another site from making an authentication request in your name (cross-site request forgery).
Lifetime
Session

authjs.callback-url

Set when
You sign in to the guestbook
Purpose
Remembers which page to return you to after the sign-in round-trip.
Lifetime
Session

authjs.pkce.code_verifier, authjs.state

Set when
During the sign-in handshake only
Purpose
The PKCE verifier and state parameter that make the OAuth exchange tamper-evident. Both are consumed the moment you come back.
Lifetime
A few minutes

Kept on your device

Written to your browser's own storage and read back by your browser, and none of it identifies you — it is how the site remembers that you have been here before. Two items do leave it: a contact message sent while offline, delivered once the network returns, and the guestbook page's random per-tab ID, sent to count who is here now. Both are in section 05. Named individually rather than summarised, so you can match any of them against your own devtools.

Your privacy choice

ma:consent:v1until you clear it
Your answer to the privacy notice, and when you gave it. This one exists so the site stops asking.

Contact form

contact:draft:v1until you clear it
What you have typed into the contact form so far, so a refresh or a mis-tap does not lose it.
contact:queue:v1until you clear it
A message you sent while offline, held on your device until the network comes back and it can actually be delivered.

Guestbook

guestbook:prefsuntil you clear it
Your guestbook sound and motion switches.
guestbook:draft:v2:<account>until you clear it
An unsent guestbook message, in a slot named after the account that typed it — so signing out cannot hand your draft to the next person on this browser.
guestbook:draft:v1until you clear it
The retired single-slot draft. It is never read; the composer deletes it on sight, for the reason above.
guestbook:presence-idthis tab only
The random tab identifier behind the "here now" counter. It is per-tab and disappears when you close it.

Interface state

loaderSeenuntil you clear it
Whether you have seen the intro animation, so it plays once.
nav-orbit-tip:dismisseduntil you clear it
Whether you dismissed the navigation hint, so it stays dismissed.
projects-category-handoffthis tab only
Which project category you tapped, carried across one navigation and then deleted.
projects-categoryuntil you clear it
The retired version of the setting above, which used to persist indefinitely. Nothing writes it any more and nothing reads it — the site deletes it if it finds it, so a visitor from before the change is tidied up rather than left carrying it forever.

What's changed since your last visit

project-count:last-seenuntil you clear it
How many projects existed last time you were here, so the site can say what is new rather than just how much there is.
project-progress:lastGooduntil you clear it
The last project-progress figures that loaded cleanly, so a slow request shows the previous numbers instead of nothing.
streak-update:last-seenuntil you clear it
The commit streak as you last saw it, to work out the difference.
skills-update:last-seenuntil you clear it
The skills list as you last saw it, to work out the difference.
languages-update:last-seenuntil you clear it
The language breakdown as you last saw it, to work out the difference.
experience-update-banner:lastPlayedthis tab only
Which update message this tab has already shown you, so it is not repeated on every navigation.

Cached data

skillsCache:v4until you clear it
A copy of the skills data from my GitHub, so the about page renders immediately instead of waiting on an API.
skillsLastFetched:v4until you clear it
When that copy was taken, so it knows when to refresh it.
github-stats:lastGood:<account>until you clear it
The last good copy of my GitHub statistics, so the about page’s cards render populated on a cold load instead of empty while the API answers.
experience-summary:last-payload:<account>until you clear it
The last good copy of my experience figures, for the same reason — and so the page can tell you what has changed since you were last here.

You can delete all of it at any time from your browser’s site settings, and I could not reach it if I wanted to. This list is checked against the code by a test that fails the build if a new key is added without appearing here, which is why it is worth trusting.

Who else is involved

Each of these receives something because of an action you took, so none of them is behind a toggle — a switch that could refuse the contact form its own mail delivery would be theatre. What is owed here is telling you plainly, before you act.

Vercel

Hosting and delivery

What happens
Serves every page. Like any web server it processes your IP address and user agent to answer the request; those are handled by Vercel under its own terms and I neither see nor store them.
Triggered by
Always — it is how the page reaches you

Vercel AI Gateway

Message refinement

What happens
If you press Refine on the contact form, the text you have typed is sent to a language model to be rewritten and sent straight back. This is the one place your own words leave this origin without you pressing Send, which is why the button says what it does before you press it and why this row exists. Nothing is stored: the request is answered and gone.
Triggered by
Only when you press Refine
Source
src/app/api/refine-message/route.js

GitHub / Google

Sign-in

What happens
If you sign the guestbook, you authenticate with one of them and it returns your display name, avatar URL and an account identifier. I never receive your password, and I request nothing beyond your public profile.
Triggered by
Only when you choose to sign in
Source
src/auth.js

Upstash (Redis)

Data store

What happens
Holds guestbook entries, the live "here now" counter and the rate-limit counters that keep the wall usable.
Triggered by
When you post, react, or view the guestbook
Source
src/lib/guestbook/redisDriver.js

A mail provider, over SMTP

Contact form delivery

What happens
A submitted contact form is delivered to my inbox as an email. The name, address and message you typed travel in it, because that is what sending a message means.
Triggered by
Only when you press Send
Source
src/app/api/send-mail/route.js

Abstract API

Contact address check

What happens
Before a contact message is accepted, the email address you entered is sent to Abstract’s Email Reputation service, which answers whether it can receive mail and whether it is a disposable address. Only the address goes, not your name or message, and the request is made by my server rather than your browser. The answer decides whether the form goes through, and nothing from it is kept. The check runs only while the site is configured with an Abstract key; without one, your address is not sent anywhere for this.
Triggered by
Only when you press Send, and only if the check is enabled
Source
src/app/api/send-mail/route.js

What exists on a server afterwards

For most visits the answer is nothing with your name on it. Where something is kept, this says what it contains and for how long — an actual interval where there is one, and the word indefinitely where there is not.

Your guestbook entry

Contains
Display name, GitHub username (GitHub sign-ins only), avatar URL, an account identifier, your message, an optional drawn signature, and the time you posted.
Kept for
Indefinitely — it is a public wall and the point is that it persists. Email me and I will remove yours.
Who sees it
Public. The account identifier is the one field that is never returned to anyone, including you.
Source
src/app/api/guestbook/route.js

Live presence

Contains
A random identifier your browser mints for the tab, with no connection to your account or your address.
Kept for
About two minutes, then the key expires on its own.
Who sees it
Aggregated into a single number: how many people are here now.
Source
src/lib/guestbook/presence.js

Rate-limit counters

Contains
Your IP address, SHA-256 hashed. The hash is the key; the address itself is never written down.
Kept for
Minutes — each counter carries a short expiry.
Who sees it
Nobody. It exists to stop one person flooding the wall.
Source
src/lib/guestbook/ratelimit.js

Refine throttle

Contains
Your IP address, in memory only, in the server instance that handled the request.
Kept for
Not stored at all. It lives in a variable and disappears when the instance does.
Who sees it
Nobody.
Source
src/app/api/refine-message/route.js

A contact message

Contains
The name, email address and message you submitted.
Kept for
It is an email in my inbox. It stays until I delete it, like any other email you would send me.
Who sees it
Me.
Source
src/app/api/send-mail/route.js

Things that look like tracking and are not

A footer that names a town and a widget that names a song look exactly like the output of surveillance. They are the opposite: all four are data about me, identical for every visitor, and none of them reads anything about you.

The town in the footer

What it is
My location, from a tracker on my own phone, rounded before it is stored and only ever shown as a town. It is not derived from your address and does not change based on who is reading.
Source
src/app/api/location/route.js

The now-playing widget

What it is
What I am listening to, read from my own Spotify account. The endpoint returns a title, an artist and some artwork; the credentials never reach your browser.
Source
src/app/api/spotify/route.js

The live build and repository figures

What it is
Public data about this repository, from the GitHub API. Cached server-side and identical for every visitor.
Source
src/app/api/github-stats/route.js

My CV, deliberately indexed

What it is
The CV at /cv is published and indexed on purpose, so it works as a landing page for my name, and its PDF is public beside it (search engines are pointed at /cv rather than the PDF). Both carry my email address and the PDF also carries my mobile number, which means both are reachable by scrapers as well as by recruiters. That is a cost I accepted knowingly rather than one I overlooked — and it is my number, not yours.
Source
src/lib/seo/site.js

What you can ask me to do

Written as what I will actually do, rather than as a recital of the statute. A recital cannot be checked; a promise can.

See what I hold about you
For almost everyone the answer is nothing — a visit that signs nothing and sends nothing leaves no record with your name on it. If you signed the guestbook or emailed me, ask and I will tell you exactly what is there.
Have it deleted
Email me and your guestbook entry goes. Device-local storage is yours to clear at any time from your browser settings, and I could not reach it if I wanted to.
Withdraw analytics consent
Change it from the Privacy choices link in the footer of any page. It takes effect immediately — the scripts are unloaded on the spot, not at the next page load.
Correct something
Your guestbook name and avatar come from GitHub or Google and update when you next sign in. Anything else, email me.
Complain
I am in the UK, so the Information Commissioner’s Office is the regulator — ico.org.uk. I would rather you told me first.

Asking me anything

I am the only person who handles any of this — there is no team, no data-protection officer and no ticket queue. Email muhammad.abdullah33176444@gmail.com and it reaches me directly. I am based in Bolton, Greater Manchester, which makes the UK Information Commissioner’s Office the regulator if you would rather go to them — though I would much rather you came to me first.

Last materially changed 27 September 2026. That date moves when what you are agreeing to changes, not when a sentence is reworded — and a material change re-asks you, because the version of this notice you answered is stored alongside your answer.

MUHAMMADABDULLAH
0%