Cambermast helps organizations adopt AI with confidence. We combine hands-on experimentation with practical governance so teams can learn quickly, prove value, and scale their AI initiatives responsibly.
- AI Training: Interactive workshops that teach modern prompting techniques, AI-assisted coding, collaboration patterns, and governance frameworks your teams can apply immediately.
- AI Agents: Design and delivery of custom AI assistants that automate knowledge work, streamline operations, and integrate with the tools you already use.
- AI Advisory: Executive coaching and strategic roadmaps that connect AI opportunities to measurable business outcomes.
Cambermast is led by CEO Bill Raymond, a certified FutureLab AI Mastermind Trainer, founding member of The GenAI Collective, and host of the Agile in Action podcast. Bill partners with clients to turn AI curiosity into lasting capability.
This repository contains the former public GitHub Pages marketing site for Cambermast. The production cambermast.com site is now owned by the private BillRaymond/cambermast-web repository and its VPS deployment.
This legacy repository may continue publishing at GitHub's repository Pages URL as a best-effort historical snapshot. It is not the current production site and may not preserve every route or form behavior outside the former custom domain. It must not configure or reclaim cambermast.com or www.cambermast.com.
This repository publishes only to GitHub Pages without a custom domain:
- Edit locally in the VS Code Dev Container.
- Push to GitHub.
.github/workflows/pages.ymlbuilds the static site and deploys GitHub Pages.
The Pages workflow publishes deploy-sha.txt so each deployment can be matched back to the exact Git commit that produced it.
This repository must not build or publish VPS/GHCR deployment artifacts. The private BillRaymond/cambermast-web repo owns the ghcr.io/billraymond/cambermast-web/app image and the VPS migration path.
- SvelteKit + Svelte 5: GitHub Pages uses the static adapter for the legacy Pages snapshot.
- Vite 7 toolchain: Dev/preview/build scripts live in
web/package.json, providing instant feedback viavite devand optimized output viavite build. - TypeScript-first: The Svelte project runs
svelte-checkwith a sharedtsconfig.json, giving type safety across components, data sources, and endpoints. - Tailwind CSS 4: Base styles, typography, and form controls are composed with Tailwind plus the official
@tailwindcss/formsand@tailwindcss/typographyplugins. - Linting & formatting: ESLint 9 +
eslint-plugin-svelteensure component quality, and Prettier (with Tailwind + Svelte plugins) keeps the codebase consistent.
- The repo-owned Docker image is the source of truth for preinstalled development tooling.
- The current base image is Node
20.20.2on Debian Bookworm slim, pinned by digest inDockerfile. - The image preinstalls core repo tools and troubleshooting utilities used in daily work:
git,jq,ripgrep,curl,procps,iproute2(ss),poppler-utils,libvips, and the browser/runtime packages required for Playwright. - The image also preinstalls
@openai/codexglobally and bakes Playwright browsers into/ms-playwrightso fresh devcontainers can run e2e tests without downloading browsers during startup. - The devcontainer mounts
web/node_modulesas a volume. On startup,web/scripts/start-vite-dev.shtreatsweb/package-lock.jsonas authoritative and automatically runsnpm ciwhen dependencies are missing, stale, or failnpm ls --depth=0. - If the dependency volume ever gets stuck in a bad state, recover with
rm -rf web/node_modules && npm --prefix web ciinside the container, or recreate the named volume from your container tooling and restart the devcontainer. - Playwright browsers are intentionally shared outside the workspace. The devcontainer/image uses
/ms-playwright, while GitHub Actions caches a runner temp Playwright browser directory between runs so browser binaries do not accumulate under the repo working tree.
- In June 2026, re-check
.github/workflows/pages.ymlagainst the latest GitHub Actions releases and remove the temporaryFORCE_JAVASCRIPT_ACTIONS_TO_NODE24=trueworkflow setting onceactions/deploy-pagespublishes a Node 24-compatible release or GitHub documents that it is no longer needed. - Add tagged-PDF/accessibility metadata support to the training brochure generation workflow so downloadable brochures are more standards-friendly.
- Global navigation controls (
web/src/routes/+layout.svelteandweb/src/lib/components/Nav.svelte) exposearia-label,aria-controls, andaria-expandedso toggles and links announce their state to assistive tech. - Disclosure menus on the homepage (e.g., the “Connect” fly-out in
web/src/routes/+page.svelte) connect buttons to panels with generated IDs and keeparia-expanded+aria-labelledbyin sync for screen readers. - Contact flows (
web/src/routes/contact/+page.svelte) mark required fields witharia-hiddenindicators, track submission state witharia-busy, and surface responses inside anaria-live="polite"region. - Interactive carousels (
web/src/lib/components/home/UpcomingSessionsCarousel.svelte) definearia-roledescription="carousel", label previous/next controls, and announce slide changes with a screen-reader-only live region. - Call-to-action components (e.g.,
ServiceCard.svelte,ReviewCard.svelte) use descriptivearia-labeltext andaria-currentattributes so multi-state cards and rating elements convey intent beyond color or layout.
llms.txtis our machine-readable README for language models. It highlights Cambermast's offerings, authoritative sources, and citation expectations so retrieval systems can answer accurately. Update it whenever services, messaging, or proof points change.ai.txtis our AI usage policy. It states how Cambermast content may be used for AI training, inference, or summarization, and lists the contact path for additional permissions. Keep it aligned with legal, marketing, and partnership requirements.
These two documents remain the source of truth for this legacy Pages snapshot. The SvelteKit app copies them into web/static/ during npm run dev, npm run build, and npm run preview. Current production copies for cambermast.com are owned by BillRaymond/cambermast-web.
Generated build output under web/build is disposable and must not be treated as source. Do not archive copied builds inside web/ using folders such as build-prev, build-stale, or collision-style names like about 2.html; if a backup is needed, store it outside the repo working tree or use one clean archive location without numeric suffix copies.
- Run
npm --prefix web run clean:artifactsto remove disposable generated outputs, Playwright run artifacts, and any archived generated trash from prior runs. web/build,web/build-prev,web/build-stale,web/.svelte-kit/output,web/test-results, andweb/playwright-reportare safe to delete; they are recreated by normal build or test flows.web/scripts/reset-generated-output.tsnow keeps only the newest two archived snapshots per generated target in/.generated-trashso resets stay debuggable without growing unbounded.- Do not delete
/ms-playwrightas part of routine cleanup inside the devcontainer; it is the shared browser store that keeps Playwright and brochure generation fast.
/training-programsredirects to/trainingto support legacy or marketing links to the catalog./eventsis the canonical public calendar of events route./events/archiveis the public past-events archive route./calendarredirects to/eventsas a legacy alias./feed/events.xmlis the canonical Calendar of Events RSS feed;/feed/calendar.xmlis a legacy compatibility alias.- Legacy path aliases live in
web/src/lib/data/redirects/redirects.json, publish at/api/redirects.json, and can be edited in development from/admin/redirects. /campaignsredirects to/admin/campaigns(internal registry).
web/src/lib/view-models/event-card.tsis the canonical source for event card display data (EventCardModel+toEventCardModel(...)).- The
/eventscalendar card contract is the baseline for all event card surfaces, including home carousel and training/agents card embeds. - Event data is authoritative. Training program data is fallback-only for card image, certificate label, and trailer URL when event values are missing.
web/src/lib/components/events/EventCard.sveltesupports constrained visual variants (calendar,carousel,catalog). Variants may change density/layout only and must not change the canonical field set.
When creating a new training event, use this strict flow:
- Pick the training program (by
sku). - Pull duration + daily time commitment from the program
scheduleTemplate. - Generate a draft event schedule, then copy the resulting
draftEventintoweb/src/lib/data/events/events.json.
Run:
npm --prefix web run events:draft -- --program-sku CM-TR-005 --start-date 2026-03-17 --id 7iu8p4 --slug ai-workshop-for-tech-writers-and-content-creators-spring-2026
By default, the generated event page slug is suffixed with the campaign id (example: ai-workshop-for-tech-writers-and-content-creators-spring-2026-7iu8p4) to prevent accidental overwrites.
Optional overrides:
--id(6-char base36; auto-generated if omitted)--campaign-id(6-char base36; defaults to--idwhen omitted)--start-time(example13:00)--duration-days(overrides training default)--hours-per-day(overrides training default)--overwrite(allow collisions with existing event/campaign ids or slugs)--no-slug-suffix(skip the-<campaignId>slug suffix; not recommended)
Output includes:
program.scheduleTemplate(source values)scheduleDraft(generated date/time window)draftEvent(ready-to-paste event object with lockedtemplate.kind = training_event_v1, explicitschedule, copied program content for description/highlights/audience/build/outcomes/agenda/FAQ/images/videoUrl, and the default upcomingOpen soonregistration state:registrationStatus = "none"with an emptycta.url)draftCampaign(ready-to-paste campaign entry forweb/src/lib/data/campaigns.json)
New training-event drafts default to an unpublished registration state until a real registration URL is added. The canonical contract for that state is registrationStatus: "none" with cta.url omitted/empty; public UI renders it as Open soon.
Every event now requires a linked campaign. Keep event.campaignId, event.cta.campaignId, and campaign.id aligned, and keep the event-backed campaign.landingPath set to /events/<event.slug>.
If existing event records drift out of sync, run npm --prefix web run campaigns:backfill-events to backfill missing event campaigns and re-link the event registry before validating.
web/src/routes/gdpr/+page.svelteis the canonical privacy notice for cambermast.com, covering lawful bases, vendor list, and contact options for data requests. Update it whenever we add a new form, vendor, or processing purpose.- The same page contains the cookie disclosure: only Cloudflare Turnstile runs by default for security and Google Analytics 4 loads if a visitor accepts analytics cookies. The consent state is stored in
cambermast-consent-v1and can be reset from the footer link. - If privacy messaging or cookie behavior changes, edit the GDPR page first, then note the change in
README.md,llms.txt, and any marketing copy so policy, AI guidance, and UI stay aligned.
web/src/routes/training/terms/+page.svelteis the canonical set of Training Terms (refunds, credits, transfers, conduct, materials, disputes).- Include a visible link to
/training/termson every registration form, landing page, or program overview so attendees know where to review policy details. - When policy language changes, update that file first and then refresh any marketing copy or registration emails that describe the guarantees.
- Training brochure content should preserve the full published curriculum. Do not hard-cap agenda length for the print/PDF brochure.
- Existing hard page breaks in brochure layouts are intentional editorial choices and must not be removed unless explicitly requested.
- Brochure QA should focus on content quality, visual cleanliness, and working links. There is no required brochure page count.
- In development, brochure footer links should resolve against the active local server origin; in production they should resolve against the canonical site origin.
npm --prefix web run devnow starts a brochure PDF watcher alongside Vite. It rebuilds PDFs only when brochure-impacting sources change, instead of rebuilding on every refresh.- The devcontainer auto-start path (
./web/scripts/start-vite-dev.sh) now uses that same watcher-enabled dev flow while still serving Vite on0.0.0.0:5173. - In the devcontainer, the post-start command launches Vite and the brochure watcher in the background, then exits. Seeing
Done. Press any key to close the terminal.after startup is expected; the site should still be available athttp://localhost:5173/. - The current brochure watcher contract includes:
web/src/routes/(plain)/training/[slug]/print/+page.svelteweb/src/routes/(plain)/training/[slug]/print/+page.tsweb/scripts/generate-training-pdfs.tsweb/src/lib/data/training/**web/src/lib/data/training/brochure.tsweb/src/lib/data/testimonials.jsonweb/src/lib/data/testimonials.tsweb/src/lib/data/partners.tsweb/src/routes/about/+page.svelte- brochure-linked
/images/**assets referenced by training brochure data, testimonials, the print route, and the About page
- Generated brochure PDFs are written to
web/static/downloads/training/. - On startup, the watcher compares brochure-input mtimes to the generated PDFs and only rebuilds when the PDFs are missing or stale.
- Use
npm --prefix web run generate:training-pdfs:devwhen you want a manual rebuild outside the watcher flow.
- Printable resource PDFs reuse the same overall pattern as training brochures: a dedicated print route, a Playwright-based PDF generator, and published files under
web/static/downloads/resources/. - PDF availability is canonicalized per resource in
web/src/lib/data/resources/resources.jsonvia thepdfblock. Ifpdfis absent, that resource is not printable. - The first printable resource is
web/src/routes/resources/ai-coding-prompt-guide/+page.svelte, with its print route atweb/src/routes/(plain)/resources/[slug]/print/+page.svelte. npm --prefix web run devnow starts both the training brochure watcher and the printable-resource watcher alongside Vite.- The printable-resource watcher rebuilds only the affected resource PDF for direct resource-specific changes. Shared printable-resource infrastructure changes rebuild all enabled resource PDFs.
- Training changes do not rebuild resource PDFs unless a printable resource explicitly depends on a training-domain file.
- Use
npm --prefix web run generate:resource-pdfs:devwhen you want to manually rebuild printable resource PDFs during development.
- Use
web/src/lib/components/forms/TurnstileField.sveltefor Cloudflare Turnstile in new forms. - For webhook-backed submissions, use
web/src/lib/utils/form-submission.ts:postJsonWithTimeout(...)for consistent timeout handling.getWebhookSubmissionErrorMessage(...)for consistent timeout/network/CORS messaging.
- Keep webhook error payloads compatible with existing parsing patterns used by forms:
message,error,error-codes, ormessages. - Do not strip or pre-escape ordinary user text such as quotes, newlines, or backslashes on the front end. The browser payload should remain normal JSON.
- In n8n, avoid raw JSON templates that place form values inside quoted JSON strings, such as
"name": "{{ $json.body.name }}". Use field mappings or serialize the full object withJSON.stringify(...)so names, textarea responses, and option labels cannot break the workflow.
web/src/routes/services/microsoft-project-server/+page.svelteis the dedicated page explaining how Cambermast and Project Hosts keep Microsoft Project Server online after Microsoft ends direct support.- Facts on that page cite Microsoft Lifecycle articles for Project Server 2013/2016/2019 and the official migration guidance. Update those references if Microsoft publishes new dates or tooling.
- The homepage CTA under “Are you a Microsoft Project Server customer?” now links to this route. Keep the Project Hosts green button styling consistent whenever you adjust copy.
When publishing significant messaging or offer changes:
- Review
llms.txtto ensure the mission summary, offerings, and authoritative sources reflect the new messaging. - Review
ai.txtfor any policy implications (new assets, licensing, or restrictions). - Review this
readme.mdto ensure it reflects relevant updates. - Keep structural metadata current: update
web/static/robots.txtandweb/src/routes/sitemap.xml/+server.tswhenever routes, slugs, or canonical URLs change so crawlers pick up the new paths. - Ensure the training worksheet updates.
- Use
web/src/routes/about/+page.svelteas the default page scaffold for new standard pages. - Keep top-level content aligned to the shared layout container; avoid extra nested
mx-auto max-w-*wrappers around the page heading block unless intentionally designing a custom layout. - Use this heading baseline by default:
h1:mb-6 text-3xl font-bold- Intro copy:
max-w-3xl text-gray-700
- Store approved headshots under
web/static/images/testimonials/. - Crop uploads to a square (1:1) at roughly 300×300px so they render crisply inside the circular avatar treatment.
- Save as optimized
.jpgor.png(WebP is fine too) and keep files under ~150 KB to avoid bloating static pages. - Update
photoUrlinweb/src/lib/data/testimonials.jsonto point at/images/testimonials/<filename>whenever you add or replace a photo.
- Canonical generated-image storage lives under
web/static/images/<type>/<slug>/. - Supported typed destinations are
events,training,resources,featured-images, andcustom. - The site image contract is fixed to square
1024x1024(1:1), horizontal1536x864(16:9), and portrait1024x1280(4:5). Use the horizontal variant for website heroes, event cards, and link previews; use the 4:5 variant for LinkedIn and other mobile-first social posts. - The dev-only generator uses OpenAI's
gpt-image-2through the Image Generation API guide: JSON requests go to image generations, while template edits use multipartimage[]uploads to image edits. Keep the configuredsizeexact and review every output for accidental text, logos, signage, and edge cutoffs before saving. - The admin image generator shows the full MinIO browser URL for every generated candidate and supports copy/paste directly from the UI.
- Keep the current live generated set for each slug folder and prune older superseded versions when doing cleanup.
- Saving a new selected set still versions existing files with
-v2,-v3, etc. rather than overwriting prior outputs. - Entity image state is canonicalized in
images.currentwith square, landscape, portrait, prompts, reference provenance, andhistoryId. - Default image fallbacks live in
web/src/lib/data/default-images.jsonand publish at/api/default-images.json.
- See
SCHEMA-FIRST.mdfor the required schema-first workflow, validation gates, and propagation checklist for any data contract changes. - Any new registry schema must include a public read-only API contract (API route + API response schema + API payload builder) and admin SOP coverage in a relevant
/admin/sop*page. - FAQ preset registry lives at
web/src/lib/data/faq-presets/faq-presets.jsonand publishes at/api/faq-presets.jsonfor reusable training and event FAQ starters. - Redirect registry lives at
web/src/lib/data/redirects/redirects.jsonand publishes at/api/redirects.jsonfor public alias auditing. - Run
npm --prefix web run validate:schema-governanceto enforce schema-to-API-to-SOP coverage.
/admin/campaignssupports campaign CRUD in development only.- Production keeps
/admin/campaignsread-only; no campaign mutation controls or write paths should be exposed there.