Skip to content

feat(web): polish marketing site — SVGs, sticky pricing, language switcher#1673

Merged
yannickmonney merged 3 commits into
mainfrom
feat/web-marketing-polish
May 5, 2026
Merged

feat(web): polish marketing site — SVGs, sticky pricing, language switcher#1673
yannickmonney merged 3 commits into
mainfrom
feat/web-marketing-polish

Conversation

@yannickmonney

@yannickmonney yannickmonney commented May 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Marketing-site refresh on services/web plus a small bit of shared infrastructure that came along for the ride.

Web — visuals

Web — pricing & hardware-pricing

  • Sticky-only-on-tier-cards comparison header in pricing-compare.tsx and hardware-compare.tsx: each Community/Pro/Enterprise (and Quality/Hybrid/Speed) <th> sticks individually at top-16 with bg-bg-base/60 + backdrop-blur-lg + a 1px inset shadow border. Real border rules fold under border-collapse: collapse on sticky cells, hence the shadow trick. The leftmost label scrolls naturally so there's no big sticky blank box on the left.
  • Tier-card vertical alignment: always reserve the price-suffix line (min-h on the empty span) and apply min-h-[4.5em] to the tagline so "Le plan inclut :" / metric borders line up across all three cards regardless of how each tagline wraps.
  • Pricing-tier feature checks now use text-emerald-600 to match the comparison-table check; Cloud: ✓ inlined onto a single line with a shared InlineCheck rendering the unicode marker as the same lucide Check glyph.
  • GDPR removed from Community (genuinely not compliant) and from Enterprise (already inherited via "Everything in Pro"); PII-redaction row added to the comparison table.
  • Hardware buy prices corrected to 31.7k / 32.9k / 35.3k; suffix simplified + VAT one-off+ VAT (rental side already reads /month).
  • "Recommended AI model" → "SoA AI model" across en/de/fr.
  • Fixed duplicated "See our hardware pricing." in the comparison-table extras span.

Web — shared components

  • New TierCard — wraps the article + animation + header/price/tagline. Both PricingTiers and HardwareTiers consume it.
  • New CompareTable — generic over TierKey, drives both compare pages from a tiers[] + rows[] (data | section | span) input. Includes the proper <caption> for screen readers.
  • New ProgressBarrole="progressbar" with clamped value + aria-valuemin/max/now, used by hardware-tiers metric bars.
  • Custom-dropdown LanguageSwitcher with country flags. Locale-aware region resolution: pick "Deutsch" and the switcher routes to de-CH on a Swiss browser, falling back to de otherwise. Strings live in messages/global.json.

i18n tests (shared)

  • Extracted parity + orphan-key tests into @tale/ui/i18n-tests exposing defineMessagesParityTests and defineMessagesUsageTests. vitest declared as an optional peer dep so consumers that don't use the entry don't need it.
  • services/platform and services/web tests are now thin wrappers calling those factories.
  • Usage scanner now also recognises t(\${var}.suffix`)` (used by tier cards), so dynamically-built tier keys resolve without allowlist entries.
  • Added services/web/lib/i18n/keys-dynamic.txt categorising the 38 legacy orphan keys (pending wire-up vs leftover cleanup) so the test stays green while we decide what to delete.
  • Plop react-service template now generates both tests, a keys-dynamic.txt stub, and the previously-unregistered config.ts / types.ts.

CodeRabbit findings

Local coderabbit review --agent returned 6 trivial findings; applied 3:

  • Wrap loadLocale JSON read with try/catch + contextual error in parity.ts.
  • Add vitest as an optional peer dep on @tale/ui.
  • Use a proper sr-only <caption> element in compare-table.tsx instead of a span inside the first <th>.

Skipped 3:

  • "SoA AI model" expansion — the abbreviation was the explicit ask.
  • Hardcoded SharePoint terms URL — the same pattern was already in pricing-compare; not introduced here.
  • Tracking-issue suggestion for legacy keys — already documented inline in keys-dynamic.txt.

Pre-PR checklist

  • Ran `bun run check` (format, lint, typecheck, all tests). 30/30 tasks succeed.
  • Updated `services/platform/messages/{en,de,fr}.json` — N/A (this PR doesn't touch the platform UI).
  • Updated `docs/{,de/,fr/}` for every user-visible change — N/A (no docs surface here; this is purely the marketing site).
  • Ran `bun run --filter @tale/docs lint` — N/A.
  • Updated `README.md`, `README.de.md`, `README.fr.md` — N/A.

Test plan

  • `/` — sticky pricing header behaves on scroll (only Community/Pro/Enterprise float, frosted-glass tbody bleeds through).
  • `/` — feature-secure / sectors / compliance render the new SVGs without overflowing or stretching.
  • `/pricing` — tier-card "Le plan inclut :" lines up across the three cards in every locale.
  • `/pricing` — Maintenance row reads `Cloud : ✓` on one line; PII-redaction shows under Compliance.
  • `/pricing` — Community has no GDPR row; Enterprise has no GDPR row (inherited via Pro); Pro shows GDPR.
  • `/hardware-pricing` — buy prices show `CHF ~31.7k` / `~32.9k` / `~35.3k` with `+ VAT` suffix; rental shows `+ VAT/month`.
  • `/hardware-pricing` — same sticky behaviour + tagline alignment as the pricing page.
  • Footer language switcher: opens, shows three flags, switching to Deutsch on a Swiss browser ends up at `de-CH`.
  • Run `bun run --filter @tale/web test` and `bun run --filter @tale/platform test` — i18n-parity + orphan-key tests still green.

Summary by CodeRabbit

  • New Features

    • Added language switcher to footer for easy locale switching.
    • Introduced reusable comparison table and tier card components with improved animations and styling.
    • Added new progress bar component for hardware metrics visualization.
    • Added new marketing SVG icons and flag glyphs for enhanced visuals.
    • Added GDPR compliance and PII redaction labels to pricing plans.
  • Bug Fixes & Improvements

    • Replaced raster illustrations with SVG assets for better performance.
    • Updated hardware and pricing comparison tables with refined typography and layout.
  • Documentation

    • Updated translation strings across English, German, and French.

…tcher

Marketing site refresh that touches several adjacent surfaces.

Web — visuals
- Migrate 16 SVG mockups from the old Framer site into public/marketing/svg
  with descriptive names (mock-chat, mock-conversations, mock-workflow-grid,
  mock-approvals-table, mock-integrations-stack, mock-compliance-columns,
  mock-blocks, mock-product-card, mock-document-card, mock-document-long,
  …) and wire them into feature-secure, feature-sectors, compliance-trust,
  feature-grid (security-2/3) replacing the corresponding PNGs.
- Replace lucide icons in feature-secure / feature-grid / compliance-trust
  with stroke-based marketing icons matching the old site (chat,
  conversations, workflows, approvals, hospitality, legal, finance,
  independent, stack, secure, built-for-you, transparent, certified,
  chevron-down, language).

Web — pricing & hardware pricing
- Sticky-only-on-tier-cards comparison header: each Community/Pro/
  Enterprise (and Quality/Hybrid/Speed) cell sticks individually at
  top-16 with bg-bg-base/60 + backdrop-blur-lg + 1px inset shadow border
  (regular borders fold under border-collapse on sticky cells). The
  leftmost label scrolls naturally with the body.
- Tier-card vertical alignment: always reserve the price-suffix line
  (min-h on the empty span) and apply min-h-[4.5em] to the tagline so
  "Plan includes" / metrics borders line up across all three cards.
- Pricing tier feature checks now use text-emerald-600 to match the
  comparison-table check; "Cloud: ✓" inlined onto a single line, with a
  shared InlineCheck rendering the unicode marker as the same lucide
  Check glyph.
- GDPR removed from the Community tier (genuinely not compliant) and
  from Enterprise (already inherited via "Everything in Pro"); PII-
  redaction row added to the comparison table.
- Hardware buy prices corrected (31.7k / 32.9k / 35.3k) and "+ VAT one-
  off" suffix simplified to "+ VAT" — the rental side already reads
  "/month" so the disambiguation is preserved.
- "Recommended AI model" → "SoA AI model" across en/de/fr.
- Fixed duplicated "See our hardware pricing." in the comparison-table
  extras span.

Web — shared components
- New TierCard (services/web/app/components/blocks/tier-card.tsx) wraps
  the article + animation + header/price/tagline so PricingTiers and
  HardwareTiers share the same vertical rhythm.
- New CompareTable (services/web/app/components/blocks/compare-table.tsx)
  renders the wrapping motion.div + table + sticky tier headers and
  drives both PricingCompare and HardwareCompare from `tiers[]` +
  rows[] (data | section | span). Includes a proper sr-only <caption>.
- New ProgressBar (services/web/app/components/progress-bar.tsx) with
  role="progressbar" + clamped value, used by HardwareTiers for the
  quality/speed/storage metric bars.
- Custom-dropdown LanguageSwitcher with country flags (FlagEN/FlagDE/
  FlagFR) and locale-aware region resolution: pick "Deutsch" and the
  switcher routes to de-CH on a Swiss browser, falling back to de
  otherwise. Strings live in messages/global.json so they're shared.

i18n tests
- Extract messages-parity and orphan-key tests into a reusable
  @tale/ui/i18n-tests entry point exposing defineMessagesParityTests
  and defineMessagesUsageTests. Vitest declared as an optional peer
  dep so consumers that don't use the entry don't need it installed.
- Refactor services/platform and services/web tests into thin wrappers
  that call those factories. Add web's keys-dynamic.txt allowlist
  documenting the legacy keys awaiting wire-up vs cleanup.
- Update the plop react-service template + generator so newly
  scaffolded services include both tests, the keys-dynamic.txt stub,
  and the previously-unregistered config.ts / types.ts.
- Usage scanner picks up the new t(`${var}.suffix`) template pattern
  used by pricing/hardware tier cards (in addition to the existing
  `prefix.${var}` pattern), so dynamically-built tier keys resolve
  without needing allowlist entries.

Pre-PR checklist
- [x] Ran `bun run check` (format, lint, typecheck, all tests).
- [x] Updated services/platform/messages — N/A (changes scoped to web
      and shared @tale/ui).
- [x] Updated docs — N/A (no user-facing API/CLI surface changed).
- [x] Ran `bun run --filter @tale/docs lint` — N/A.
- [x] Updated README.md / README.de.md / README.fr.md — N/A.
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces a reusable i18n testing library in packages/ui exporting defineMessagesParityTests and defineMessagesUsageTests to validate translation key consistency and code usage. It refactors existing test implementations in services/platform and services/web to use these shared utilities. The PR adds new UI components (CompareTable, TierCard, LanguageSwitcher, ProgressBar, marketing icon set), migrates several feature-block illustrations from PNG to SVG assets, introduces translation keys for GDPR compliance and pricing updates across English/German/French locales, and extends the plop service generator to include i18n test scaffolding.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • tale-project/tale#1642: Refines i18n orphan-key detection heuristics in services/platform's messages-usage.test logic, which this PR replaces with calls to the new shared defineMessagesUsageTests utility.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(web): polish marketing site — SVGs, sticky pricing, language switcher' is concise, specific, and accurately summarizes the main changes (visual updates, pricing UI improvements, and new language switcher).
Description check ✅ Passed The description is comprehensive and well-structured, covering all major sections from the template (Summary, Pre-merge checklist, Test plan). It clearly explains what changed and why, includes proper checklist items marked as completed or N/A with reasoning, and provides detailed test plan steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-marketing-polish

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/web/messages/de.json (1)

257-262: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Hour-unit inconsistency in German copy: / h vs / Std.

maintenanceRate, customDevelopmentRate, and consultingRate use the German abbreviation Std., but the new proMaintenance line uses / h. Pick one (preferably Std. to match the rest of the German pricing strings) so users see consistent unit labels in the comparison table.

🌐 Proposed fix
-        "proMaintenance": "Cloud: ✓\nSelbst gehostet: CHF 80–120 / h"
+        "proMaintenance": "Cloud: ✓\nSelbst gehostet: CHF 80–120 / Std."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/web/messages/de.json` around lines 257 - 262, The German pricing
strings are inconsistent: the keys maintenanceRate, customDevelopmentRate, and
consultingRate use "Std." while proMaintenance uses "/ h"; update the value for
the proMaintenance key to use "Std." (e.g., "Cloud: ✓\nSelbst gehostet: CHF
80–120 / Std.") so the hour-unit matches the other keys and keeps the comparison
table consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ui/src/i18n-tests/parity.ts`:
- Around line 102-108: The test name for the it(...) block is ambiguous; update
the description string in the test that uses locale, baseLocale, keys and
baseKeys (the it(...) asserting extra = [...keys].filter(k => !baseKeys.has(k))
and expect(extra).toEqual([])) to clearly state it checks for extra keys not
present in the base locale — e.g., replace "has no keys missing from
${baseLocale}.json" with "has no extra keys not present in ${baseLocale}.json"
or "has no keys absent from ${baseLocale}.json" so CI output reads
unambiguously.
- Around line 91-122: Replace the Vitest-compatible describe.each calls with the
recommended describe.for so each locale string is passed as a single argument
(not spread) and you gain TestContext; update both occurrences where
describe.each(primary) and describe.each(regional) are used (inside the parity
test block that computes keys via flatten(loadLocale(messagesDir, locale))) to
describe.for(primary) and describe.for(regional) respectively, keeping the
existing callback signature (locale) => { ... } and preserving the tests that
compute keys and assert against baseKeys.

In `@services/web/app/components/blocks/feature-secure.tsx`:
- Around line 99-107: Replace the raw <img> with the shared Image component:
import Image from '@/components/ui/image', remove the bare <img> inside the div
with className={panelStage}, and render <Image src={item.illustration} ...
className={panelImage} /> preserving the original attributes (alt value or
aria-hidden, draggable, loading="lazy") and any className usage; ensure you
reference item.illustration and panelImage so the visual layout and
accessibility flags remain the same.

In `@services/web/app/components/blocks/pricing-compare.tsx`:
- Around line 47-55: Add role="img" to the SVG Check usages to meet WCAG AA:
update the InlineCheck component (function InlineCheck) to pass role="img" into
the Check element alongside the existing aria-label, and likewise update the
Check rendered in renderCell (the Check at line ~71) to include role="img" while
keeping its aria-label and strokeWidth/className props intact so screen readers
treat the SVGs as images.

In `@services/web/app/components/blocks/tier-card.tsx`:
- Around line 97-99: Remove the non-breaking-space fallback in the span inside
the TierCard component: replace the ternary "{priceSuffix ? priceSuffix : ' '}"
with just "{priceSuffix}" (or "{priceSuffix ?? null}") so the min-h-[1.25rem]
handles spacing and you don't accidentally render a stray NBSP when callers pass
an empty string; update the span element that references priceSuffix in
services/web/app/components/blocks/tier-card.tsx accordingly.

In `@services/web/app/components/layout/language-switcher.tsx`:
- Around line 131-175: This code uses ARIA menu roles (role="menu" on the ul and
role="menuitemradio" + aria-checked on each button) without implementing menu
keyboard behavior; remove the misleading menu semantics and rely on native
button/list accessibility: delete role="menu" and role="menuitemradio" and
remove aria-checked, keep the UL as a plain list and the items as buttons, and
mark the active locale with aria-current="true" (use currentBase to set
aria-current on the button rendered inside the BASE_LOCALES.map where
handleSelect, LOCALE_FLAGS and t are used) so keyboard users still get clear
state and buttons remain natively keyboard-focusable.

In `@services/web/app/components/progress-bar.tsx`:
- Around line 11-15: The JSDoc and API are wrong: aria-labelledby must be
applied on the element with role="progressbar" and there is no way to pass it
currently. Update the ProgressBar component by correcting the JSDoc comment for
ariaLabel to state that consumers may alternatively use aria-labelledby on the
progressbar itself, add an optional ariaLabelledBy?: string prop to the
component's props (alongside ariaLabel), and ensure the rendered element with
role="progressbar" uses aria-labelledby={ariaLabelledBy} when provided (falling
back to aria-label={ariaLabel} if ariaLabelledBy is undefined). Also update any
prop-type/TS types and the JSDoc to reflect the new prop and correct guidance.

In `@services/web/messages/global.json`:
- Around line 1-10: The shared key languageSwitcher.ariaLabel in
services/web/messages/global.json causes t('languageSwitcher.ariaLabel') in
language-switcher.tsx to always announce English; remove ariaLabel from
global.json and add a languageSwitcher.ariaLabel entry to each per-locale file
(services/web/messages/en.json, de.json, fr.json) with the proper localized
strings (e.g., "English": "Language", "de": "Sprache", "fr": "Langue") so the
calls in language-switcher.tsx (refs: languageSwitcher.ariaLabel usage at lines
~109 and ~134) resolve to the correct locale-specific label. Ensure keys'
nesting matches existing languageSwitcher structure in the per-locale files.

---

Outside diff comments:
In `@services/web/messages/de.json`:
- Around line 257-262: The German pricing strings are inconsistent: the keys
maintenanceRate, customDevelopmentRate, and consultingRate use "Std." while
proMaintenance uses "/ h"; update the value for the proMaintenance key to use
"Std." (e.g., "Cloud: ✓\nSelbst gehostet: CHF 80–120 / Std.") so the hour-unit
matches the other keys and keeps the comparison table consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6d855452-5895-4b77-9453-5e4962cf50f8

📥 Commits

Reviewing files that changed from the base of the PR and between 2eccbf1 and 93c0fa1.

⛔ Files ignored due to path filters (16)
  • services/web/public/marketing/svg/mock-approvals-table.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-automation-flow.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-blocks.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-card-stack.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-chat.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-compliance-columns-alt.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-compliance-columns.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-conversations.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-doc-summarizing-alt.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-doc-summarizing.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-document-card.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-document-long.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-integrations-stack-alt.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-integrations-stack.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-product-card.svg is excluded by !**/*.svg
  • services/web/public/marketing/svg/mock-workflow-grid.svg is excluded by !**/*.svg
📒 Files selected for processing (32)
  • packages/ui/package.json
  • packages/ui/src/i18n-tests/index.ts
  • packages/ui/src/i18n-tests/parity.ts
  • packages/ui/src/i18n-tests/usage.ts
  • services/platform/lib/i18n/messages-usage.test.ts
  • services/platform/lib/i18n/messages.test.ts
  • services/web/app/components/blocks/compare-table.tsx
  • services/web/app/components/blocks/compliance-trust.tsx
  • services/web/app/components/blocks/feature-grid.tsx
  • services/web/app/components/blocks/feature-sectors.tsx
  • services/web/app/components/blocks/feature-secure.tsx
  • services/web/app/components/blocks/hardware-compare.tsx
  • services/web/app/components/blocks/hardware-tiers.tsx
  • services/web/app/components/blocks/pricing-compare.tsx
  • services/web/app/components/blocks/pricing-tiers.tsx
  • services/web/app/components/blocks/tier-card.tsx
  • services/web/app/components/icons/marketing-icons.tsx
  • services/web/app/components/layout/language-switcher.tsx
  • services/web/app/components/layout/site-footer.tsx
  • services/web/app/components/progress-bar.tsx
  • services/web/app/routes/index.tsx
  • services/web/lib/i18n/keys-dynamic.txt
  • services/web/lib/i18n/messages-usage.test.ts
  • services/web/lib/i18n/messages.test.ts
  • services/web/messages/de.json
  • services/web/messages/en.json
  • services/web/messages/fr.json
  • services/web/messages/global.json
  • tools/plop/generators/react-service.ts
  • tools/plop/templates/react-service/lib/i18n/keys-dynamic.txt
  • tools/plop/templates/react-service/lib/i18n/messages-usage.test.ts.hbs
  • tools/plop/templates/react-service/lib/i18n/messages.test.ts

Comment thread packages/ui/src/i18n-tests/parity.ts Outdated
Comment thread packages/ui/src/i18n-tests/parity.ts Outdated
Comment thread services/web/app/components/blocks/feature-secure.tsx
Comment thread services/web/app/components/blocks/pricing-compare.tsx Outdated
Comment thread services/web/app/components/blocks/tier-card.tsx
Comment thread services/web/app/components/layout/language-switcher.tsx
Comment thread services/web/app/components/progress-bar.tsx Outdated
Comment thread services/web/messages/global.json
…w fixes

- Pricing page: Community + Enterprise tiers with region (CH/EUR) and billing
  (monthly/yearly) toggles, shared via `pricing-section` and `lib/pricing/region`
- Compare table now reads region-aware values; translations updated across
  en/de/fr to match the new structure
- Accessibility/i18n review fixes:
  - parity test renamed for clarity and switched from `describe.each` to
    `describe.for`
  - `role="img"` on lucide `<Check>` next to its aria-label
  - language switcher: drop misleading menu/menuitemradio semantics, use
    `aria-current` on the active locale
  - `ProgressBar`: add `ariaLabelledBy` prop, fix JSDoc
  - `tier-card`: drop NBSP fallback under price suffix
  - `i18n.ts`: one-namespace-deep merge so per-locale `languageSwitcher.ariaLabel`
    coexists with shared `languageSwitcher.locales` in `global.json`
Vitest is declared as an optional peerDependency on @tale/ui (consumers of
@tale/ui/i18n-tests need it in scope) and also imported by the package's own
tests. Knip flagged the internal usage; ignore it for this workspace.
@yannickmonney yannickmonney merged commit a7b269d into main May 5, 2026
17 checks passed
@yannickmonney yannickmonney deleted the feat/web-marketing-polish branch May 5, 2026 22:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant