Add tools/build_botlinks.py which regenerates, from the pages already on disk:
- /llms.txt llmstxt.org-style Markdown index (English, with descriptions)
- /links-for-bots/ a lightweight fully-linked crawl hub covering EN/MK/SI
Re-run `python tools/build_botlinks.py` after adding or removing pages; it only
reads each page's <title> and meta description, so it stays current automatically.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses real gaps found in testing (a Macedonian question on an English
page returned nothing):
- Detect the language of the QUESTION (Cyrillic -> MK; Slovene markers -> SI;
else the page language) and load that language's index + curated answers.
Fixes cross-language questions.
- Expand curated multilingual intents (now incl. enrolment/eVŠ, deadlines,
health, "what is MSOS", etc.) with stem keywords for better paraphrase
matching. MK/SI answers machine-drafted — proofread before launch.
- Suggested-question chips on open (localized) to guide users.
- Privacy-safe analytics: fire a GA4 'assistant_query' event ONLY after the
visitor accepts cookies (q text, result type, question vs page language) so
the team can see real FAQs and unanswered questions.
- Move the widget to bottom-left on pages with the floating "Become a member"
CTA so they never overlap. Design polish (chips, typing dots, launcher pulse).
- CI: rebuild the search index on every deploy (setup-python + build script)
so the assistant stays in sync with content automatically. Build script is
now path-portable (no hardcoded path).
Verified in a headless browser: MK-on-EN now answers correctly, chips work,
analytics event fires only with consent, become-a-member offset applies,
zero console errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A privacy-friendly help widget that answers visitor questions from the
site's own content and links to the full page — runs 100% in the browser,
no API, no server, no per-token cost.
- tools/build_search_index.py: parses every EN/MK/SI page into ~720
heading-level chunks per language -> assistant/index-<lang>.json
(lazy-loaded only when the chat is opened; gzips to ~80 KB).
- assistant.js: dependency-free BM25 search + multilingual stopword
filtering + a curated short-answer layer for common questions, plus an
accessible chat widget (role=dialog, keyboard, aria). CSP-safe (self only).
- assistant.css: scoped .msa-* styles in the site's teal palette;
responsive (full-screen panel on mobile).
- Injected on 208 pages (blog WIP excluded; picks it up when that lands).
Verified in a headless browser across EN/MK/SI: curated answers, search
fallback with correct ranking (rare terms win), correct deep links,
Cyrillic + Slovene diacritics, and zero console errors. Curated MK/SI
answers are machine-drafted — proofread before launch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- consent.js: self-contained, non-blocking bottom banner in EN/MK/SI,
remembers the choice in localStorage, links to the per-language cookie
policy. Accept -> gtag consent 'update' granted; Decline stays denied.
- Head GA snippet restores a returning visitor's prior "granted" choice
before the first hit; loads /consent.js on every page.
- Re-openable via window.msosOpenConsent() for a "manage cookies" link.
Verified in-browser: shows for new visitors, Accept grants + remembers,
reload keeps consent without re-prompting, Slovene text + /si/ policy link.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- gtag.js injected on all 201 pages via tools/seo_inject.py + on 404.html
- Consent Mode v2 defaults all storage to 'denied' (EEA-safe: tag loads,
no tracking cookies until a consent banner grants analytics_storage)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add datePublished/dateModified to Article JSON-LD on the 11 dated
articles (curated once from EN, applied to all languages)
- Replace og-default.jpg with the official brand OG image (1200x630)
- Add docs/launch-seo-checklist.md (go-live steps: redirects, Search
Console, analytics, rich-result validation, monitoring)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add branded 1200x630 og-default.jpg; article pages use their hero as
og:image, others use the default
- Wire the existing (previously unlinked) favicon set into every page
- Add branded 404.html with root-absolute paths + noindex
- Emit Article + localized BreadcrumbList JSON-LD on all article pages
- Convert 74 heavy served photos to WebP (28MB -> 8.5MB, -70%), rewrite
references, add loading=lazy/decoding=async to below-fold images and
fetchpriority=high to heroes (protect LCP)
- Stop tracking images/original/ (458MB of unused source masters; kept on
disk, gitignored) and ignore .playwright-mcp/
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SEO (all 201 pages, en/mk/si):
- Inject canonical, hreflang (en/mk/sl + x-default), Open Graph, Twitter
cards and Organization JSON-LD via idempotent SEO:START/END block
- Generate multilingual sitemap.xml (xhtml:link alternates) + robots.txt
- Curated meta descriptions for lead-less pages (home, become-a-member,
faq, news hub, my-route); every page now has a description
- Absolute URLs target final domain msosorg.com, not the temp host
- Add reusable tools/seo_inject.py (+ README)
Content fixes:
- Translate about-us and timeline-and-milestones into MK + SI, wire up
their pages, and repair the previously-dead "Who We Are" nav links
across all mk/si pages
- Remove duplicate en/projects/makedonska-studentska-vecer-vo-ljubljana
(unreferenced; already covered by macedonian-student-night-in-ljubljana)
Locale correctness:
- Normalise Slovene pages to <html lang="sl"> (correct ISO 639-1)
- Map sl->si in main.js so the language-switcher highlight and newsletter
i18n keep working on Slovene pages (data-lang="si" still maps to /si/)
MK/SI translations and descriptions are AI-drafted; flag for native review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>