Repository navigation
feat(ux): UX review batch — 31 P0/P1/P2 fixes - #11
Merged
Merged
Conversation
The /admin/ route was leaking into the public sitemap-0.xml and was indexable. This: - Adds a sitemap filter so /admin/* URLs are excluded - Adds Disallow: /admin/ to robots.txt - Adds a noindex prop to BaseLayout and uses it on the admin page Resolves P0-5 from the 2026-05-04 UX review.
Sections in /learn/* skipped h3 entirely — h2 (e.g. "Symmetry Elements") went straight to h4 children rendered by PropertyList and ComparisonBlock. That violates WCAG 1.3.1 and breaks screen-reader/LLM passage attribution. Both components now accept a headingLevel prop (default 3). SectionRenderer passes headingLevel=4 only when the components are nested inside a subsection (which already uses h3), preserving correct structure at every depth. Resolves P0-6 from the 2026-05-04 UX review.
Three pages displayed three different (and all stale) mineral counts: - / claimed "50+" - /gallery claimed "90+" - /404 claimed "94+" The live database has 159 families. New src/lib/stats.ts queries mineral_families and mineral_expressions at build time and exposes the counts via getSiteStats(); the three pages now consume that helper. Resolves P0-7 from the 2026-05-04 UX review.
Replace crystal-500/600 in text and button backgrounds with crystal-700 across all components. crystal-600 (#0284c7) failed AA on white at 4.5:1; crystal-700 (#0369a1) passes ~5.7:1 for body text and UI controls. - text-crystal-600 → text-crystal-700 (links, headings, hover states) - bg-crystal-500/600 with text-white → bg-crystal-700 (buttons, badges, active states) - from-crystal-500 to-crystal-600 → from-crystal-600 to-crystal-700 (gradients) - hover:bg-crystal-600 → hover:bg-crystal-800 - prose-a defaults: add underline + decoration colour for link-in-text-block rule - Decorative progress bars left at crystal-500 (3:1 UI contrast holds)
…chy', 'fix/mineral-count-source-of-truth' and 'fix/contrast-palette' into feature/ux-fix-batch
- Rewrite hero to outcome-led headline ("Identify gems with confidence"),
shift primary CTA to /gallery and secondary to /learn, append open-source
reassurance line.
- Add WebGL availability check + prefers-reduced-motion handling to
HeroCrystal3D; fall back to a static SVG illustration with proper
role=img and aria-label for browsers without WebGL.
- Generate SVGs for the 46 mineral families that previously 404'd:
18 rendered via crystal-renderer pipeline, 28 origin-aware placeholders
for amorphous, simulant, composite, and synthetic-only entries.
- Add scripts/audit-crystal-svgs.mjs and wire it into prebuild via the new
npm run check-svgs target so future drift fails CI before deploy.
… sitemap, about page, redirects, security headers P1-1: Self-host Inter & JetBrains Mono via @fontsource-variable to eliminate CLS and third-party hop P1-2: Hero respects prefers-reduced-motion (handled in P0 commit, verified) P1-3: Header active state with aria-current="page" + visual underline marker P1-4: Mineral detail mobile CTA — duplicate PlaygroundLink for sub-lg viewports P1-5: Schema.org JSON-LD upgrades — LearningResource+Article, Wikidata Mineral additionalType, generic StructuredData component P1-6: Sitemap lastmod + tiered priority (home/gallery/learn=1.0, minerals=0.8, docs=0.5) P1-7: About page with editorial standards + author/reviewedBy/reviewedAt schema for learn entries P1-8: _redirects with 12 legacy /learn flat-slug 301s P1-9: _headers with CSP, HSTS preload, frame-ancestors none, per-path Cache-Control
…llms.txt, indexnow, quiz
P2-1: Learn hub "Start here" curated foundations section above the 90-card wall
P2-2: Category descriptions surface total count + remainder ("plus N more")
P2-3: DifficultyBadge — distinct hue + leading dot per level for at-a-glance scannability
P2-4: Learn hub client-side filter (data-search hashes; collapses empty groups)
P2-5: Removed duplicate CDL block from PlaygroundLink (kept canonical copy in CrystalStructureCard)
P2-6: Mineral page nested <main> → <article> (BaseLayout already provides <main>)
P2-7: Acronym title-casing helper (CVD/HPHT/FTIR/UV/IR/YAG/CZ…) for origin + growth method
P2-8: Gallery Groups filter collapses to <select> when >6 entries
P2-9: Footer "Packages" column hidden outside /docs
P2-10: <title> brand-first ("gemmology.dev — X") for SERP recall
P2-11: /llms.txt build-time endpoint with all learn + mineral surfaces
P2-12: IndexNow GitHub Action — diff-based URL submission to Bing/Yandex/Naver
P2-13: Removed redundant "Professional Gemmological Tools" section from tools hub
P2-14: Quiz setup uses 2-column layout at lg (categories on left, controls on right)
P2-15: Mineral page breadcrumb moved above H1 (semantic + UX win)
|
🚀 Preview deployed to: https://1ffdaf7b.gemmology-dev.pages.dev |
Required by .github/workflows/indexnow.yml — IndexNow endpoints fetch https://gemmology.dev/<KEY>.txt to verify host ownership before accepting URL submissions. Key value matches the INDEXNOW_KEY GitHub Actions secret.
|
🚀 Preview deployed to: https://0a236c2d.gemmology-dev.pages.dev |
Bissbert
added a commit
that referenced
this pull request
May 11, 2026
* fix(seo): exclude /admin/ from sitemap, robots, and indexing
The /admin/ route was leaking into the public sitemap-0.xml and was
indexable. This:
- Adds a sitemap filter so /admin/* URLs are excluded
- Adds Disallow: /admin/ to robots.txt
- Adds a noindex prop to BaseLayout and uses it on the admin page
Resolves P0-5 from the 2026-05-04 UX review.
* fix(a11y): promote learn property/comparison headings from h4 to h3
Sections in /learn/* skipped h3 entirely — h2 (e.g. "Symmetry Elements")
went straight to h4 children rendered by PropertyList and ComparisonBlock.
That violates WCAG 1.3.1 and breaks screen-reader/LLM passage attribution.
Both components now accept a headingLevel prop (default 3). SectionRenderer
passes headingLevel=4 only when the components are nested inside a
subsection (which already uses h3), preserving correct structure at every
depth.
Resolves P0-6 from the 2026-05-04 UX review.
* fix(content): single source of truth for mineral counts
Three pages displayed three different (and all stale) mineral counts:
- / claimed "50+"
- /gallery claimed "90+"
- /404 claimed "94+"
The live database has 159 families. New src/lib/stats.ts queries
mineral_families and mineral_expressions at build time and exposes the
counts via getSiteStats(); the three pages now consume that helper.
Resolves P0-7 from the 2026-05-04 UX review.
* fix(a11y): contrast palette pass for WCAG AA compliance
Replace crystal-500/600 in text and button backgrounds with crystal-700
across all components. crystal-600 (#0284c7) failed AA on white at 4.5:1;
crystal-700 (#0369a1) passes ~5.7:1 for body text and UI controls.
- text-crystal-600 → text-crystal-700 (links, headings, hover states)
- bg-crystal-500/600 with text-white → bg-crystal-700 (buttons, badges, active states)
- from-crystal-500 to-crystal-600 → from-crystal-600 to-crystal-700 (gradients)
- hover:bg-crystal-600 → hover:bg-crystal-800
- prose-a defaults: add underline + decoration colour for link-in-text-block rule
- Decorative progress bars left at crystal-500 (3:1 UI contrast holds)
* feat(ux): P0 fixes — hero copy, WebGL fallback, missing SVGs
- Rewrite hero to outcome-led headline ("Identify gems with confidence"),
shift primary CTA to /gallery and secondary to /learn, append open-source
reassurance line.
- Add WebGL availability check + prefers-reduced-motion handling to
HeroCrystal3D; fall back to a static SVG illustration with proper
role=img and aria-label for browsers without WebGL.
- Generate SVGs for the 46 mineral families that previously 404'd:
18 rendered via crystal-renderer pipeline, 28 origin-aware placeholders
for amorphous, simulant, composite, and synthetic-only entries.
- Add scripts/audit-crystal-svgs.mjs and wire it into prebuild via the new
npm run check-svgs target so future drift fails CI before deploy.
* feat(ux): P1 fixes — fonts, hero lazy-load, nav, mobile CTA, JSON-LD, sitemap, about page, redirects, security headers
P1-1: Self-host Inter & JetBrains Mono via @fontsource-variable to eliminate CLS and third-party hop
P1-2: Hero respects prefers-reduced-motion (handled in P0 commit, verified)
P1-3: Header active state with aria-current="page" + visual underline marker
P1-4: Mineral detail mobile CTA — duplicate PlaygroundLink for sub-lg viewports
P1-5: Schema.org JSON-LD upgrades — LearningResource+Article, Wikidata Mineral additionalType, generic StructuredData component
P1-6: Sitemap lastmod + tiered priority (home/gallery/learn=1.0, minerals=0.8, docs=0.5)
P1-7: About page with editorial standards + author/reviewedBy/reviewedAt schema for learn entries
P1-8: _redirects with 12 legacy /learn flat-slug 301s
P1-9: _headers with CSP, HSTS preload, frame-ancestors none, per-path Cache-Control
* feat(ux): P2 polish batch — mineral page, footer, learn hub, titles, llms.txt, indexnow, quiz
P2-1: Learn hub "Start here" curated foundations section above the 90-card wall
P2-2: Category descriptions surface total count + remainder ("plus N more")
P2-3: DifficultyBadge — distinct hue + leading dot per level for at-a-glance scannability
P2-4: Learn hub client-side filter (data-search hashes; collapses empty groups)
P2-5: Removed duplicate CDL block from PlaygroundLink (kept canonical copy in CrystalStructureCard)
P2-6: Mineral page nested <main> → <article> (BaseLayout already provides <main>)
P2-7: Acronym title-casing helper (CVD/HPHT/FTIR/UV/IR/YAG/CZ…) for origin + growth method
P2-8: Gallery Groups filter collapses to <select> when >6 entries
P2-9: Footer "Packages" column hidden outside /docs
P2-10: <title> brand-first ("gemmology.dev — X") for SERP recall
P2-11: /llms.txt build-time endpoint with all learn + mineral surfaces
P2-12: IndexNow GitHub Action — diff-based URL submission to Bing/Yandex/Naver
P2-13: Removed redundant "Professional Gemmological Tools" section from tools hub
P2-14: Quiz setup uses 2-column layout at lg (categories on left, controls on right)
P2-15: Mineral page breadcrumb moved above H1 (semantic + UX win)
* chore(seo): add IndexNow verification key file
Required by .github/workflows/indexnow.yml — IndexNow endpoints fetch
https://gemmology.dev/<KEY>.txt to verify host ownership before accepting
URL submissions. Key value matches the INDEXNOW_KEY GitHub Actions secret.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
End-to-end implementation of every finding from
~/Downloads/gemmology-dev-ux-review-2026-05-04/FIX-PLAN.md— 31 fixes across accessibility, performance, SEO, content, and polish.P0 (7 fixes — accessibility & correctness)
getSiteStats()and a single primary CTAHeroCrystal3D— static SVG octahedron when WebGL unavailable, respectsprefers-reduced-motion, swaps sql.js call for directGLTFLoader.load(/models/gltf/<slug>.glb)(eliminates ~600KB DB load on home)crystal-600→crystal-700site-wide (WCAG AA at 16px regular)crystal-rendererPython toolchain, 28 origin-aware placeholders for amorphous/composite/simulant materials; newnpm run check-svgsaudit gates the buildsrc/lib/stats.tsreads live fromminerals.dbP1 (9 fixes — performance, SEO, IA)
@fontsource-variable/interand@fontsource-variable/jetbrains-mono(eliminates Google Fonts hop + CLS)client:visiblearia-current=\"page\"+ visual underline markerPlaygroundLinkduplicated for<lgdirectly under QuickFactsLearningResource+Article, WikidataQ43533additionalTypefor Mineral, genericStructuredDatacomponent for breadcrumbs+WebPagelastmodon every URL, tier-based priority (home/gallery/learn=1.0, minerals=0.8, docs=0.5)/learn/<flat-slug>301s + gallery/docs old URL redirectsframe-ancestors 'none', HSTS preload, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, per-path Cache-ControlP2 (15 fixes — polish)
<main>→<article>, acronym title-casing (CVD/HPHT/FTIR/UV/IR/YAG/CZ…), breadcrumb above H1<select>when >6 entries/docs<title>brand-first ("gemmology.dev — X") for SERP recall/llms.txtbuild-time endpoint with all learn + mineral URLslgTest plan
npm run build— 910 pages built clean in ~7sdist/llms.txtgenerated, lists 138 learn entries + 303 mineral surfacesdist/sitemap-0.xmlincludes<lastmod>and tiered<priority>dist/index.html<title>is brand-firstdist/_headersanddist/_redirectsdeployed to Cloudflare Pagesdist/robots.txtDisallows/admin/npm run check-svgs— 0 missing/,/learn,/gallery,/playgroundconfirms a11y ≥ 95, color-contrast 0 failurescurl -I https://gemmology.dev/learn/crystal-systems→ 301 to/learn/fundamentals/crystal-systemsINDEXNOW_KEYrepo secret + place key file inpublic/See
~/Downloads/gemmology-dev-ux-review-2026-05-04/REPORT.mdandFIX-PLAN.mdfor the full audit and acceptance criteria.