# Goodseph build playbook: how a consultancy website was built with Claude

Audience: an AI agent (Claude Code or similar) helping a non-developer build a professional services website on WordPress. Read this whole file before acting. The human owner makes every decision about brand, wording and money. You do the building and the checking.

Source: the build of goodseph.com, a brand and ideation consultancy site, by Joseph with Claude. All names, prices and dates are examples. Adapt everything to the person you are helping.

---

## 0. Ground rules (read first)

1. **The human owns the brand.** Never invent claims, testimonials, numbers, clients or biography. Ask, or leave a clearly marked placeholder.
2. **Never handle secrets.** Do not type, store or repeat passwords, API keys, tokens or payment details. When a step needs one (an email service API key, a login), tell the human exactly where to paste it and let them do it.
3. **Confirm before anything outward-facing or hard to undo:** publishing, sending email, deleting data, changing live settings, submitting forms. Local work needs no confirmation.
4. **Respect read-only folders.** If the human names a folder as read-only, never edit or delete anything in it.
5. **Keep a rules list** and obey it in every edit. Goodseph's rules, as examples: no em or en dashes in site copy; soft shadows, no black borders; pill-shaped buttons; keep the admin email unchanged; do not rewrite category descriptions; generated images go in their own project folder.
6. **Report honestly.** If a check fails or a step was skipped, say so. Do not claim "done" until you have looked at the real result.

## 1. What you need before building

- A domain (registrar example: Cloudflare Registrar, sold at cost, DNS in the same place).
- Hosting with a current PHP version, daily backups, a control panel and the ability to upload a theme zip. Example: a small Indian provider (mywebhosting.in, Basic package, LiteSpeed). Hostinger and Bluehost offer the same shape of service.
- A fresh WordPress install. A one-click installer is fine.
- A Claude plan that includes Claude Code (the build used the 20 US dollar plan). Use the **Claude desktop app**, not only the browser chat. The desktop app can work on a folder on the person's computer, run commands and open a built-in browser to check pages. A plain chat window can only talk.
- Content, gathered by the human BEFORE you build. See section 2.

Rough cost at the time: domain about 1,200 INR a year, hosting 1,200 INR a year plus 18 percent GST, Claude 20 USD a month.

## 2. Content first (the human's job, 2 to 3 weeks of evenings)

Ask the human for, and save as plain documents:
- One sentence on who they help and how.
- The headline and tagline.
- The method or process, in named steps.
- Service descriptions.
- Case studies: client, problem, what was done, result.
- About page text.
- Logo, photos, illustrations or characters.
- What a visitor should do (book a call, send an inquiry).
- A rules list (section 0, item 5).

Do not start design until these exist. Goodseph had versioned copy documents (v3 to v5) and an asset plan before any code.

## 3. Build locally, never on the live site

Why: a local folder is a safe dry run. You can change anything, break it and look at it, and nobody sees. Build the whole theme there as one folder, then upload it once. That single upload is what gets the live site up and running.

1. Run a local WordPress. WordPress Playground works well: mount the theme folder into `wp-content/themes/<name>` and serve it on a local port.
2. Build a **block theme** (`theme.json` version 3, `style.css` with a Version header, `functions.php`, `templates/`, `parts/`, `patterns/`, `assets/`).
3. Use a version string (for example 0.17.7) in both `style.css` and a `THEME_VERSION` constant. Bump it on every release. It cache-busts assets.
4. Keep files **UTF-8 without a BOM**. See pitfalls.

## 4. Architecture choices that worked

- **Custom post types** for structured content: case studies, services, and inquiries.
- **Dynamic blocks (`register_block_type` with a render callback)** instead of shortcodes inside post content. WordPress's `wpautop` inserts stray `<p>` and `<br>` into shortcode output and breaks layouts.
- **Inquiries saved before email.** The contact form posts to `admin-post.php`, checks a nonce, saves the inquiry as a private CPT in the admin, then sends email with a Reply-To. If email fails the inquiry is still kept. Mark the mail result in post meta.
- **CPT capabilities:** if you map custom capabilities, set `map_meta_cap` to false for an inquiries CPT that uses `manage_options`. Mapping every capability to `manage_options` with `map_meta_cap` true broke `manage_options` sitewide.
- **Design tokens in CSS custom properties** (ink, accent, tints, shadows, logo height). One token set, used everywhere.
- **Icons:** use one icon family (Phosphor, as SVG files). Do not hand-draw SVG icons.
- **Generated images:** the human can make site pictures with image generators such as Gemini, Grok or ChatGPT. Ask them to keep one style prompt, use a reference photo for a consistent character, request plain or transparent backgrounds, and save everything in one named folder. You then resize, compress (WebP) and add alt text.
- **Images:** one helper that outputs `<img>` with width, height, `srcset`, `sizes` and meaningful alt. Give poses and illustrations a dimensions table.
- **Blog:** categories with a tint and icon each, a "Writing" page, single post template with a progress bar, and archive treatment for old posts. Keep category slugs stable.

## 5. Working method with Claude (what to do in each loop)

1. Make the change locally.
2. **Look at the real page.** Use a browser tool or headless screenshots at desktop and phone widths. Do not trust code review alone.
3. Compare against the rules list.
4. Fix everything the screenshot shows in one batch, then re-check once.
5. When the human sees a bug, ask for a screenshot and measure the element (bounding boxes, computed styles) before guessing.
6. Offer options for taste decisions (colour, layout) and let the human choose. Revert quickly when they say no.

### Claude skills used in this build
These are installed skills (invoked by name) that shape how Claude designs and writes:
- **design-taste-frontend:** reads the brief, picks a design direction and avoids generic, template-looking output. Used to set the design language. Source: https://github.com/Leonxlnx/taste-skill . Install: `npx skills add https://github.com/Leonxlnx/taste-skill --skill "design-taste-frontend"`
- **impeccable:** a design toolkit with sub-commands. Its **critique** mode was run on the whole site and on the portfolio section to catch hierarchy, spacing and polish problems. Source: https://impeccable.style and https://github.com/pbakaus/impeccable . Install: `npx impeccable install`, then run `/impeccable init`.
- **copywriting:** used for headline and page copy. Source: https://github.com/coreyhaines31/marketingskills . Install: `npx skills add coreyhaines31/marketingskills --skill copywriting -a claude-code`
These commands need Node installed. Restart the agent after installing so it loads the skills. Only install skills from sources the human trusts, and tell the human what you are installing.
If you have equivalents, use them. If you have none, follow the same ideas by hand: read the brief, avoid generic layouts, critique your own screenshots.

## 6. Release process

1. Bump the version.
2. Check no file starts with a BOM (bytes EF BB BF).
3. Zip the theme folder so the top level is the theme directory.
4. Human uploads in WordPress: Appearance, Themes, Add New, Upload (choose "replace current with uploaded"). Keep the previous zip.
5. Hard refresh. Verify the live version: fetch the page and `style.css` and read the version.
6. If assets still show the old version, the server is caching old PHP (OPcache). The human restarts PHP from the hosting panel or asks the host. Files can be correct while PHP is stale.

## 7. Email that arrives

- WordPress's default mail is unreliable. Use an SMTP or API sender (example: Brevo, with the FluentSMTP plugin).
- Authenticate the sending domain (SPF, DKIM, DMARC). If DNS is on Cloudflare, the provider's automatic setup is easiest.
- **The human creates the API key (restricted to transactional email) and pastes it into the plugin.** You never see it.
- Send a test and confirm it reaches the inbox, then test the real form.

## 8. Booking

Embed a free Cal.com page inside a `<details>` element under a "Pick a time" button, with a plain link fallback. Make the button fit its container (`box-sizing: border-box`, never wider than the card).

## 9. SEO

- Write one title (about 60 characters including the brand suffix) and one description (about 155 characters) **per page**, by hand, and keep them in a document the human can review.
- Use **The SEO Framework** (free) for titles, descriptions, social tags and the sitemap. Make the theme step aside when a plugin is active, but still output what plugins skip: Person, Article, Service, CreativeWork structured data.
- Settings to check: homepage title, description, **homepage social titles** (empty social titles fall back to a stripped title), a default 1200x630 social image, noindex for search and 404, sitemap on.
- The sitemap lists posts and pages only. Add overview pages and non-empty categories with the `the_seo_framework_sitemap_additional_urls` filter (array of `url => ['lastmod' => 'Y-m-d H:i:s']`).
- Verify live: fetch every URL in the sitemap and check status, title, description, canonical, robots, one H1, image alts, JSON-LD parses.
- Back-date archive posts to their true year if the human wants honest dates.

## 10. Analytics and search engines

- **Google Site Kit** connects Analytics and Search Console. The human signs in to Google. Never enter their credentials.
- Site Kit does not track signed-in admins by default. Test in a private window while signed out.
- After a form submit the site redirects with `?gs_sent=1`. In Analytics create an event `inquiry_sent` from `page_view` where the URL contains `gs_sent=1`, and mark it as a key event.
- Rejected spam redirects with `gs_sent=2` so it is not counted.
- Submit `sitemap.xml` in Search Console and Bing Webmaster Tools. Bing verification can be a meta tag pasted into The SEO Framework's Bing field.
- New properties show no data for a day or two. Do not panic.

## 11. Spam protection (after the first fake inquiry)

Layers that were added, in order:
1. Nonce.
2. Hidden honeypot field.
3. Per-visitor rate limit (5 an hour).
4. A page timer set by JavaScript (`gs_elapsed`); reject under 3 seconds or missing.
5. Reject 15 or more ticked services, or 2 or more links in the text fields.
Reject silently (show the thank-you, keep nothing, use a different success parameter).

## 12. Pitfalls and fixes seen in this build

| Symptom | Cause | Fix |
|---|---|---|
| Redirects and login behave oddly after saving files | A BOM at the start of a PHP file. PowerShell `Set-Content -Encoding utf8` adds one | Write files with a tool that does not add a BOM and check the first 3 bytes before zipping |
| Upload "did nothing" | Server OPcache holds old compiled PHP | Restart PHP or ask the host. Verify by reading the version the page stamps on assets |
| Stray `<p>` and `<br>` inside layout | `wpautop` on shortcode output | Use dynamic blocks |
| Image or SVG disappears in a zero-width box | Global `img, svg { max-width: 100% }` collapses it | `max-width: none` on that element |
| Menu transform applied twice | WordPress puts `wp-block-navigation` on both the `nav` and the `ul` | Target `nav.wp-block-navigation` only |
| Header controls sit too low when the bar shrinks | A transform used to align with the logo wordmark at the top still applied in the compact state | Reset the transform in the scrolled state; verify by comparing the midlines of the bar, logo and controls |
| Button overflows its card | `width: 100%` plus padding without border-box | `display: flex; box-sizing: border-box; max-width: 100%` |
| A crash on a single quote in PHP | Apostrophe inside a single-quoted PHP string | Use a typographic apostrophe or escape it |
| Spam inquiry with every box ticked | Bot ignored honeypot | Section 11 |

## 13. What to hand the human at the end

- The live site, tested on a phone and in a private window.
- A short list of what is left for them (keys to paste, Google sign-ins, testimonials, the first posts).
- A backup plan (daily backups on the host, plus the last theme zip).
- The rules list, so the next session keeps the same standards.

## 14. A test you can run on yourself

Before saying "finished", answer yes to all: Did I look at the real pages at phone and desktop width? Did a test inquiry arrive in both the admin and the inbox? Does every page have a unique title and description? Is the live version the one I think it is? Did I put any secret anywhere I should not have?
