Science-backed parent education for children 0 to 5, open to any parent who seeks it. Learn what your child is building and how to respond with the confidence that comes from knowing the research.
翻译准备中
教育GitHub @growingmindsscience在线运营者未确认
试用过了吗? 点一下就好,无需登录。
尚未收集仓库信息。
尚未检测到更新。GitHub 发布版本后会显示在这里。
制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Static site for Growing Minds Science, currently deployed on Vercel.
The site sells research-based, self-paced, asynchronous parent classes for families of children ages 0–5. The homepage is structured as a sales-oriented landing page; the milestone tracker remains a standalone static tool; the first flagship class (Toddlerhood) has a dedicated detail page.
/ (index.html) — pure HTML/CSS/JS, no build step/classes/toddlerhood.html (pretty URL: /classes/toddlerhood)/milestones.html (pretty URL: /milestones)High-level shape (not every file — see the directories for the full set):
growing-minds-science/
├─ index.html # Sales homepage
├─ about/ contact/ faq/ # Supporting marketing pages
├─ classes/ # 5 class pages (toddlerhood live; others waitlist)
├─ articles/ # Articles hub + 8 research/practical explainers
├─ tools/ # Tools-for-parents hub + Growing Minds AI + decoders
├─ milestones.html # Standalone tracker; no framework bundle
├─ thank-you.html # Waitlist success page
├─ api/ # Vercel functions (AI, waitlist, contact, auth, Stripe)
│ ├─ growing-minds-ai.js # Claude-backed chat (see "Growing Minds AI" below)
│ ├─ _retrieval.js _knowledge-data.js # Local grounding corpus + lexical retrieval
│ └─ … # waitlist, contact, login/session, stripe-webhook, …
├─ nsc/ # Number Path — self-contained Next.js app (own README)
├─ knowledge/ # Sources + curated cards compiled into _knowledge-data.js
├─ scripts/ # build-knowledge, build-structured-data, build-og-cards, …
├─ vercel.json # Vercel redirects + security headers (authoritative)
├─ robots.txt sitemap.xml README.md
└─ assets/
├─ css/ js/
└─ img/
├─ original-logo-mark-no-words-512.png # Logo mark (header, OG, apple-touch-icon)
├─ favicon.svg # Tab icon
├─ og/ # Generated per-article social cards (1200x630)
└─ site/ # Editorial photography
Legacy code removed: the pre-Vercel
netlify/functions/directory (an old OpenAI-basedgrowing-minds-ai.mjs) has been deleted, andnetlify.tomlwas already gone.vercel.jsongoverns andapi/*.jsare the live functions. When the Netlify sites are disconnected, also clear their environment variables.⚠️ Netlify is still connected to this repo, despite the config being gone — it builds on every PR and publishes at least two live, crawlable mirrors (
growingmindsscience.netlify.app, andgrowing-minds-science.netlify.appserving stale content). Because these serve the same pages from a different host, every page now declares an absolute canonical so the mirrors consolidate to www.fromlittleonward.com instead of competing with it (scripts/check-canonicals.mjsenforces this). The real fix is to disconnect or unpublish those Netlify sites — that has to be done in the Netlify dashboard, not here.
Six editorial photographs ship in assets/img/site/ and each one is used once
on the homepage in the role described below:
| File | Role |
|---|---|
hero-parent-baby.jpg | Homepage hero (right column desktop / under hero on mobile) |
baby-hands.jpg | Birth to 12 months class card thumbnail |
learning-blocks.jpg | Toddler class card thumbnail |
lens-1-play.jpg … lens-4-floor.jpg | The homepage Lens frames (frame 3, lens-3-run-back.jpg, also heads the full Lens page) |
family-reading.jpg | Family systems class card thumbnail |
notebook-coffee.jpg | "Why parents trust this" supporting image |
The About Matthew section is intentionally text-first so no stock image can be mistaken for a portrait or visually block the biography. If a real portrait becomes available, it can be added later with a clear caption.
Deploy the repository on Vercel. The domain can still be managed in Netlify DNS, but page hosting and API routes are expected to run on Vercel.
vercel.json serves /assets/css/* and /assets/js/* as immutable for a year,
so every reference carries a content hash (chrome.css?v=6f2055b923). After
editing any file in assets/css or assets/js, or adding a page that links one:
node scripts/stamp-assets.mjs
Commit what it rewrites. CI runs node scripts/stamp-assets.mjs --check and
fails on any stale or missing stamp. Two rules keep it working: write
lazy-loaded asset URLs in JS as one whole literal ("/assets/js/x-game.js",
never built from a variable), and on a merge conflict in a ?v= value take
either side and re-run the script. It is deterministic.
The waitlist posts to the Vercel API route at /api/waitlist, which forwards
validated submissions to Web3Forms without exposing the Web3Forms access key in
browser HTML.
Required Vercel environment variable:
WEB3FORMS_ACCESS_KEYSuccessful browser form submissions redirect to /thank-you.html.
The /login and /account pages use Vercel Edge API routes:
POST /api/login verifies one configured account and sets a signed,
HttpOnly, SameSite=Lax session cookie.GET /api/auth/google/start starts Google OAuth sign-in.GET /api/auth/google/callback completes Google OAuth sign-in.GET /api/session returns the current session state.POST /api/logout clears the session cookie.Required Vercel environment variables:
GMS_LOGIN_EMAILGMS_LOGIN_NAMEGMS_LOGIN_PASSWORD_SALTGMS_LOGIN_PASSWORD_HASHGMS_SESSION_SECRETOptional Google sign-in environment variables:
GOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRETGOOGLE_ALLOWED_EMAILS: comma-separated allowlistGOOGLE_ALLOWED_DOMAIN: single allowed email domainAt least one of the two allowlist variables is required (when both are set, an account must match both). With neither set, Google sign-in stays off instead of letting in every Google account.
Use this authorized redirect URI in Google Cloud for the OAuth client (the callback is built from the request's own origin, so it must match the host people sign in on):
https://www.fromlittleonward.com/api/auth/google/callback
Generate the password salt and hash locally:
node scripts/hash-password.mjs 'replace-with-a-strong-password'
Generate a session secret:
openssl rand -base64 32
This is a single-account login for member access on a static site. Use a real identity provider or database-backed auth before adding public self-service registration, password reset, or per-customer entitlements.
The homepage signup form asks which class the visitor is interested in with a
plain interest select; nothing preselects it (the old ?interest= query
string and class-card preselect were removed with the unused main.js
handlers). Each waitlist class page and the articles page send a fixed hidden
interest value instead.
The Growing Minds AI page at /tools/growing-minds-ai includes a working chat
backed by a Vercel API route:
api/growing-minds-ai.js/api/growing-minds-aiANTHROPIC_API_KEYANTHROPIC_MODEL (defaults to
claude-sonnet-4-6; set claude-opus-4-8 for depth or a Haiku id to cut cost)GMS_AI_ACCESS_CODE. Use a long
random value (for example openssl rand -hex 8), not a word, and rotate it
if it leaks.GMS_SESSION_SECRET (verifies AI Pro
subscriber tokens; also used elsewhere for the parent-site session cookie,
under a separately derived key)api/create-checkout-session.js only redirects
old links to /pricing. api/verify-subscription.js still reads
STRIPE_SECRET_KEY so existing subscribers can unlock until their
subscriptions end; the webhook reads STRIPE_WEBHOOK_SECRET. See
.env.example for the full list.Set environment variables in Vercel. The API key must never be placed in browser JavaScript or committed to the repository.
Grounding is local — no external vector store. Retrieval runs in-repo:
api/_retrieval.js scores the question against api/_knowledge-data.js with
lexical overlap + light stemming and passes the top cards to the model as
context. That knowledge base is compiled offline by
scripts/build-knowledge.mjs from three inputs (the 42 milestones in
milestones.html, curated cards in knowledge/curated.mjs, and your uploaded
material in knowledge/sources/). Rebuild after changing any input:
node scripts/build-knowledge.mjs
There is no embeddings service and no OpenAI dependency; the corpus is small and curated, so lexical scoring is a good fit (upgradable to embeddings later without touching callers).
Access tiers. The server checks every call. Class families get 50
questions a day (the fair-use limit the class pages state): counted per account
for a signed-in account whose session carries ai:unlimited (a class, a gift
code, or the legacy shared code's grant), and per client for a matching
GMS_AI_ACCESS_CODE (share only with enrolled class families) or a legacy AI
Pro subscriber token. Membership has no daily cap. Everyone else gets a free
allowance of 5 questions a day. The browser counter is UX only. The chat page
checks a code with a { "validateOnly": true, "accessCode": "..." } request,
which never calls the model. The endpoint accepts only same-origin JSON
(Content-Type: application/json; requests marked Sec-Fetch-Site: cross-site
are refused).
Shared and soft limits. The two daily allowances are counted in one
Supabase table (nsc/supabase/migrations/0021_ai_free_usage.sql) when
AI_USAGE_SUPABASE_URL, AI_USAGE_SUPABASE_SERVICE_ROLE_KEY and
GMS_SESSION_SECRET are all set; keys are HMACs of the day and the client or
account, never an address. Without them, or when Supabase does not answer, they
fall back to the memory of each Edge isolate. The burst limit (10 requests a
minute) and the wrong-code limit (8 an hour) are always per isolate, per IPv4
address or IPv6 /64: they reset on cold start and are not shared between
isolates or regions. None of this is a spending cap, so keep a monthly spend
limit on the Anthropic workspace that owns ANTHROPIC_API_KEY.
The tracker is intentionally a single static milestones.html file with inline
CSS, inline JavaScript, and embedded milestone data. It does not use React,
Vite, hashed bundles, or a milestones/assets/ folder. This avoids Netlify
manual-deploy edge cases where subdirectory app assets can be served as HTML
(which previously caused infinite reload loops).
A small shared layer makes the tools meet a parent in the moment and remember
their context — with no account, no build step, and no growth-hacking mechanics
(deliberately no streaks, badges, or notifications, per PRODUCT.md).
Shared files:
assets/js/gms-tools.js — a single window.GMS namespace: a local child
profile, a saved "shelf", the decoder enhancer, speechSynthesis narration,
a print helper, and the age-aware hub reordering. It mirrors the
main.js safe-storage pattern (localStorage with an in-memory fallback) and
namespaces its keys gms-*.assets/css/tools.css — styles for the decoder widget, profile chip/editor,
shelf, Save/Listen/Ask action row, the right-now page, and the print pocket
card. Built on existing tokens, so light/dark and reduced-motion come for free.Local storage keys (device-only, never sent to any server):
gms-child-v1 — {activeId, children:[{id, name, birthdate|null, band|null}]}.
A child has a birthdate (primary) or an age band (fallback). Age maps to
the same seven bands as the milestone tracker.gms-shelf-v1 — {items:[{id, key, toolSlug, toolTitle, toolUrl, title, body, tryLine, savedAt}]} for the "Saved for you" shelf.Features:
tools/index.html): an optional, local profile. When set,
age-relevant tool cards float to the front of their grid (data-age-min /
data-age-max in months on each .tool-card), and the milestone tracker
auto-opens to the child's band (via #band= hash or the profile).tools/right-now.html, pretty URL /tools/right-now):
a calm, one-tap crisis guide. Situation → short answer data lives in the page's
#router-data JSON. Each answer offers Save / Listen / Ask about this / Print.gms-tools.js toggle enhancer; the three
variant choosers (.scenario-*, .stage-*, .pattern-*) keep their own
toggle but receive the Save/Listen/Ask action row. All output articles are
saveable and narratable.gms-shelf-v1; the hub renders
it under "Saved for you".GMS.printCard() (wired to [data-print-card]) prints
only the .pocket-card block by toggling body.printing-pocket, leaving normal
Ctrl+P untouched./tools/growing-minds-ai?q=<question> prefills
the chat box (prefill only — it never auto-sends, so the free-question limit is
respected). "Ask about this" links use GMS.askUrl(), which prepends a coarse
age phrase (never the name or birthdate) when a profile exists.Smoke test: npm i -D playwright-core once, then node scripts/smoke-tools.mjs.
It serves the repo and drives Chromium through the profile, reorder, milestones
deep-link, all three decoder variants, the right-now router, the shelf, the print
card, and the AI prefill. The npm artifacts are gitignored so the static site
carries no package.json.
<h1> per page.aria-label or visible label.prefers-reduced-motion.localStorage and respects system preference on first visit.