
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
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.