Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 18 additions & 40 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,46 +1,24 @@
# Web3Forms access keys.
# Web3Forms access key.
#
# These values are PUBLISHABLE, not secret. They carry the NEXT_PUBLIC_ prefix,
# which means Next inlines them into the client bundle at build time, and anyone
# viewing source on the deployed site can read them. Keeping them out of the
# source tree is hygiene, not confidentiality: it stops the repository shipping a
# ready-made endpoint, and it keeps one environment's configuration out of
# another's. It does not make the values private, and nothing here should be
# described as though it does.
# Generate at https://web3forms.com. The key belongs to one Web3Forms form, and
# that form's recipient mailbox is set in its dashboard.
#
# What that means in practice:
# Set this in Vercel's Environment Variables (Production + Preview) and in a
# local .env.local for development. Never commit a real key.
#
# - The realistic risk is spam and quota consumption, not disclosure.
# - Web3Forms offers a domain restriction under Settings > Security Settings.
# It is enforced on a request header that any client can set, so it raises
# the effort required and does not prevent abuse. Set it; do not rely on it.
# - There is NO way to rotate a key. The access key is the form identifier.
# Deleting the form is the only way to invalidate one, and that almost
# certainly destroys the form's submission history, so export first.
# - Values are inlined at BUILD time. Changing one in the hosting dashboard
# does nothing to a deployment that already exists. Redeploy, or the old
# value keeps being served.
# - Paste carefully. A stray leading tab or trailing newline travels into the
# bundle with the key, and the service then rejects it as a malformed UUID.
#
# Each key belongs to one Web3Forms form, and a form's recipient mailbox is set
# in its dashboard, so routing enquiries to different mailboxes means one form
# per mailbox.
# NOTE: NEXT_PUBLIC_ values are inlined at BUILD time. Adding or changing one
# after a deployment requires a redeploy before it takes effect. Paste the value
# carefully — a stray leading tab or trailing newline makes Web3Forms reject
# the key as a malformed UUID.

# The fallback for every route below. Note that once all seven per-mailbox keys
# are set, nothing reads this one, so a fault in it is invisible to testing.
# Required. Used by every form on the site: /contact, /enquiries, and the
# testimonial form on /testimonials.
# Recipient: contact@invisionsolutions.co.uk
NEXT_PUBLIC_WEB3FORMS_KEY=

# Per-mailbox keys used by /enquiries. Any left unset fall back to the key above.
NEXT_PUBLIC_WEB3FORMS_KEY_SALES=
NEXT_PUBLIC_WEB3FORMS_KEY_CONTACT=
NEXT_PUBLIC_WEB3FORMS_KEY_SUPPORT=
NEXT_PUBLIC_WEB3FORMS_KEY_BILLING=
NEXT_PUBLIC_WEB3FORMS_KEY_INVOICE=
NEXT_PUBLIC_WEB3FORMS_KEY_ADMIN=

# Used by /enquiries and, read directly, by the testimonial form on /testimonials.
NEXT_PUBLIC_WEB3FORMS_KEY_HELLO=

# /contact reads NEXT_PUBLIC_WEB3FORMS_KEY_CONTACT, the same key as the General
# enquiry desk, so the form it submits to is exercised on every /enquiries test.
# There were previously seven per-mailbox keys (NEXT_PUBLIC_WEB3FORMS_KEY_SALES,
# _CONTACT, _SUPPORT, _BILLING, _INVOICE, _ADMIN, _HELLO), one per Web3Forms
# form. Those forms have been deleted from the account and nothing in the code
# reads those variables any more. Delete them from Vercel too: a variable
# naming a form that no longer exists is the failure that took /contact down in
# August, and leaving them set invites someone to wire one back up.
25 changes: 0 additions & 25 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,31 +60,6 @@ jobs:
- name: Build
run: npm run build

# The brand assets in public/brand/ are generated, committed, and served
# to the public from /brand. Nothing verified that the generator still
# produced them until this step existed.
#
# That gap is not hypothetical. `npm run brand` was broken on main and
# stayed broken through two merges, because no job ran it: the generator
# located its webfont by CSS *filename*, and Next emits one hash-named
# stylesheet carrying every family. A manual run days later is what
# surfaced it.
#
# `git diff --exit-code` is the assertion. Regenerating is not enough on
# its own: the generator can run to completion and still emit different
# bytes, which is exactly the risk when the imaging library underneath it
# moves a major version.
- name: Brand assets reproduce byte-identically
run: |
npm run brand
git diff --exit-code public/brand/ || {
echo "::error::Regenerating produced different bytes from the committed brand assets."
echo "Either the generator changed behaviour, or a dependency under it did."
echo "If the change is intended, commit the regenerated files in the same PR."
git diff --stat public/brand/
exit 1
}

secrets:
name: Secret scan
runs-on: ubuntu-latest
Expand Down
24 changes: 10 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ Open [http://localhost:3000](http://localhost:3000).
| `npm run start` | Serve the production build |
| `npm run lint` | ESLint |
| `npm run check:install-scripts` | Fail if a dependency ships an install script not on the allowlist |
| `npm run brand` | Regenerate the downloadable brand assets (run `build` first) |

## Environment variables

Copy `.env.example` to `.env.local` and fill in the Web3Forms access keys:
Copy `.env.example` to `.env.local` and fill in the Web3Forms access key:

```bash
cp .env.example .env.local
Expand All @@ -34,6 +33,10 @@ Web3Forms access keys are **publishable, not secret**. They are prefixed `NEXT_P

Deployment and mailbox configuration is documented privately.

There is one Web3Forms form and one key. `/contact`, `/enquiries` and the testimonial form on `/testimonials` all submit with `NEXT_PUBLIC_WEB3FORMS_KEY`, which delivers to `contact@invisionsolutions.co.uk`. Each form sets its own subject line, so mail rules can still tell submissions apart.

This was previously seven forms and eight keys, one per department mailbox. Those forms have been deleted from the Web3Forms account, and nothing reads the old `NEXT_PUBLIC_WEB3FORMS_KEY_*` variables any more. Delete them from the hosting dashboard too: a variable naming a form that no longer exists is the fault that stopped `/contact` delivering in August, and leaving them set invites someone to wire one back up.

### When a submission fails

Three unrelated causes used to produce one identical message, which made a failure report carry no information. The form now names the cause on screen, so a screenshot is enough to tell them apart without asking anyone to open a browser console:
Expand All @@ -51,22 +54,15 @@ The mark is a cloud drawn as one continuous line: the stroke traces the silhouet

There is **one version and no variants**. No dark colourway, no inverted form, and no heavier stand-in at small sizes. The tile, favicon and app icon all carry the same artwork on a white field. On anything other than white or near-white, use the white tile rather than recolouring the mark.

Geometry and colour roles live in `src/lib/brand.ts` and are mirrored in `scripts/generate-brand-assets.mjs`. Change both together.
Geometry and colour roles live in `src/lib/brand.ts`, which is the single source for the mark. `src/components/logo.tsx`, `src/app/icon.tsx` and `src/app/opengraph-image.tsx` all draw from it.

The mark and the wordmark are trademarked material. See `LICENSE.md`.

### Regenerating the downloadable files

```bash
npm run build # must run first; the generator reads Next's webfont files
npm run brand
```

This writes 15 files to `public/brand/`, surfaced on `/brand`. Wordmark type is converted to outlines so the SVGs render correctly without Space Grotesk installed.
There is no public brand-assets page and no downloadable logo files. There was a `/brand` route serving 15 generated files from `public/brand/`, produced by `scripts/generate-brand-assets.mjs`; the page, the files and the generator have all been removed. Send anyone who needs artwork a file directly.

### Two things worth knowing before editing the lockup

**The descriptor is width-matched.** "SOLUTIONS" is tracked so it spans exactly the width of "INVISION" above it. Size is the lever, not tracking: at 25% of the wordmark it would need 1.28em of tracking to reach that width, which scatters the letters, so it sits at 45% and needs 0.4615em. The generator re-solves the value from real font metrics rather than reading the constant, so the two cannot drift apart. Both figures are recorded in `WORDMARK` in `src/lib/brand.ts`.
**The descriptor is width-matched.** "SOLUTIONS" is tracked so it spans exactly the width of "INVISION" above it. Size is the lever, not tracking: at 25% of the wordmark it would need 1.28em of tracking to reach that width, which scatters the letters, so it sits at 45% and needs 0.4615em. Both figures are recorded in `WORDMARK` in `src/lib/brand.ts`. They were previously re-solved from real font metrics by the generator, which no longer exists, so the constants are now the only record — check them against rendered output if the wordmark is ever retyped.

**`src/app/_fonts/` exists for the Open Graph image.** `next/font` ships Space Grotesk as a single variable file covering all three weights, and Satori does not apply variable axes, so `ImageResponse` silently falls back to a generic sans. Those three static font instances are loaded explicitly in `src/app/opengraph-image.tsx` to stop that happening. Do not delete them.

Expand All @@ -82,14 +78,14 @@ HSTS is sent with `max-age` and `includeSubDomains` but deliberately **without `

Routes, one folder per page under `src/app/`:

`/` · `/services` · `/approach` · `/case-studies` · `/testimonials` · `/enquiries` · `/contact` · `/brand` · `/payreckon`
`/` · `/services` · `/approach` · `/case-studies` · `/testimonials` · `/enquiries` · `/contact` · `/payreckon`

- `src/app/icon.tsx`, `src/app/opengraph-image.tsx`: favicon and social card, generated at build time
- `src/app/globals.css`: design tokens (colour, font, focus states) as CSS custom properties, mapped into Tailwind's `@theme`
- `src/components/*`: header, footer, logo, surface primitives, verification-ledger motif, scroll-reveal wrapper, social icons, Web3Forms form
- `src/lib/brand.ts`: mark geometry, colour roles, wordmark metrics
- `src/lib/content.ts`: services and case study copy, shared between the Home teasers and the full pages
- `src/lib/mailboxes.ts`: the department mailboxes behind `/enquiries`
- `src/lib/mailboxes.ts`: the single Web3Forms access key and the addresses the site publishes
- `src/lib/metadata.ts`: canonical URL and per-page metadata helper
- `src/lib/payreckon.ts`: PayReckon product copy, with sourcing rules in the file header

Expand Down
Loading
Loading