尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Marketing site for Forward Matter, a Sydney software company building automated market-intelligence software for financial services and the pharmaceutical industry.
Built with Next.js (App Router) and plain CSS, from the design handoff kept in
design-handoff/ (see design-handoff/NOTES.md).
npm install
npm run dev # http://localhost:3000
npm run build # production build
npm start # serve the production build
npm run typecheck # tsc --noEmit
npm run build:static # flat, host-anywhere export in out/
out/ can be dropped on any static host, including ones that serve from a
subdirectory. It exists so the site can be shared before a real deployment, and it
is not the thing to deploy: it has no /api/contact of its own, no image optimiser,
and it pins a Google Fonts fallback for hosts that block Fontshare. Deploy
npm run build instead.
To make the preview's form actually send, point it at a deployed API:
NEXT_PUBLIC_CONTACT_ENDPOINT=https://<deployment>/api/contact npm run build:static
and add the preview's origin to CONTACT_ALLOWED_ORIGINS on the API side so the
browser's preflight passes. Without an endpoint the preview still shows the success
state — it is there to show the design — but says the enquiry was not sent, rather
than letting a real prospect believe it reached you.
/simulator is for people we have given an access phrase to. The simulator source is
kept outside this repository (place it at simulator-src/app.html, which git ignores)
and is never served as is: npm run build:simulator encrypts it once with a random key
(AES-256-GCM) and locks a copy of that key under each access phrase (PBKDF2, 200,000
rounds) into public/simulator/payload.json, which is what the page fetches. The access
phrase form tries the phrase in the visitor's browser against each locked copy and renders
the decrypted simulator in a sandboxed frame, so there is no server-side secret and it
works on the static export as well.
Each person or group can have their own phrase. The phrases are never in the repository; set them in the environment where the build runs (not in Vercel, which only ever receives the encrypted file):
SIMULATOR_PASSPHRASE='first phrase' SIMULATOR_PASSPHRASE2='second phrase' npm run build:simulator
git add public/simulator/payload.json && git commit -m "Rebuild the simulator payload"
Every variable named SIMULATOR_PASSPHRASE, SIMULATOR_PASSPHRASE2, SIMULATOR_PASSPHRASE3
and so on is used. To remove someone's access, remove their variable and rebuild. A rebuild
is a new file, not a revocation: anyone who saved an old payload.json can still open it
with the phrase that opened it then.
| Path | What it holds |
|---|---|
app/page.tsx | Home — hero, intelligence system, catalyst panel, platform, case study, closing block |
app/about/page.tsx | About us — why we exist, the team, contact CTA |
app/simulator/page.tsx | Simulator — a password-locked internal page (noindex, not in the sitemap) |
components/SimulatorGate.tsx | The access phrase form; decrypts and frames the simulator in the browser |
simulator-src/app.html | The simulator itself, kept outside the repository and ignored by git; never served in the clear |
scripts/build-simulator.mjs | Encrypts the simulator into public/simulator/payload.json |
app/api/contact/route.ts | Server-side validation and delivery for the enquiry form |
app/globals.css | Design tokens and every shared surface, button and section style |
components/ | Shell (header, footer, brand mark) and one component per home section |
content/site.ts | All copy and structured content |
public/ | Portraits, case-study photograph, the particle animation |
Copy changes belong in content/site.ts. Colour, shadow and spacing changes belong
in the token block at the top of app/globals.css.
A deliberate neumorphic treatment on a light grey ground: raised panels with a white
top-left highlight and a grey bottom-right drop shadow, recessed wells with inset
shadows, buttons that physically depress on :active, and text-shadow: 0 1px #fff
on most type. The shadow recipes are the heart of the style and live as CSS custom
properties (--shadow-card, --shadow-well, --shadow-btn and so on). The light
source is always top-left; keep that direction consistent when adding surfaces.
Every section is a repeat(auto-fit, minmax(min(100%, Npx), 1fr)) grid, so columns
collapse to one at narrow widths without media queries. Keep the min(100%, …)
inside minmax — a bare floor overflows the container instead of collapsing.
The form posts to /api/contact, which trims and validates the fields server-side
(name, firm and a well-formed email are required), drops anything that fills the
hidden honeypot field, and then delivers the enquiry — by email through Resend, or
to a webhook.
Set these in the hosting project, never in the repo:
| Variable | What it does |
|---|---|
RESEND_API_KEY | Resend key, starts re_. With CONTACT_TO, enquiries arrive as email. |
CONTACT_TO | Who the enquiry is addressed to. No default — a default would mean a real address sitting in a public repository. |
CONTACT_FROM | The from address, e.g. Forward Matter <[email protected]>. Needs a Resend-verified domain. Unset, this falls back to Resend's shared [email protected]. |
CONTACT_WEBHOOK_URL | Deliver as JSON to a CRM, Slack or Zapier hook instead of email. |
CONTACT_ALLOWED_ORIGINS | Comma-separated origins allowed to post cross-origin, for a static export hosted elsewhere. Same-origin posts never need this. |
NEXT_PUBLIC_CONTACT_ENDPOINT | Build-time only, and only for the static export: the full URL of a deployed /api/contact. |
[email protected] only delivers to the address that owns the Resend account,
so until forwardmatter.com is verified at resend.com → Domains, CONTACT_TO has
to be that address. Once it is verified, set CONTACT_FROM and CONTACT_TO can be
anything.
With none of the delivery variables set the route returns 503 and the form shows its error. It deliberately does not accept-and-log: an enquiry that vanishes while the visitor reads "Thank you" is worse than one that visibly failed.
No acknowledgement email is sent to the enquirer. Anything this endpoint mailed to a submitted address would make it a way to deliver attacker-chosen text over the Forward Matter domain, and the form already confirms receipt on the page.
aria-hidden, never takes pointer events, and is
not rendered at all when the visitor prefers reduced motion or canvas is
unavailable, leaving the static radial highlight in place.scroll-margin-top on anchored sections (so
targets clear the sticky header) are all in place.api.fontshare.com, so the
stylesheet is linked with a preconnect as the prototype does. Drop the woff2
files into public/fonts and swap the <link> in app/layout.tsx when convenient.metadataBase, the sitemap and robots use https://forwardmatter.com. Update them
if the production domain differs.