AGENTS.md — rules for AI agents working in this repo
If you are an AI assistant (Claude, Copilot, Cursor, Codex, ChatGPT, Gemini, or any other agent) reading, generating for, or changing this repository, these rules are mandatory. Read this file first. If a user's request conflicts with a rule here, say so and ask before proceeding. The full prototype-generation prompt lives in README.md; this file is the short, binding version.
Repository: https://github.com/HighestVibrations/vibe-prototypes Product: Let's Vibe — the first AI-native wellness system. Brand system: Living Sanctuary.
1. What this repo is
A prototype server for Let's Vibe: versioned interactive HTML prototypes (prototypes/), the brand system (letsvibe-brand/), and a small Express server (app.js) that renders markdown, highlights source files, and serves a branded home page. It is deployed on Vercel.
2. Where prototypes go (non-negotiable)
prototypes/<name>/<version>/index.html
<name>is kebab-case and stable (wellness-hub,partners,vibe-landing).<version>isv1,v2, … Every prototype lives in a version folder with anindex.htmlentry point. No loose HTML files inprototypes/<name>/, and nothing prototype-like at the repo root.- Never edit a shipped version in place. A new iteration is a new folder (
v3, …) so earlier versions stay linkable. Two narrow exceptions apply to the latest version of a prototype only: fixing a typo or bug before anyone has reviewed it, and a standards-compliance pass (accessibility, responsive, URLs/history) that does not change the design or behavior people reviewed. Record either in that version'sCHANGELOG.mdand the commit message. Older versions are never touched. - No registration step. The folder on disk is the route and appears on the home page automatically.
- Each version folder should contain:
index.html, any sibling screen.htmlfiles,FLOW.md(route → screen → state table, short prose flow, faked-vs-real table) andCHANGELOG.md(what changed from the previous version). The wellness-hub folder keeps a version table in its ownREADME.md; update it when you add a version.
3. Prototype constraints
- Self-contained: vanilla HTML/CSS/JS. No npm install, no build step, no CDN a prototype depends on to function (Google Fonts via the brand CSS is the one accepted exception).
- No catch-all routing. Unknown URLs are not rewritten to
index.html, so every view needs a real URL that works on cold load, reload and Back/Forward. Follow URLs, browser history and deep links below; it is mandatory. - Isolate all fake data in labeled
MOCK DATA BOUNDARYblocks. Declare one explicit state object, build each screen and reusable piece as a small named function, and list integration points in a comment at the top ofindex.html. (Details: README prompt.) - Do not invent product claims, clinical or medical claims, testimonials, or real people's data. Let's AI offers wellness guidance, not treatment; it never diagnoses. Use obviously fake sample data (the sign-in accounts
seeker@123.comandpractitioner@123.comare the demo convention).
URLs, browser history and deep links (required)
Every view a person could want to share, bookmark, reload or return to must have its own URL, and the browser's Back, Forward and Reload buttons must behave like they do on a real website. The URL is the source of truth for "where am I"; the screen is rendered from it. This applies to every page, step, tab, flow state, detail view and meaningful modal, and to anything an AI agent or tester needs to replay.
- Routing strategy: either multi-page (real sibling
.htmlfiles linked with<a href>) or hash routes (#/seeker/check-in) in a single page. Hash routing is the default for single-file prototypes because the server has no catch-all and a hash never reaches the server.history.pushState/replaceStateare allowed only with a same-document URL ('#/route'or'?query'); never to a path that changes the file path. - Route design: lowercase, kebab-case, human-readable, stable, and hierarchical:
#/practitioner/dashboard,#/seeker/events,#/practitioners/ana-lopez. Identify records by readable slug or id, not by list position. Put view refinements in a query inside the hash:#/practitioners?modality=reiki&sort=rating&page=2. Unknown or malformed values fall back to safe defaults. Never put secrets, credentials, tokens or personal data in a URL. - Deep links land exactly on target: opening any route URL cold (new tab, reload, pasted link, bookmark) renders that view directly: no flash of the home screen and no bounce through it. Render from the URL on first load, and again on every
hashchange/popstate. Never depend on in-memory state having been set by an earlier screen. If a view needs data, load it from the URL's identifiers. - History semantics:
- Moving to a different view, step, tab, or opening a detail view or a meaningful modal/sheet pushes a history entry, so Back returns to the previous place (and Back closes the modal).
- Refinements inside a view (filters, sorting, search text, pagination while typing) replace the current entry, debounced, so Back is not flooded with entries.
- Automatic redirects (sign-in bounce, default route, invalid route correction) replace, so Back never traps the user in a redirect loop.
- Real links: navigation is
<a href="#/…">(or a real file), never a<div>/<button>with only anonclick. Right-click copy link, open in new tab and middle-click must work. A button may call the router only for actions, not for plain navigation. - Protected or role-based views (Seeker vs Practitioner, signed-in only): a deep link while signed out goes to sign-in (by replace) and, after sign-in, returns to the originally requested route (for example
#/sign-in?next=%2Fpractitioner%2Fdashboard). Keep the demo session insessionStorage, never in the URL. A wrong-role link shows a clear message with a way forward, not a blank page. - Not found: an unknown route renders a friendly "page not found" view (with
<title>, heading and a link home), never a blank screen or a silent redirect. - Per-navigation housekeeping: update
document.titleto name the view; move keyboard focus to the new view's heading or<main>; scroll to top on a new view and restore scroll position on Back/Forward; keep the active nav item in sync viaaria-current="page". - What belongs in the URL: view, record id, wizard step, active tab, filters/sort/search/page, open modal/sheet, role or environment. What does not: hover, focus, transient toasts, unsent form input, and anything sensitive.
- Assets and paths: reference scripts, styles and images with relative paths so the prototype works at
/prototypes/<name>/<version>/with or without the trailing slash. - Document every route in
FLOW.mdand in the comment at the top ofindex.html: a table of URL, screen, state read from the URL, and exit links, plus a short list of ready-to-paste deep links for each major flow, including the role-specific ones.
Light and dark mode (required)
Every new prototype, new version and server-rendered page must ship with both a light and a dark mode, designed from the start. The Living Sanctuary tokens were built for it (--on-dark, --on-dark-soft, --pine-2, --grad-cta-on-dark and the other -on-dark tokens exist for this reason), so there is no excuse for a single-theme prototype. A screen that only works in one mode is not done. (Older versions that predate this rule are left as they are; any new version of them must add dark mode.)
- Follow the system setting with
@media (prefers-color-scheme: dark), and declare support up front:<meta name="color-scheme" content="light dark">and:root { color-scheme: light dark; }. Add<meta name="theme-color">for both schemes. A manual theme toggle is optional; if you add one, it must override the system setting through adata-themeattribute, remember the choice inlocalStorage, be a real labelled<button>, and never be the only way to get dark mode. - Use the token mapping below. Never hard-code hex, and never invert colors automatically.
| Role | Light | Dark |
|---|---|---|
| Page background | --mist |
--ink |
| Cards and raised surfaces | --paper |
--pine-2 |
| Hairlines and borders | --line |
--pine |
| Primary text | --ink |
--on-dark |
| Secondary text | --ink-soft |
--on-dark-soft |
| Eyebrows and quiet labels | darkened --sage-deep (AA) |
--sage |
| CTA fill, label, shadow | --grad-cta, --on-cta, --cta-shadow |
--grad-cta-on-dark, --on-cta-on-dark, --cta-shadow-on-dark |
| Inline links | --cta-text |
--cta-text-on-dark |
| Brand band or masthead | sage gradient (never the CTA color) | deep green gradient |
- Both modes meet every rule in this file, not just the light one: AA contrast (text 4.5:1, large text and UI 3:1, focus ring 3:1), the CTA rule (the CTA color stays reserved in both modes, so don't reuse the dark-mode gold for content), one gold spotlight per screen, the orb once per view, accessible focus styles, and no color-only meaning.
- Design the dark theme, don't flip it. Shadows get deeper and lower-contrast (not white glows); photos stay layered over the brand gradient with a scrim; avoid pure
#000and#fff; the orb keeps its gradient; icons, SVGs, charts and illustrations use token colors (orcurrentColor) so they follow the mode; check raster images and logos for a bad white box or low contrast on dark. - Verify both. Test every screen with
prefers-color-schemeset to light and to dark (for example PlaywrightcolorScheme), run the accessibility scan in both, and compare screenshots. Cold-load a deep link in each mode; the first paint must already be the right theme (no flash of the wrong one). List the check in your handoff.
4. Brand rules (Living Sanctuary)
Source of truth: letsvibe-brand/tokens/ (token files win over prose) and letsvibe-brand/brand/BRAND_GUIDELINES.md. Read the guidelines before designing anything.
- Use tokens, never hard-coded hex. Link from a version folder with
../../../letsvibe-brand/tokens/tokens.css,css/base.cssandcss/components.css, or copy the token:rootblock inline. - Type: Fraunces for display, Hanken Grotesk for body. Sentence case. Wordmark is Let’s Vibe with a curly apostrophe.
- One citrine (gold) spotlight per screen. Buttons are pills.
- The CTA rule (mandatory): one reserved color for every link and button, used for nothing else. Style every actionable link and button, including version links, with the brand CTA tokens: fill
--grad-cta(a radial gradient lit from the top-left) with--on-ctatext and the two-layer--cta-shadowtext-shadow on the label, or--cta-textfor inline text links; over dark surfaces use--grad-cta-on-dark,--on-cta-on-dark,--cta-shadow-on-darkand--cta-text-on-dark. The CTA color must never appear on content or design: no headings, body text, labels, eyebrows, numbers or step/counter markers, icons, dividers, tags, illustrations, charts, or card and section backgrounds. Content and decoration use ink, sage, sand, paper or the category accents; if a non-action and a CTA could share a color, change the non-action. Numbered identifiers are content: ring them in--sage-deepwith--inktext. Don't place a CTA directly on a surface of the same hue (for example a pine button on a pine panel); give it a contrasting surface. Every CTA defines all of these states as CSS transitions: rest, hover (brightness(1.12)), active/pressed (brightness(.84)+translateY(1px) scale(.97), also held while its menu is open oraria-pressed),:focus-visibleand disabled (50% opacity, no press). Active must look clearly different from hover. Anything that looks like a button must be one, and every button or link must look like one. Details:BRAND_GUIDELINES.md("CTA color — reserved"). - The orb appears once per view, large, as an anchor; never decorative, never shrunk into a bullet. Its 7-second breath is the only ambient animation.
- Always use CSS transitions for motion (mandatory). Every property of every element animates between states: nothing snaps while its neighbors animate. If a header's logo scales smoothly but its background or height jumps, that violates this rule. Every visual state change (hover, focus, press, open/close, expand/collapse, show/hide, compact/expand, theme change, route or view change) is animated with CSS
transition, never an instant jump, never per-frame JavaScript tweening, never inline-style animation loops. JavaScript only flips state (a class,aria-expanded, adata-*attribute, a CSS custom property) and CSS does the animating. Size, height, width, padding, margin, position, opacity, color, shadow, background and border all transition, not just transforms. Choreograph related changes as one: when several properties change together (a header compacting, a panel opening), drive them from a single registered custom property (@property --p,0 → 1) that is the only thing transitioned, and derive every size, padding and height from it withcalc(), so they share one duration and easing and can never drift apart. Never give a container a separate hard-codedheighttransition on top of animating contents; let its height follow from the animating contents (or from--p). Avoid animating to or fromauto/display/hidden; where a disclosure must change height, useinterpolate-size: allow-keywordswith::details-content, or grid-template-rows0fr → 1fr. Pick the easing by what moves:--ease(settle) for fades, hovers and entrances, and--ease-inout(symmetric, gentler) for geometry such as size, height, position and layout. Use--dur-slow(400ms) for anything that changes the size of a large region. Specifics: use the motion tokens (--dur-fast150ms for hovers,--dur-base200ms for state changes,--dur-slow400ms for panels,--ease); animatetransform,opacity,filter,box-shadow, colors and sizes, not layout-thrashing properties where avoidable; register custom properties with@propertyso a value like a progress variable can itself transition; reveal hidden panels by transitioningopacity/transformplusvisibility(never toggledisplayorhiddenwhen you need an animation, and keep closed panels out of the tab order viavisibility: hidden); use@starting-stylefor elements entering the DOM or<details>. Honorprefers-reduced-motion: reduceby dropping transition durations to ~0 (state still changes, just without motion). The orb's breathe is the only ambient animation (and is not a transition). - Logo and type stay together (mandatory). The orb (logo) and the Let’s Vibe wordmark (type) form one lockup: adjacent, in a single link/element, sharing one baseline-centred row. Nothing may come between them: no menu, button, link, text, divider, image, whitespace larger than the lockup gap, or layout cell. Never split them into different corners, columns or sections, and never place one in a different header zone from the other. When the header resizes (for example compacting on scroll) they scale and move together, keeping their relationship. Other elements (such as the menu) go to the sides of the lockup, never inside it.
- Eyebrows are short, uppercase labels above headings, never sentences.
- Modalities never use photos (typographic cards). Practitioners and events may use photos, always layered over a brand gradient with a scrim for text.
- Vocabulary: members are Seekers; a check-in is a vibration; the assistant is Let's AI. Buttons name what happens ("Declare vibration", "Reserve a place").
- Accessibility and responsive design are mandatory, not polish. See section 5.
5. Accessibility and responsive design (required)
Every prototype screen, and every change to the home page or server-rendered pages, must be accessible and responsive. This is a hard requirement, not a nice-to-have. Do not hand off work that fails any item below, and never trade these away for visual polish. Target: WCAG 2.2 level AA.
Accessibility
- Semantic HTML first: real
<header>,<nav>,<main>(one per page),<footer>,<button>for actions,<a href>for navigation, lists for lists, tables only for data. Do not build buttons or links out of<div>/<span>. Use ARIA only to fill gaps semantic HTML can't, and never to contradict it. - Structure:
<html lang="en">, a unique and descriptive<title>per screen, exactly one<h1>, and headings in order without skipping levels. Include a "Skip to content" link when a page has repeated navigation. - Keyboard: everything works with the keyboard alone, in a logical tab order, with no keyboard traps. Visible focus on every interactive element (
:focus-visible, using the brand focus token; neveroutline: nonewithout a replacement). Modals and menus move focus in, trap it only while open, close onEsc, and return focus to the trigger. When a hash or page change shows a new screen, move focus to its heading or<main>. - Names and labels: every form field has a real, visible
<label>; every icon-only control has an accessible name (aria-label); every meaningful image has usefulalttext, and decorative images usealt="". Errors are specific, tied to their field (aria-describedby,aria-invalid) and announced. - Dynamic content: announce async changes (toasts, validation, loading, results) with
aria-liveregions orrole="status"/role="alert". Expanding controls exposearia-expanded; current items exposearia-current. - Color and contrast: text contrast ≥ 4.5:1 (≥ 3:1 for large text and for UI components, icons and focus rings), in both light and dark mode. Never use color alone to convey meaning; pair it with text, an icon or a pattern.
--ink-softis for secondary text only. - Motion and media: honor
prefers-reduced-motion: reduce(no ambient or parallax motion; the orb holds still). Nothing flashes more than 3 times per second. Video and audio have captions or transcripts and never autoplay with sound. - Touch targets are at least 44 × 44 px with adequate spacing.
- Never disable zoom: no
user-scalable=noand nomaximum-scalebelow 5 in the viewport meta tag.
Responsive design
- Mobile-first. Always include
<meta name="viewport" content="width=device-width, initial-scale=1">. Design for a 320px-wide screen first, then enhance upward. - No horizontal scrolling at any width from 320px up (WCAG reflow), except for content that truly needs two dimensions (wide data tables, code blocks inside their own scroll container).
- Fluid layout: use CSS grid/flexbox,
clamp(),min()/max(), and relative units (rem,%,ch,vw) rather than fixed pixel widths. Images and media aremax-width: 100%and keep their aspect ratio. Use the brand spacing and fluid type tokens. - Text scales: layouts must survive browser text at 200% and text-only zoom without clipped, overlapping or hidden content. Do not set fixed heights on text containers.
- Breakpoints are content-driven, but every screen must be checked at 320, 375, 768, 1024 and 1280 px wide, in portrait and landscape on phones, plus light and dark mode.
- Input-agnostic: hover is never the only way to reveal something; everything works with touch, keyboard and pointer.
- Respect safe areas on notched phones (
env(safe-area-inset-*)) for fixed bars.
How to prove it (do this and report it): resize through the widths above; tab through every screen with the keyboard only; zoom to 200%; turn on reduced motion and dark mode; and run an automated checker (for example axe or Lighthouse accessibility) when one is available, fixing every serious or critical issue. State in your handoff what you checked and what you could not check.
6. Server and deployment rules (Vercel)
The Vercel runtime cannot require() ES-module-only packages (it fails with ERR_REQUIRE_ESM). The fix in place is: pnpm run build bundles app.js and all dependencies into one CommonJS file (app.bundle.js, git-ignored) via esbuild, and api/index.js loads it. Therefore:
- Do not bypass or remove the esbuild bundle step, and do not make
api/index.jsrequire dependencies directly. - Before adding or upgrading a dependency, confirm the server still starts: run
pnpm run build, then loadapi/index.js(it must serve/,/README.mdand a prototype with HTTP 200). Prefer dependencies that are CommonJS or bundle cleanly; avoid adding any that need native binaries or browser simulation (jsdomwas removed for this reason). vercel.jsonroutes every request toapi/index.jsand bundles files withincludeFiles.prototypes/**andletsvibe-brand/**are already covered. Any new top-level folder that must be served has to be added toincludeFiles. Prefer putting new work underprototypes/instead.public/is only a placeholder Vercel requires; nothing in it is served.- Keep
app.jsrunnable locally (pnpm start→ http://localhost:3000). It must export the Express app and only calllistenwhen run directly. - Package manager is pnpm (version pinned in
package.json). Commitpnpm-lock.yaml; never commitpackage-lock.jsonoryarn.lock.
7. Git and change hygiene
- Use Conventional Commits (
feat(prototypes): …,fix: …,docs: …,refactor: …,chore: …). - This team prototypes fast: commit straight to
main. No pull requests. Vercel deploys every push tomainautomatically (live site: https://vibe-prototypes-two.vercel.app/). Becausemainis live and shared:- Run
git pull --rebase origin mainbefore you start and again right before you push, so you never overwrite a teammate's work. - Run the checks in section 8 before pushing. Only push work that passes. Small, frequent commits beat big ones.
- Never force-push or rewrite history on
main, and never delete other people's files, folders, branches or work without being asked. - If a rebase conflicts, resolve it by keeping both sides' intent; if you cannot do that safely, stop and ask. Never discard someone else's changes to make a push succeed.
- Use a branch or pull request only if the person asks for one.
- Run
- When adding a version, write its
CHANGELOG.mddescribing changes from the previous version, and update the rootCHANGELOG.mdfor repo-level changes. - Keep changes minimal and in scope. Do not reformat, rename or move files you were not asked to touch. Use
git mvfor moves so history is preserved. - Never commit secrets, tokens,
.envfiles, or real personal data.
8. Verify before you hand off
- The prototype loads at
/prototypes/<name>/<version>/. - URLs and history: every view, step, tab, detail and meaningful modal has its own URL; each route opened cold in a new tab (and after reload) lands exactly on that view; Back/Forward replay a recorded journey in order; a copied URL opens the same view elsewhere; refinements replace and navigation pushes history; sign-in redirects return to the requested route; an unknown route shows a not-found view; document title, focus and scroll update per navigation; routes are listed in
FLOW.md. - Only tokens used for color, type and spacing; one gold spotlight; reduced motion honored.
- Light AND dark mode (required for every new prototype and version):
color-schemedeclared, the token mapping used, no hard-coded hex, both modes pass contrast and the CTA rule, every screen and deep link checked and scanned in both, no flash of the wrong theme. - CSS transitions: every state change is a CSS transition driven by class/attribute state (no JS tweening, no instant show/hide); every changing property animates (size, height, padding, position, color, shadow, background, not just transforms); related changes are driven by one shared
--p, so nothing snaps while another part animates; motion tokens used (--ease-inoutfor geometry); reduced motion removes the motion. Check by sampling frames mid-transition. - Logo + type lockup: orb and wordmark sit together as one unit with nothing between them, at every width and in the compact header state.
- CTA rule: every link and button uses the CTA tokens and nothing else does (numbers, labels, icons and decoration use other colors); CTAs contrast with their surface.
- Accessibility (WCAG 2.2 AA): semantic landmarks and heading order, full keyboard operation with visible focus, labels and alt text, AA contrast in light and dark, no color-only meaning, reduced motion honored, zoom not disabled.
- Responsive: viewport meta present, no horizontal scroll from 320px, checked at 320 / 375 / 768 / 1024 / 1280 px, usable at 200% text size, touch targets ≥ 44px.
-
pnpm run buildsucceeds and the app serves/and your prototype with HTTP 200. - Docs updated (version table,
FLOW.md,CHANGELOG.md, README routes if relevant). - You report faithfully what you tested and what you did not. Do not claim something works unless you ran it.
9. Working with non-technical teammates
Many people using this repo (designers, strategists, partners) are not developers and will start from the copy-ready prompts on the home page (source: getting-started/prompts.json). When a request reads like one of those prompts, or the person seems non-technical:
- Speak plainly. No jargon: say "a link to your prototype", not "a route"; "save and publish", not "commit and push" (use the real term once in brackets if it helps). Keep updates short and friendly, never dump logs.
- Ask little. If the purpose, audience (Seeker, Practitioner or both) or key screens are missing, ask at most three short questions, then proceed with sensible defaults and say what you chose. Offer to adjust.
- Start from the latest. Update from
mainbefore beginning and again before pushing. If something blocks that (uncommitted work, conflicts), explain it simply and fix it safely; never discard someone's work without asking. - Do the whole job. Create the right folder,
FLOW.mdandCHANGELOG.md, run every check in section 8, then update frommain, commit, and push straight tomain(no pull request on this team). Vercel publishes it within a minute or two. - New idea vs. new version vs. small fix: new idea → new prototype at
v1; changes to something already reviewed → new version folder; trivial fix to the latest unreviewed version → in place (see section 2). When unsure, ask. - Finish with a plain-language handoff: what you built, the live link (https://vibe-prototypes-two.vercel.app/prototypes///, available a minute or two after the push) and the local path, what you checked, and what you could not check.
- Protect them: never commit secrets or real personal data; if they paste any, say so and leave it out.
- The slash commands in
.claude/commands/(/get-latest,/new-prototype,/new-version,/check-standards,/ship) are shortcuts for these same rules; the rules here win if they ever differ.