Skip to content

refactor(webui): marketing home redesign from .pen — new sections, dark mode, animations#1730

Merged
AdeolaAdekoya merged 2 commits into
mainfrom
designs-settings-ai-providers-search-pagination
May 22, 2026
Merged

refactor(webui): marketing home redesign from .pen — new sections, dark mode, animations#1730
AdeolaAdekoya merged 2 commits into
mainfrom
designs-settings-ai-providers-search-pagination

Conversation

@AdeolaAdekoya
Copy link
Copy Markdown
Collaborator

@AdeolaAdekoya AdeolaAdekoya commented May 21, 2026

Summary

Brings the marketing home page in line with the latest frontpages.pen design — new sections, dark-mode support, and subtle entrance/marquee animations across the page.

What changed

Hero

  • New copy ("All the Power of AI. None of the Risk." + new subtitle)
  • Two CTAs: Request demo (primary) + Read docs (secondary, links to DOCS_URL)
  • Theme-aware hero image (light/dark PNG swap)
  • Full-bleed image with max-h-125 + object-cover object-top so it never stretches absurdly tall on wide screens or clips the mockup's top

"Your Infrastructure. Your Data. Your Rules." (new section)

  • Title + subtitle + 3 pillar cards (Self-hosted by default / Enterprise-grade security / Fully open source)
  • Animated SVG illustrations per theme, scoped pillar bg (#fafafa light / #151515 dark)
  • Stagger reveal on scroll

Feature blocks (Chat / Conversations / Agents / Automations)

  • Replaces the old gradient-tab FeatureSecure — now a stacked 4-section block
  • 2-column desktop layout (title left, description right), full-bleed screenshots below
  • Theme-aware screenshots (light + dark PNGs)
  • Balanced 2-line titles via explicit \n line breaks in translations

Integrations bar (new)

  • 9-logo marquee with seamless infinite scroll, mask-fade edges, pause-on-hover
  • Brand SVGs: OpenAI, Shopify, Claude, GitHub, Microsoft, Slack, Discord, Gmail, Tavily
  • Title 48px

Compliance & Trust

  • Rewrote as a 3-column card (eyebrow + 40px title + ISO/SOC/GDPR badges on the left; Layers + ShieldCheck feature columns on the right) with inset vertical dividers
  • Card bg #fcfcfd light / #141416 dark
  • Below lg, stacks vertically with horizontal dividers

FAQ

  • Tighter title spacing + real ArrowRight icon on "Contact our team" (with hover:underline)
  • Cursor pointer on accordion triggers
  • Smoother expand (400ms Apple ease for height, 250ms ease-out for opacity)
  • Title aligned to compliance card's left edge

CTA

  • "Get Started with Tale" + secondary "See how it works" button (links to docs)

Footer

  • GitHub icon moved into brand column, single-line copyright
  • Segmented theme switcher with sliding indicator (light/dark/system)
  • Language switcher trigger no longer shows the flag (menu items keep it)
  • Full-width inset dividers

Dark mode

  • Re-enabled ThemeProvider in app/main.tsx + entry-server.tsx
  • FOUC-prevention inline script in index.html
  • Marketing-only palette overrides in app/globals.css: --color-bg-base: #0f0f0f, --color-border-base: #27272a

Cleanup

  • Removed unused FeatureGrid, FeatureSectors, LogoWall blocks + their translation keys
  • Deleted 15+ unreferenced marketing PNGs from public/marketing/

Translations updated across en/de/fr; de-CH inherits from de.

Test plan

  • bun run check passes (verified locally — 41/41 green)
  • Home page renders correctly in light mode at desktop / tablet / mobile widths
  • Home page renders correctly in dark mode at the same widths
  • Theme switcher in footer toggles between light/dark/system
  • Language switcher swaps locale (en/de/fr) and preserves URL
  • Hero CTAs land on the right routes (Request demo → /request-demo, Read docs → DOCS_URL)
  • Integrations marquee pauses on hover
  • FAQ accordion opens/closes smoothly; "Contact our team" routes to /contact
  • No console errors / hydration warnings

Summary by CodeRabbit

  • New Features

    • Segmented theme switcher with pill-style toggle option
    • Language switcher flag visibility control
    • Integrations bar displaying scrolling brand logos
    • New tagline section with pillar cards
    • Secondary "docs" call-to-action in hero
    • Enhanced footer customization options
  • UI Improvements

    • Refined accordion animation timings
    • Redesigned compliance section with security badges
    • Feature Secure simplified to static layout
    • FAQ section enhanced with contact call-to-action
    • Dark mode styling improvements
    • Automatic theme detection and persistence
  • Other

    • Updated content for English, German, and French
    • Integration icons system added

Review Change Stack

…rk mode, animations

Bring the marketing home page in line with the latest frontpages.pen design:

- Hero: new copy ("All the Power of AI. None of the Risk."), light/dark image
  swap, two CTAs (request demo + read docs), full-bleed image with 500px
  height cap and top-aligned crop
- New "Your Infrastructure. Your Data. Your Rules." section with 3
  self-hosted/security/open-source pillar cards (animated SVGs per theme)
- Replaced FeatureSecure tab UI with 4 numbered feature blocks (Chat,
  Conversations, Agents, Automations) — 2-column text + screenshot, theme-aware
  images, balanced 2-line titles
- New IntegrationsBar with auto-scrolling 9-logo marquee, edge mask, and
  pause-on-hover
- ComplianceTrust rewritten as a 3-column card (eyebrow + title + badges /
  Independent / Certified) with inset vertical dividers
- FAQ accordion: tighter title spacing, real ArrowRight icon on "Contact our
  team", flat backgrounds, snappier expand transition
- CTA: "Get Started with Tale" with secondary "See how it works" button
- Footer: GitHub icon moved into brand column, single-line copyright merged,
  segmented theme switcher (sliding indicator), language switcher no longer
  shows flag in trigger, full-width inset dividers
- Marketing-only dark palette overrides in app/globals.css (bg-base #0f0f0f,
  border-base #27272a) and re-enabled ThemeProvider with FOUC script
- Removed unused FeatureGrid/FeatureSectors/LogoWall blocks + their
  translation keys; cleaned up the corresponding marketing PNGs from public/

Translations updated across en/de/fr; de-CH inherits from de.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

📝 Walkthrough

Walkthrough

This PR redesigns the Tale marketing website homepage and supporting infrastructure. It introduces dark theme support at the application root, adds new Tagline and IntegrationsBar sections, redesigns the hero and compliance/trust sections, removes the legacy FeatureGrid/FeatureSectors/LogoWall components, and extends the footer with customizable theme switcher variants and language flag toggles. Integration provider icons are added to support the new sections. All UI copy is updated across English, German, and French locales. The changes span the UI component library (accordion), web UI components (language/theme switcher, site footer), and the marketing website application.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • tale-project/tale#1707: Both PRs modify the same packages/ui/src/components/feedback/accordion.tsx AccordionItem trigger styling (typography/chevron/transition-related class changes).

Suggested reviewers

  • yannickmonney
  • Israeltheminer
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description provides comprehensive details on all changes (Hero, Tagline, Features, Integrations, Compliance, FAQ, CTA, Footer, Dark Mode, Cleanup) and includes a test plan, but the pre-merge checklist boxes are empty instead of being marked as PASS/N/A. Complete the pre-merge checklist by ticking boxes or marking N/A with explanations (e.g., confirm bun run check passed, translation updates completed, or note that docs/README updates are not applicable).
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: a marketing home page redesign with new sections, dark mode support, and animations from the .pen design.
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 designs-settings-ai-providers-search-pagination

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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/webui/src/layout/theme-switcher.tsx`:
- Line 61: Remove unnecessary type assertions by using theme directly in calls
like SEGMENTED_ORDER.indexOf(theme), ICONS[theme], and ORDER.indexOf(theme)
(drop all `as Theme` casts), and add reduced-motion overrides by appending
`motion-reduce:transition-none` to the elements that currently rely on
transitions (the segmented indicator, option elements, and the checkmark element
that use `transition-transform`, `transition-colors`, or `transition-opacity`)
so users with reduced motion preferences get no transitions.

In `@services/web/app/components/blocks/cta-deploy.tsx`:
- Line 37: The heading in the CTA component (component: cta-deploy / JSX element
with className containing "text-fg-base text-[32px] ... whitespace-nowrap")
forces a single line and breaks localization; remove the "whitespace-nowrap"
utility (or replace it with "whitespace-normal") from that className so
translated titles can wrap on narrow viewports while keeping the existing text
size and tracking classes intact.

In `@services/web/app/components/blocks/feature-secure.tsx`:
- Around line 9-30: Replace the forbidden "as const" usages by declaring
explicit readonly types: create a FeatureConfig type (shape { key: FeatureKey;
light: string; dark: string }) and a FeatureKey union type (e.g., type
FeatureKey = 'chat' | 'conversations' | 'agents' | 'automations'), then type
FEATURES as readonly FeatureConfig[] and annotate easeOut as readonly [number,
number, number, number]; update the featureKey variable to be typed as
FeatureKey so its type is derived from the declared union rather than using "as
const" on the array.

In `@services/web/app/components/blocks/hero-headline.tsx`:
- Around line 79-94: The hero images are using raw <img> tags; import the Image
component from '`@/components/ui/image`' at the top of the file and replace both
bare <img> elements (the ones with src "/marketing/hero-light.png" and
"/marketing/hero-dark.png") with the Image component, preserving the same props
and classes (src, alt, aria-hidden, className with dark:hidden/dark:block,
loading behavior and non-draggable behavior—use the Image API or an onDragStart
handler if needed) so styling and accessibility remain identical.

In `@services/web/app/components/blocks/integrations-bar.tsx`:
- Around line 67-88: The marquee animation on the element using the
"marquee-track" class (style={{ animation: 'marquee 40s linear infinite' }})
doesn't respect prefers-reduced-motion; update the component to disable or pause
the animation when the user prefers reduced motion — either remove/omit the
inline animation when window.matchMedia('(prefers-reduced-motion:
reduce)').matches is true or (preferably) add a CSS rule using `@media`
(prefers-reduced-motion: reduce) that sets .marquee-track { animation: none;
animation-play-state: paused; } so LogoTile/LOGOS rendering remains identical
but the motion is suppressed for reduced-motion users.
🪄 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: 314d38e1-2d8e-46cc-a629-67f09f05c9c2

📥 Commits

Reviewing files that changed from the base of the PR and between d217a38 and 728b4cc.

⛔ Files ignored due to path filters (66)
  • designs/web/Frame 21 2.png is excluded by !**/*.png
  • designs/web/Frame 21.png is excluded by !**/*.png
  • designs/web/Frame 211.png is excluded by !**/*.png
  • designs/web/Frame 212.png is excluded by !**/*.png
  • designs/web/Frame 22 2.png is excluded by !**/*.png
  • designs/web/Frame 22.png is excluded by !**/*.png
  • designs/web/Frame 23.png is excluded by !**/*.png
  • designs/web/Frame 24 2.png is excluded by !**/*.png
  • designs/web/Frame 24.png is excluded by !**/*.png
  • designs/web/Group 61.png is excluded by !**/*.png
  • designs/web/Group 626.png is excluded by !**/*.png
  • designs/web/dark mode/tale-01-self-hosted-animated (2).svg is excluded by !**/*.svg
  • designs/web/dark mode/tale-02-security-animated (2).svg is excluded by !**/*.svg
  • designs/web/dark mode/tale-03-open-source-animated (2).svg is excluded by !**/*.svg
  • designs/web/hero-light-image.png is excluded by !**/*.png
  • designs/web/images/Group 60.png is excluded by !**/*.png
  • designs/web/images/ISO 27001-test 1.png is excluded by !**/*.png
  • designs/web/images/Pexels Photo by Andrea Piacquadio-1.png is excluded by !**/*.png
  • designs/web/images/Pexels Photo by Andrea Piacquadio.png is excluded by !**/*.png
  • designs/web/images/Rectangle 6665-1.png is excluded by !**/*.png
  • designs/web/images/Rectangle 6665.png is excluded by !**/*.png
  • designs/web/images/Rectangle 6666.png is excluded by !**/*.png
  • designs/web/images/Rectangle 6667.png is excluded by !**/*.png
  • designs/web/images/Rectangle 6668.png is excluded by !**/*.png
  • designs/web/images/Rectangle 666ii5.png is excluded by !**/*.png
  • designs/web/images/Rectkangle 6667.png is excluded by !**/*.png
  • designs/web/images/Rkectangle 6666.png is excluded by !**/*.png
  • designs/web/images/SOC2 Type 2-test 1-1.png is excluded by !**/*.png
  • designs/web/images/SOC2 Type 2-test 1.png is excluded by !**/*.png
  • designs/web/images/SOC2 Type 2jk-test 1.png is excluded by !**/*.png
  • designs/web/images/Screenshot 2026-05-04 at 2.26.26 PM.png is excluded by !**/*.png
  • designs/web/light mode/tale-01-self-hosted-animated.svg is excluded by !**/*.svg
  • designs/web/light mode/tale-02-security-animated.svg is excluded by !**/*.svg
  • designs/web/light mode/tale-03-open-source-animated (3).svg is excluded by !**/*.svg
  • services/web/public/marketing/feature-agents-dark.png is excluded by !**/*.png
  • services/web/public/marketing/feature-agents.png is excluded by !**/*.png
  • services/web/public/marketing/feature-automations-dark.png is excluded by !**/*.png
  • services/web/public/marketing/feature-automations.png is excluded by !**/*.png
  • services/web/public/marketing/feature-chat-dark.png is excluded by !**/*.png
  • services/web/public/marketing/feature-chat-panel.png is excluded by !**/*.png
  • services/web/public/marketing/feature-chat.png is excluded by !**/*.png
  • services/web/public/marketing/feature-conversations-dark.png is excluded by !**/*.png
  • services/web/public/marketing/feature-conversations.png is excluded by !**/*.png
  • services/web/public/marketing/feature-open-source-dark.svg is excluded by !**/*.svg
  • services/web/public/marketing/feature-open-source.svg is excluded by !**/*.svg
  • services/web/public/marketing/feature-secure-approvals.png is excluded by !**/*.png
  • services/web/public/marketing/feature-secure-automations.png is excluded by !**/*.png
  • services/web/public/marketing/feature-secure-chat.png is excluded by !**/*.png
  • services/web/public/marketing/feature-secure-conversations.png is excluded by !**/*.png
  • services/web/public/marketing/feature-security-dark.svg is excluded by !**/*.svg
  • services/web/public/marketing/feature-security.svg is excluded by !**/*.svg
  • services/web/public/marketing/feature-self-hosted-dark.svg is excluded by !**/*.svg
  • services/web/public/marketing/feature-self-hosted.svg is excluded by !**/*.svg
  • services/web/public/marketing/hero-chat.png is excluded by !**/*.png
  • services/web/public/marketing/hero-dark.png is excluded by !**/*.png
  • services/web/public/marketing/hero-light.png is excluded by !**/*.png
  • services/web/public/marketing/sector-finance.png is excluded by !**/*.png
  • services/web/public/marketing/sector-hospitality.png is excluded by !**/*.png
  • services/web/public/marketing/sector-legal.png is excluded by !**/*.png
  • services/web/public/marketing/sector-mock.png is excluded by !**/*.png
  • services/web/public/marketing/security-1-independent.png is excluded by !**/*.png
  • services/web/public/marketing/security-2-stack.png is excluded by !**/*.png
  • services/web/public/marketing/security-3-proven.png is excluded by !**/*.png
  • services/web/public/marketing/security-4-needs.png is excluded by !**/*.png
  • services/web/public/marketing/trust-blocks.png is excluded by !**/*.png
  • services/web/public/marketing/trust-network.png is excluded by !**/*.png
📒 Files selected for processing (29)
  • designs/web/frontpages.pen
  • packages/ui/src/components/feedback/accordion.tsx
  • packages/webui/src/layout/language-switcher.tsx
  • packages/webui/src/layout/site-footer.tsx
  • packages/webui/src/layout/theme-switcher.tsx
  • services/web/app/components/blocks/compliance-trust.tsx
  • services/web/app/components/blocks/cta-deploy.tsx
  • services/web/app/components/blocks/faq-accordion.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/form-card.tsx
  • services/web/app/components/blocks/hero-headline.tsx
  • services/web/app/components/blocks/integrations-bar.tsx
  • services/web/app/components/blocks/logo-wall.tsx
  • services/web/app/components/blocks/segmented-radio.tsx
  • services/web/app/components/blocks/tagline.tsx
  • services/web/app/components/blocks/tier-card.tsx
  • services/web/app/components/icons/integration-icons.tsx
  • services/web/app/components/layout/site-footer.tsx
  • services/web/app/entry-server.tsx
  • services/web/app/globals.css
  • services/web/app/main.tsx
  • services/web/app/pages/home-page.tsx
  • services/web/app/pages/legal-page.tsx
  • services/web/index.html
  • services/web/messages/de.json
  • services/web/messages/en.json
  • services/web/messages/fr.json
💤 Files with no reviewable changes (3)
  • services/web/app/components/blocks/feature-grid.tsx
  • services/web/app/components/blocks/logo-wall.tsx
  • services/web/app/components/blocks/feature-sectors.tsx

function SegmentedThemeSwitcher({ className }: { className?: string }) {
const { t } = useT('themeSwitcher');
const { theme, setTheme } = useTheme();
const activeIndex = Math.max(0, SEGMENTED_ORDER.indexOf(theme as Theme));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove theme as Theme assertions and add reduced-motion overrides for the segmented transitions.

  • Replace SEGMENTED_ORDER.indexOf(theme as Theme) (and other theme as Theme casts like ICONS[theme as Theme] / ORDER.indexOf(theme as Theme)) with direct theme usage—no as needed.
  • Add motion-reduce:transition-none to the indicator/options/checkmark elements that currently use transition-transform, transition-colors, and transition-opacity.
🤖 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 `@packages/webui/src/layout/theme-switcher.tsx` at line 61, Remove unnecessary
type assertions by using theme directly in calls like
SEGMENTED_ORDER.indexOf(theme), ICONS[theme], and ORDER.indexOf(theme) (drop all
`as Theme` casts), and add reduced-motion overrides by appending
`motion-reduce:transition-none` to the elements that currently rely on
transitions (the segmented indicator, option elements, and the checkmark element
that use `transition-transform`, `transition-colors`, or `transition-opacity`)
so users with reduced motion preferences get no transitions.

>
<h2
className="text-accent-base text-[32px] font-medium tracking-[-0.044em] md:text-[56px] md:tracking-[-0.038em]"
className="text-fg-base text-[32px] font-medium tracking-[-0.044em] whitespace-nowrap md:text-[56px] md:tracking-[-0.038em]"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid forcing one-line heading for localized CTA title.

Line 37 uses whitespace-nowrap, which can cause overflow/clipping for longer translations on narrower viewports. Let the heading wrap.

💡 Suggested change
- className="text-fg-base text-[32px] font-medium tracking-[-0.044em] whitespace-nowrap md:text-[56px] md:tracking-[-0.038em]"
+ className="text-fg-base text-[32px] font-medium tracking-[-0.044em] md:text-[56px] md:tracking-[-0.038em]"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className="text-fg-base text-[32px] font-medium tracking-[-0.044em] whitespace-nowrap md:text-[56px] md:tracking-[-0.038em]"
className="text-fg-base text-[32px] font-medium tracking-[-0.044em] md:text-[56px] md:tracking-[-0.038em]"
🤖 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/app/components/blocks/cta-deploy.tsx` at line 37, The heading in
the CTA component (component: cta-deploy / JSX element with className containing
"text-fg-base text-[32px] ... whitespace-nowrap") forces a single line and
breaks localization; remove the "whitespace-nowrap" utility (or replace it with
"whitespace-normal") from that className so translated titles can wrap on narrow
viewports while keeping the existing text size and tracking classes intact.

Comment on lines +9 to +30
const FEATURES = [
{
key: 'chat',
light: '/marketing/feature-chat.png',
dark: '/marketing/feature-chat-dark.png',
},
});

// Frosted glass inner card overlaying the gradient panel. Mirrors the
// Pencil spec: flush-left, ~6% right margin, ~10% top inset, extends past
// the bottom edge so it's clipped by the parent. Rounded only at the
// top-right corner with a subtle gradient stroke (white/20 → white).
const panelGlassCard =
'pointer-events-none absolute top-[10.4%] right-[6%] bottom-[-3.3%] left-0 overflow-hidden rounded-tr-3xl bg-white/10 backdrop-blur-md';

// Gradient stroke for the glass card — top is fainter, bottom edge near
// fully opaque white. Implemented as a separate absolute ring so it sits
// on top of the backdrop-blurred surface.
const panelGlassStroke =
'pointer-events-none absolute inset-0 rounded-tr-3xl [mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] [mask-composite:exclude] p-px [background:linear-gradient(180deg,rgba(255,255,255,0.2)_0%,rgba(255,255,255,1)_100%)]';
{
key: 'conversations',
light: '/marketing/feature-conversations.png',
dark: '/marketing/feature-conversations-dark.png',
},
{
key: 'agents',
light: '/marketing/feature-agents.png',
dark: '/marketing/feature-agents-dark.png',
},
{
key: 'automations',
light: '/marketing/feature-automations.png',
dark: '/marketing/feature-automations-dark.png',
},
] as const;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="services/web/app/components/blocks/feature-secure.tsx"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

# Show the relevant sections with line numbers
nl -ba "$FILE" | sed -n '1,220p'

Repository: tale-project/tale

Length of output: 105


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="services/web/app/components/blocks/feature-secure.tsx"

echo "== file head (with line numbers) =="
cat -n "$FILE" | sed -n '1,220p'

echo
echo "== search for 'as const' and ' as ' patterns =="
rg -n "as const|as [A-Za-z_]" "$FILE" || true

Repository: tale-project/tale

Length of output: 4299


Remove as const assertions in feature-secure.tsx

  • services/web/app/components/blocks/feature-secure.tsx uses as const for both easeOut (line 7) and FEATURES (line 30); repo rules forbid as assertions.
  • Replace FEATURES: ... as const with explicit readonly typing (e.g., FeatureKey/FeatureConfig) and keep featureKey typed from that declared type.
♻️ Proposed fix (FEATURES + featureKey)
-type FeatureKey = 'chat' | 'conversations' | 'agents' | 'automations';
+type FeatureKey = 'chat' | 'conversations' | 'agents' | 'automations';
 
-interface FeatureConfig {
+interface FeatureConfig {
   key: FeatureKey;
   light: string;
   dark: string;
 }
 
-const FEATURES: readonly FeatureConfig[] = [
+const FEATURES: readonly FeatureConfig[] = [
   {
     key: 'chat',
     light: '/marketing/feature-chat.png',
     dark: '/marketing/feature-chat-dark.png',
   },
@@
-];
+];
 
 interface FeatureRowProps {
-  featureKey: (typeof FEATURES)[number]['key'];
+  featureKey: FeatureConfig['key'];
   light: string;
   dark: string;
   index: number;
   isLast: boolean;
 }

Also change easeOut to an explicit readonly tuple type (e.g., readonly [number, number, number, number]) without as.

🤖 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/app/components/blocks/feature-secure.tsx` around lines 9 - 30,
Replace the forbidden "as const" usages by declaring explicit readonly types:
create a FeatureConfig type (shape { key: FeatureKey; light: string; dark:
string }) and a FeatureKey union type (e.g., type FeatureKey = 'chat' |
'conversations' | 'agents' | 'automations'), then type FEATURES as readonly
FeatureConfig[] and annotate easeOut as readonly [number, number, number,
number]; update the featureKey variable to be typed as FeatureKey so its type is
derived from the declared union rather than using "as const" on the array.

Comment on lines 79 to 94
<img
src="/marketing/hero-chat.png"
src="/marketing/hero-light.png"
alt=""
aria-hidden
className="mx-auto block max-h-125 w-full object-cover object-top select-none dark:hidden"
loading="eager"
draggable={false}
/>
<img
src="/marketing/hero-dark.png"
alt=""
aria-hidden
className="w-full select-none"
className="mx-auto hidden max-h-125 w-full object-cover object-top select-none dark:block"
loading="eager"
draggable={false}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace bare <img> with the Image component.

Both hero images use bare <img> tags instead of the custom Image component. As per coding guidelines, all images must use the Image component from @/components/ui/image.

📸 Proposed fix using the Image component

First, add the import at the top of the file:

 import { Button } from '`@tale/ui/button`';
+import { Image } from '`@tale/ui/image`';
 import { motion, useReducedMotion } from 'framer-motion';

Then replace the bare <img> tags:

-        <img
+        <Image
           src="/marketing/hero-light.png"
           alt=""
           aria-hidden
           className="mx-auto block max-h-125 w-full object-cover object-top select-none dark:hidden"
           loading="eager"
           draggable={false}
         />
-        <img
+        <Image
           src="/marketing/hero-dark.png"
           alt=""
           aria-hidden
           className="mx-auto hidden max-h-125 w-full object-cover object-top select-none dark:block"
           loading="eager"
           draggable={false}
         />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<img
src="/marketing/hero-chat.png"
src="/marketing/hero-light.png"
alt=""
aria-hidden
className="mx-auto block max-h-125 w-full object-cover object-top select-none dark:hidden"
loading="eager"
draggable={false}
/>
<img
src="/marketing/hero-dark.png"
alt=""
aria-hidden
className="w-full select-none"
className="mx-auto hidden max-h-125 w-full object-cover object-top select-none dark:block"
loading="eager"
draggable={false}
/>
import { Button } from '`@tale/ui/button`';
import { Image } from '`@tale/ui/image`';
import { motion, useReducedMotion } from 'framer-motion';
Suggested change
<img
src="/marketing/hero-chat.png"
src="/marketing/hero-light.png"
alt=""
aria-hidden
className="mx-auto block max-h-125 w-full object-cover object-top select-none dark:hidden"
loading="eager"
draggable={false}
/>
<img
src="/marketing/hero-dark.png"
alt=""
aria-hidden
className="w-full select-none"
className="mx-auto hidden max-h-125 w-full object-cover object-top select-none dark:block"
loading="eager"
draggable={false}
/>
<Image
src="/marketing/hero-light.png"
alt=""
aria-hidden
className="mx-auto block max-h-125 w-full object-cover object-top select-none dark:hidden"
loading="eager"
draggable={false}
/>
<Image
src="/marketing/hero-dark.png"
alt=""
aria-hidden
className="mx-auto hidden max-h-125 w-full object-cover object-top select-none dark:block"
loading="eager"
draggable={false}
/>
🤖 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/app/components/blocks/hero-headline.tsx` around lines 79 - 94,
The hero images are using raw <img> tags; import the Image component from
'`@/components/ui/image`' at the top of the file and replace both bare <img>
elements (the ones with src "/marketing/hero-light.png" and
"/marketing/hero-dark.png") with the Image component, preserving the same props
and classes (src, alt, aria-hidden, className with dark:hidden/dark:block,
loading behavior and non-draggable behavior—use the Image API or an onDragStart
handler if needed) so styling and accessibility remain identical.

Comment on lines +67 to +88
<div
role="list"
aria-label={t('integrations.title')}
className="group w-full overflow-hidden mask-[linear-gradient(to_right,transparent,#000_10%,#000_90%,transparent)]"
>
<div
className="marquee-track flex w-max items-center gap-6 group-hover:[animation-play-state:paused] md:gap-10"
style={{ animation: 'marquee 40s linear infinite' }}
>
{LOGOS.map(({ Icon, name }) => (
<LogoTile key={`a-${name}`} Icon={Icon} name={name} />
))}
{LOGOS.map(({ Icon, name }) => (
<LogoTile
key={`b-${name}`}
Icon={Icon}
name={name}
ariaHidden
/>
))}
</div>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Marquee animation must respect prefers-reduced-motion.

The continuous marquee animation at line 74 does not respect the user's motion preferences. As per coding guidelines, all animations must honor prefers-reduced-motion. Users with vestibular disorders who enable this setting should see a static or paused state instead of continuous scrolling.

♿ Proposed fix to respect reduced motion
           <div
             role="list"
             aria-label={t('integrations.title')}
             className="group w-full overflow-hidden mask-[linear-gradient(to_right,transparent,`#000_10`%,`#000_90`%,transparent)]"
           >
             <div
               className="marquee-track flex w-max items-center gap-6 group-hover:[animation-play-state:paused] md:gap-10"
-              style={{ animation: 'marquee 40s linear infinite' }}
+              style={{
+                animation: reduceMotion
+                  ? 'none'
+                  : 'marquee 40s linear infinite',
+              }}
             >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
role="list"
aria-label={t('integrations.title')}
className="group w-full overflow-hidden mask-[linear-gradient(to_right,transparent,#000_10%,#000_90%,transparent)]"
>
<div
className="marquee-track flex w-max items-center gap-6 group-hover:[animation-play-state:paused] md:gap-10"
style={{ animation: 'marquee 40s linear infinite' }}
>
{LOGOS.map(({ Icon, name }) => (
<LogoTile key={`a-${name}`} Icon={Icon} name={name} />
))}
{LOGOS.map(({ Icon, name }) => (
<LogoTile
key={`b-${name}`}
Icon={Icon}
name={name}
ariaHidden
/>
))}
</div>
</div>
<div
role="list"
aria-label={t('integrations.title')}
className="group w-full overflow-hidden mask-[linear-gradient(to_right,transparent,`#000_10`%,`#000_90`%,transparent)]"
>
<div
className="marquee-track flex w-max items-center gap-6 group-hover:[animation-play-state:paused] md:gap-10"
style={{
animation: reduceMotion
? 'none'
: 'marquee 40s linear infinite',
}}
>
{LOGOS.map(({ Icon, name }) => (
<LogoTile key={`a-${name}`} Icon={Icon} name={name} />
))}
{LOGOS.map(({ Icon, name }) => (
<LogoTile
key={`b-${name}`}
Icon={Icon}
name={name}
ariaHidden
/>
))}
</div>
</div>
🤖 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/app/components/blocks/integrations-bar.tsx` around lines 67 -
88, The marquee animation on the element using the "marquee-track" class
(style={{ animation: 'marquee 40s linear infinite' }}) doesn't respect
prefers-reduced-motion; update the component to disable or pause the animation
when the user prefers reduced motion — either remove/omit the inline animation
when window.matchMedia('(prefers-reduced-motion: reduce)').matches is true or
(preferably) add a CSS rule using `@media` (prefers-reduced-motion: reduce) that
sets .marquee-track { animation: none; animation-play-state: paused; } so
LogoTile/LOGOS rendering remains identical but the motion is suppressed for
reduced-motion users.

- theme-switcher: drop unnecessary `as Theme` casts, add
  `motion-reduce:transition-none` to the segmented indicator/option/checkmark
  transitions so reduced-motion users get a static UI
- cta-deploy: change `whitespace-nowrap` to `md:whitespace-nowrap` so the
  hero CTA title can still wrap on narrow viewports for longer translations
- feature-secure: replace `as const` on FEATURES + easeOut with explicit
  `FeatureKey` / `FeatureConfig` types + a typed readonly tuple
- hero-headline: switch the two bare `<img>` tags to the shared `Image`
  component from `@tale/ui/image`

Marquee already respects `prefers-reduced-motion` via the existing CSS rule
in services/web/app/globals.css, so no change needed there.
@AdeolaAdekoya AdeolaAdekoya merged commit eacefe6 into main May 22, 2026
23 of 25 checks passed
@AdeolaAdekoya AdeolaAdekoya deleted the designs-settings-ai-providers-search-pagination branch May 22, 2026 00:00
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