Skip to content

OnTargetDevs/OnTargetABA.com

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

155 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

On Target ABA — site

Static marketing site for On Target ABA. Deployed via Cloudflare Pages from this repo.

Quality care without the wait. ABA therapy & autism testing for kids in Cleveland, Columbus, and Salt Lake City.

Repo layout

website/
  *.html                     31 public pages (services, locations, blog, legal, …)
  blog/post.html             Runtime markdown renderer for /blog/posts/{slug}
  assets/
    css/app.css              Design tokens, animations, components
    js/
      app.js                 Scroll reveal, sticky nav, mobile nav, FAQ, marquee mirror
      header.js              Shared announcement bar + nav + breadcrumb (active-page state)
      footer.js              Shared footer with the "Built by Shalom Karr" credit chip
      leadbot.js             Floating intake widget (lazy-loaded by app.js)
    images/                  Logo + photography
    fonts/                   Self-hosted Plus Jakarta Sans + Fraunces (woff2, Latin subset)
    blog/
      *.md                   161 blog posts as markdown + YAML frontmatter
      index.json             Generated by scripts/build-blog-index.py
    og/                      Per-page Open Graph SVGs (generated by gen-og-images.mjs)
  scripts/                   Build-time scripts (see "Scripts" below)
  _redirects                 Cloudflare Pages rewrites (pretty blog URLs)
  _headers                   Security headers, MIME, cache policy
  build.sh                   CF Pages build entry point
  sitemap.xml, robots.txt    Generated by scripts/build-sitemap.py
  c8f5d3a1….txt        IndexNow ownership-verification key file
billboard.svg                Highway billboard concept
CLAUDE.md                    Per-project notes for future Claude sessions
README.md                    This file

Tech

  • HTML + Tailwind CSS via CDN, configured inline per page.
  • Custom CSS in assets/css/app.css for design tokens, scroll animations, marquee, accordion, target/bullseye mark, lead-bot widget styles.
  • Vanilla JS, no framework. Header + footer injected into <div id="site-header"> / <div id="site-footer"> placeholders.
  • Fonts self-hosted (Plus Jakarta Sans + Fraunces, woff2, Latin subset).
  • Runtime markdown rendering for blog posts via marked.min.js + DOMPurify (CDN).

Brand palette

Token Hex Use
ink #163243 Body type, footer
ink-soft #34495E Secondary text
teal #00B7EA Bright accent (links, ornaments)
teal-deep #0E5E6E Body emphasis, dark gradients
coral #E84F3B Primary CTAs, italic headline emphasis
sun #F4C842 Highlights, announcement bar
sage #C5E0D5 5th accent — sage-soft cards
cream #FAF5E6 Warm page background
line #E8DFD0 Hairline borders

Local preview

No build step is required to view the static pages. From website/:

python -m http.server 8000
# &rarr; http://localhost:8000

(Opening index.html from file:// also works for everything except the blog renderer, which needs fetch() of the markdown files.)

Deployment (Cloudflare Pages)

CF Pages project settings:

  • Root directory: /website
  • Build command: bash build.sh
  • Build output directory: .

build.sh runs every push to main:

  1. python3 scripts/build-blog-index.py
  2. node scripts/gen-og-images.mjs
  3. python3 scripts/inject-seo.py
  4. bash scripts/setup-fonts.sh
  5. node scripts/selfhost-fonts.mjs
  6. node scripts/add-skip-link.mjs
  7. python3 scripts/build-sitemap.py
  8. python3 scripts/indexnow-ping.py (or --all if SITEMAP_FULL_PING=1)

_redirects rewrites /blog/posts/*/blog/post. Don't use /blog/post.html as the destination — CF auto-strips .html, which would turn the rewrite into a 308 and drop the slug. The extensionless form resolves to post.html internally without touching the URL bar.

Scripts

Everything under website/scripts/ runs at build time. Each is idempotent and safe to re-run.

Currently in the repo

Script Runtime What it does
build-blog-index.py Python Scans assets/blog/*.md, parses frontmatter, emits assets/blog/index.json with { posts: [&hellip;], categories: [&hellip;] }. The blog landing fetches this; the post renderer uses it for "related posts."
gen-og-images.mjs Node Generates 1200×630 Open Graph SVGs into assets/og/ — one per blog post + one per main funnel page. Skips slugs whose SVG is newer than index.json. No npm dependencies.
inject-seo.py Python Re-injects schema.org JSON-LD @graph (Organization + MedicalBusiness + LocalBusiness for 4 clinics + WebPage + BreadcrumbList + Review + AggregateRating + per-page-type extras), OG/Twitter meta, canonical link, theme color. Idempotent via <!-- auto-seo-start --> / <!-- auto-seo-end --> markers. Per-page metadata lives in the SEO_PAGES dict.
setup-fonts.sh Bash Downloads Plus Jakarta Sans + Fraunces (Latin subset, weights 400-800) from Google Fonts and writes them to assets/fonts/{family}-{weight}.woff2. Skips files that already exist.
selfhost-fonts.mjs Node Alternative font-fetch path that parses Google's CSS response and downloads the woff2 URLs it references. Provides a fallback path if setup-fonts.sh fails.
add-skip-link.mjs Node Sweeps every HTML file and ensures it starts with <a href="#main-content" class="skip-link">Skip to main content</a> and that the first <section> after the header has id="main-content". Idempotent.
build-sitemap.py Python Reads assets/blog/index.json and the static-page registry inside the script, emits sitemap.xml (URLs use the production canonical domain) and robots.txt.
indexnow-ping.py Python POSTs recent URLs (default: lastmod within 14d) from sitemap.xml to api.indexnow.org. --all pings every URL. Non-fatal — a failed ping doesn't break the deploy. Key + key file live at the deploy root.

Removed (one-off sweeps that have already done their work)

These were run once during initial migration, their changes are baked into the HTML / assets, and they're no longer needed. If a future task needs the same shape, the description here is enough to rebuild it.

Removed What it did
add-blog-nav.py Inserted a Blog link into the desktop nav on every page. Now lives in header.js.
download-assets.ps1 One-shot PowerShell that walked a manifest of asset URLs and downloaded each to assets/images/. Originally used to mirror images off the legacy WordPress site. Replace with a 20-line script: Invoke-WebRequest in a loop.
embed-jotforms.py Replaced placeholder <form> blocks on pre-intake-form.html, contact.html, and autism-testing.html with <script src="https://form.jotform.com/jsform/{ID}"> embeds. Form IDs are documented in CLAUDE.md.
fix-encoding.ps1 Normalized every HTML file to UTF-8 + BOM, decoded mojibake (&acirc;&dagger;'&rarr;), replaced literal Unicode glyphs with HTML entities. Re-create with a ReadAllBytes / WriteAllBytes PowerShell loop + a hex-codepoint glyph table. Only needed if agents touch HTML and re-introduce raw glyphs.
qa-check.ps1 Swept HTML for broken local links, missing image references, and leftover hex from prior palettes. ~50-line PowerShell using regex + Test-Path.
recolor.ps1 Case-insensitive regex sweep that maps legacy palette hex values to the current Figma palette across HTML/CSS/JS/SVG. Re-create from the swaps hashtable structure: each entry is '#OLDHEX' = '#NEWHEX'.
swap-logo.ps1 One-time sweep that replaced the placeholder bullseye-and-text logo with <img src="/assets/images/footerImg.png"> in nav and footer. Header.js now owns the nav logo; footer.js owns the footer logo. Not needed again.
sync-tailwind-config.ps1 Sweep that rewrote every page's inline <script>tailwind.config = {&hellip;}</script> block to match the current palette. Re-create with a single [regex]::Replace against the multiline tailwind.config pattern.

IndexNow

  • Key: c8f5d3a1e947b2f6a4c1b9d8e6f3a2b5
  • Key file: website/c8f5d3a1e947b2f6a4c1b9d8e6f3a2b5.txt
  • Endpoint: https://api.indexnow.org/IndexNow (fans out to Bing, Yandex, Seznam, Naver)
  • Override window: set SITEMAP_FULL_PING=1 in CF dashboard env vars to ping every URL on the next deploy.

Admin dashboard

/admin is a private Google-OAuth–gated UI for editing every page, post, header, and footer in this repo without leaving the browser.

Sign in with any allow-listed Google account (configured via the ADMIN_EMAILS env var). After OAuth, you land on a dashboard with four tiles: Pages, Posts, Header, Footer. Every save becomes a Pull Request on OnTargetDevs/OnTargetABA.com so the repo history is the audit log — nothing pushes straight to main.

What the editor can do

  • Edit any text on the page. The editor walks the live page in an iframe and overlays Edit handles on every text-bearing element (h1–h6, p, li, td, button, a, span/strong/em). Click, type, press Enter to commit.
  • Visual Tailwind styling. When you click into a text node, a floating toolbar offers bold / italic / underline / line-through / uppercase toggles, font-size cycle (xs–7xl), font-family (sans / display), brand color swatches, and border-radius. Changes are saved as a full Tailwind className string on the override and replayed at runtime on every public view.
  • Mobile / tablet / desktop preview. Topbar viewport toggle resizes the iframe to 390 / 820 / full-width so you can verify layouts on every form factor without opening DevTools.
  • Hide any section. Every <section>, <aside>, <header>, <footer>, <article>, <main> gets a coral corner toolbar with Hide / Save as template / Replace / Insert after.
  • Save a section as a template. Captures the section's outerHTML into assets/templates/sections/{name}.html via PR. The Replace and Insert-after options on every section toolbar list every saved section template for one-click reuse.
  • Image upload. Every <img> gets an Image handle. The modal lets you upload a file (committed to assets/images/uploads/{yyyy-mm}/ via PR) or paste a URL.
  • Per-page SEO panel. Collapsible details box with title, description, keywords, canonical, OG title/description/image, Twitter image. Stored at assets/data/pages/{slug}.seo.json and applied at runtime by page-overrides.js.
  • Page registry actions. /admin/pages.html lists every page; per-row toggles for Hide, Draft, Delete. Each flips a flag in assets/data/pages.json (always written as { schemaVersion: 1, pages: [...] }) and regenerates sitemap.xml in the same PR so search engines drop hidden pages.
  • Blog post editor. /admin/post-editor.html has form-driven frontmatter (title, slug, date, category dropdown, author, excerpt, read_time, hero_image URL) plus a markdown textarea with live marked.js + DOMPurify preview. Save Draft or Publish both submit a PR; on merge, CF Pages rebuilds the blog index and sitemap.
  • Header / Footer editors. Form-driven, round-trips assets/data/header.json and footer.json. Drag-reorder for nav links and footer columns; per-page breadcrumb chain editor.
  • Drafts visible to admins on the real URL. When you toggle a page to draft, the public gets 404, but a logged-in admin sees the latest draft content at /{slug} — no special preview path. Powered by the catch-all functions/[[path]].js Function.

The automation loop

Save in /admin
  -> Function commits files to admin/<kind>-<slug>-<uuid> branch
  -> Function opens PR with the change(s) + regenerated artifacts
  -> validate-content workflow lints frontmatter + JSON
  -> auto-merge-admin workflow squash-merges on success
     (the free-plan equivalent of GitHub's native auto-merge,
      which Pro-gates private repos)
  -> push to main triggers CF Pages deploy
  -> purge-cache workflow waits for the matching deploy then
     purges the ontargetnotes.com Cloudflare zone
  -> custom domain serves the new state

Typical end-to-end: 60–120 s. You can watch the PR list at github.com/OnTargetDevs/OnTargetABA.com/pulls if a change isn't appearing — merge conflicts or validation failures stop here without affecting main.

Configuration

Required env vars in the Cloudflare Pages dashboard (all marked as secret_text — CF's API silently drops plain_text mixed into a multi-value PATCH):

Name Purpose
GOOGLE_CLIENT_ID Web OAuth client ID
GOOGLE_CLIENT_SECRET Web OAuth client secret
GOOGLE_REDIRECT_URI https://website.ontargetnotes.com/OAuth/Callback — case-sensitive, must match Google Cloud Console allow-list
ADMIN_EMAILS Comma-separated Google accounts permitted to sign in
JWT_SECRET 64-char hex used to sign the ota_admin cookie. Rotate to invalidate all sessions.
GITHUB_TOKEN Fine-grained PAT scoped to OnTargetDevs/OnTargetABA.com with Contents R/W + Pull requests R/W + Metadata R
GITHUB_REPO OnTargetDevs/OnTargetABA.com
GITHUB_BRANCH main

When something feels stuck

Symptom Likely cause Fix
/api/auth/google returns 1101 Worker exception Env vars not set in CF dashboard PATCH each one (all as secret_text) and trigger a fresh deploy
Google says redirect_uri_mismatch after sign-in Case mismatch with the URI in Google Cloud Console Console allow-list must read exactly https://website.ontargetnotes.com/OAuth/Callback (capital O and C)
Pages / posts list is empty in the admin A Function tried to call the GitHub Contents API without the website/ prefix Already handled by REPO_PREFIX in functions/_utils.js; if you saw this, the deploy hasn't picked up the latest yet
Edits don't appear after merging The Cloudflare CDN zone hasn't flushed The purge-cache.yml workflow handles this automatically; if it didn't run, purge manually via the CF dashboard

Notes

  • Jotform IDs and other production integration IDs are documented in CLAUDE.md.
  • cookie-consent.html is a placeholder shell — the original is a Termageddon runtime embed; wire the real script before launch.
  • All forms use onsubmit="event.preventDefault(); &hellip;" placeholder handlers on top of the Jotform iframe so the styled wrapper degrades cleanly.

Documentation

In-depth references live under docs/:

  • docs/ADMIN_DASHBOARD.md — user-facing walkthrough of the /admin dashboard: sign-in, editing pages, creating blog posts, and how each save becomes a PR.
  • docs/DEPLOYMENT.md — Cloudflare Pages configuration, required environment variables and secrets, the build pipeline, and the rollback procedure.
  • docs/ARCHITECTURE.md — how the static site, the admin dashboard, and the Pages Functions fit together (auth flow, draft-preview catch-all, PR workflow).
  • docs/CONTENT_AUTHORING.md — blog frontmatter conventions, page templates, and editorial style notes.

License & Contributing

  • See LICENSE for the proprietary, all-rights-reserved license terms (© 2026 Shalom Karr).
  • See CONTRIBUTING.md for the PR workflow, commit conventions, branch naming, and local development setup.

License

Content © On Target ABA, LLC. Markup and design © 2026.

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors