Legal

Cookies and browser storage

Everything this site keeps in your browser, what each entry is for, and how to change your choice.

The short version

What is actually set

Most of what this site keeps in your browser is not a cookie at all — it is localStorage, and in a couple of cases a database the sign-in library opens for itself. The distinction matters less than what each entry holds, so the tables below list every one of them by name.

Google Analytics sits behind the Analytics choice on the banner, and nothing else does. Turn that category on and the analytics script is fetched and two cookies are set — _ga, and one whose name begins _ga_, both described in the tables below. Leave it off, or never answer the banner, and the script is never requested, Google is never contacted and neither cookie exists. Nothing is loaded before you agree, and turning the category back off stops collection straight away.

No advertising tag of any kind ships on this site. There is no retargeting tag, no advertising pixel and no third-party ad-tech, so the Marketing choice on the banner still gates nothing — saying so is more useful than implying a control that is doing work it is not.

Necessary

Entries the site cannot work without

These are set because the site would not function without them. Under PECR they do not require consent, which is why the banner does not offer them as a choice — a switch that cannot be switched is not a choice, and presenting one is the pattern that teaches people to stop reading banners.

Browser storage entries used by this site
NameKindWhat it holdsHow longIf you clear it
rey.admin-rail-collapsed.v1localStorageThe single character "1" or "0" — whether the back-office navigation rail is collapsed to its icon width. Nothing else: no identifier, no account, no order, nothing about a person.Until cleared. Written only when the collapse control is actually used.The rail opens at its full width on the next load, and can be collapsed again. No other effect. A visitor who has never opened the back office never has this key at all.
rey.admin-catalogue-collapsed.v1localStorageA pipe-separated list of catalogue CATEGORY IDS whose section the admin has collapsed on the back-office catalogue board. Category ids only: no identifier, no account, no price, no order, nothing about a person or about what anyone bought. NO EXAMPLE VALUE IS GIVEN HERE, AND THAT IS DELIBERATE. This row first carried a worked example of two real category ids, and `npm run check:claims` blocked the commit on it: some of this catalogue's ids are outcome words, and the scoped waiver that permits them (operator, 2026-08-28, decision D5) covers the catalogue data files and the ids themselves — NOT this file. The gate was right. A narrow waiver stays narrow by refusing exactly this kind of convenient spill into a neighbouring file.Until cleared. The key is REMOVED, not written empty, when the last collapsed section is reopened — so an admin who collapses nothing never accumulates a key.Every section opens on the next load and can be collapsed again. No other effect. A visitor who has never opened the back office never has this key at all.
rey.admin-enquiry-draft.v1localStorageAn in-progress reply an admin is drafting on the contact centre, one entry per thread — the message text and when it was last saved. Nothing else: no customer address, no order, no identifier for anyone other than which internal enquiry id the draft belongs to. An admin's own draft text can of course be about a customer, in the same way an email you have not sent yet is; this key does not add anything beyond what the admin typed.Until the thread's draft is cleared (send it, or type the box back to empty) or it ages out — an entry older than 30 days is dropped the next time the store is touched, and at most 50 threads' drafts are kept at once, oldest dropped first past that.Any in-progress, unsent replies on this admin's browser are lost; nothing sent is affected, because a sent reply is never read from this key. The composer opens empty on the next visit to each thread, exactly as it did before this feature existed.
firebaseLocalStorageDbindexedDBThe signed-in session (tokens, refresh state) when someone IS signed in. Opened empty the moment the page loads, on every route — it exists even for a visitor who has never seen a sign-in form.SDK-managed. Populated on sign-in, cleared on sign-out; the empty database itself persists across visits once created.Signs the browser out of any Firebase session, the same as using a private window. No effect on a visitor who was never signed in.
firebase-heartbeat-databaseindexedDBThe SDK's own record of which Firebase products/versions this page has loaded.SDK-managed.No visible effect on the site.
rey.ruo-acknowledged.v1localStorageThe literal string "yes" once the research-use + age notice has been cleared on this device — by dismissing the modal with "I have read this and I am 18 or over" (default mode) or by submitting a date of birth that clears the floor (where this site asks for a date of birth instead of a simple acknowledgement).Until cleared, OR until the companion RUO_ACKNOWLEDGED_AT_STORAGE_KEY timestamp is more than 180 days old, whichever comes first — see lib/ruo-acknowledgement.ts. A "yes" whose companion timestamp has expired, is missing, or cannot be read counts the same as this key being absent.The notice or gate shows again on the next page load. Nothing else is affected.
rey.ruo-acknowledged-at.v1localStorageThe browser clock, in milliseconds since 1970, at the moment the research-use + age notice/gate above was cleared. Written every time RUO_ACKNOWLEDGED_STORAGE_KEY is written, at the same moment. No personal data — a number.Until cleared, or overwritten the next time the notice/gate is cleared again.RUO_ACKNOWLEDGED_STORAGE_KEY (above) counts as expired even if it is still present, and the notice/gate shows again on the next page load.
rey.ruo-date-of-birth.v1sessionStorageYour date of birth, as a plain date — for example "1990-04-23" — but ONLY if the date you entered at the entry gate passed the age check above. A date that does not pass is never written here at all.Until this browser TAB is closed. Unlike every other row on this page, this one uses sessionStorage rather than localStorage, so it does not survive closing the tab, reopening the site in a new tab, or restarting the browser.Checkout asks for your date of birth again, exactly as it would if you had never cleared the entry gate. Nothing else is affected — the entry gate itself does not reopen.
rey.ruo-consent-claim.v1localStorageA record of exactly which wording of the research-use and age notice was on screen when you confirmed it — which notice it was, its version, and a fingerprint of its exact text. It holds no personal data: the fingerprint is of our own wording, never of anything you typed.Until cleared, or until confirmed again (each confirmation overwrites it).No functional loss. A form submitted afterwards simply carries no consent claim — the server accepts or refuses on its own rules either way.
rey.cart.v1localStorageA list of the item codes in your basket and how many of each. No price, no product name, no line total.Until cleared or emptied. No expiry is set.The basket is empty on the next visit. Nothing else is affected.
rey.account.remember.v1localStorageThe literal string "1" or "0" — whether the person ticked "keep me signed in" the last time they signed in on this device. Nothing else: no address, no token, no name.Until the browser is told to clear site data. Overwritten on each sign-in.The tick-box returns to its default (ticked). No effect on whether you are signed in right now.
rey.referral-code.v1cookieThe referral code this browser arrived with, exactly as the SERVER normalised it - never the raw query string. Nothing else: no ambassador id, no rate, no amount, no identifier for the person browsing.The cookie carries its own expiry, set from the attribution window. That expiry is the only thing that ends it — we keep no second copy of the date that could disagree with it.The browser is no longer attributed to anybody. Orders placed after that are ordinary unattributed orders; nothing else changes, and no page behaves differently.

Analytics

Entries set only if you turn Analytics on

These are the opposite of the table above: they are not necessary, they require your consent, and they are set only after you turn the Analytics category on. Leave it off, or never answer the banner, and none of them exists. Turning the category back off stops collection and you can clear what is already stored with your browser's own controls.

Browser storage entries used by this site
NameKindWhat it holdsHow longIf you clear it
_gacookieA randomly generated number identifying this browser to Google Analytics, and the time it was first generated. A second cookie is set alongside it whose name begins “_ga_”, holding the same kind of value for this site’s own analytics property. Neither holds your name, your email address, your delivery address or anything you typed into a form.Two years, set by Google’s own SDK, not by this site. Turning the Analytics category off stops collection immediately; clearing your cookies removes the identifiers.Nothing on the site changes and no price moves. Your next visit is counted as a new one rather than a returning one.

Your choice

The record of what you chose

Answering the banner writes these two entries. The first is your decision; the second is a random identifier whose only job is to let a reload, a double render and a change of mind collapse into one row rather than three. It is not joined to an account and it is not sent anywhere else.

Browser storage entries used by this site
NameKindWhat it holdsHow longIf you clear it
rey.cookie-consent.v1localStorageA record of which optional cookie categories this browser accepted, when the choice was made by the browser clock, and which version of the banner asked. No personal data: two booleans, a timestamp and a number.Until cleared, or until COOKIE_CONSENT_VERSION is bumped (which re-asks everyone).The banner appears again on the next page load and every optional category is counted as refused until it is answered.
rey.cookie-anon-id.v1localStorageA random UUID minted in this browser the first time a cookie choice is saved. It is not derived from anything about the visitor and is never joined to an account, an order or an email address.Until cleared. No expiry is set.A new id is minted on the next saved choice. Previously recorded audit rows can no longer be tied to this browser, including by a data-subject request.

Cookie choices and the record we keep of them

When you answer the cookie banner, your choice is stored in this browser and a record of it is also written to our own database.

That record holds the two choices you made, a random identifier created in your browser (not linked to any account), a one-way hash of your IP address, and your browser's User-Agent string. Your IP address itself is not stored.

The record exists so that we can show, if we are asked, what was agreed and when. It is not used to identify you, to profile you, or to advertise to you.

How long we keep it: 24 months from the date you made the choice, after which we ask again rather than rely on an old answer.

Who else receives it: nobody outside the services that run this site — Google Cloud and Firebase, which host the site and hold the database in London (europe-west2), Google Workspace for our own mail, and Resend, which sends order and account emails. It is not shared with anyone else, and it is never sold.

Change your mind

Asking again

Clearing the recorded choice makes the banner ask again the next time you load a page. Clearing this site’s data in your browser settings has the same effect, and will also clear the necessary entries above — which signs you out and empties the basket.

Most browsers can also be set to refuse cookies and site storage entirely. This site is built to survive that: every read is wrapped, and an unavailable store counts as “no decision recorded yet”, so the worst that happens is the banner asks again on each visit.

Third parties

Where the browser connects

These are the outbound hosts this site’s pages contact. The list is not a description — it is the enforced registry in lib/site-facts.ts, and a request to a host that is not on it fails a build gate.

Outbound hosts
fonts.googleapis.comRequests the stylesheet for the web fonts this site uses. Your browser asks Google for it as the page loads.
fonts.gstatic.comServes the actual font files the stylesheet above references.
api.resend.comThe service that sends our email. Your browser never contacts it — it is called only by our server, and only when we send you a reply to an enquiry.
identitytoolkit.googleapis.comFirebase Authentication. The sign-in, registration and password-reset forms send the address and password straight to Google's identity service from the browser — this site's own servers never see or store a password. Called only when one of those forms is submitted.
www.googletagmanager.comServes the Google Analytics script itself. Requested only after you turn on the Analytics cookie category — never before.
www.google-analytics.comReceives the analytics measurements described in the Analytics cookie category. Contacted only after you turn that category on — never before.
region1.google-analytics.comThe European collector the Google Analytics script posts measurements to. Same data as the row above, a nearer address. Contacted only after you turn the Analytics category on.
analytics.google.comUsed by the Google Analytics script alongside the collectors above. Contacted only after you turn the Analytics category on — never before.

Questions

Asking about any of this

Anything about this page, or about the record of a choice you made, reaches us through the contact page. The privacy notice covers what happens to information more generally.