Skip to content

(SP: 2) [Frontend] Redesign Home Hero & Add Features Section#319

Merged
ViktorSvertoka merged 7 commits intodevelopfrom
feat/ui-improvements
Feb 13, 2026
Merged

(SP: 2) [Frontend] Redesign Home Hero & Add Features Section#319
ViktorSvertoka merged 7 commits intodevelopfrom
feat/ui-improvements

Conversation

@yevheniidatsenko
Copy link
Collaborator

@yevheniidatsenko yevheniidatsenko commented Feb 13, 2026

Description

Redesigned the main landing page to include a new "Features" section and improved the "Welcome" hero area with better visuals and full localization.

Changes

  • New Section: Added FeaturesHeroSection to showcase capabilities (Q&A, Quizzes, Performance).
  • Redesign: Updated WelcomeHeroSection (formerly Legacy) with new description and improved layout.
  • i18n: Added full translation support (EN, UK, PL) for all hero content, removing hardcoded text.
  • Content: Updated About page stats to reflect current numbers (LinkedIn 1.5k+).

Database Changes (if applicable)

  • Schema migration required
  • Seed data updated
  • Breaking changes to existing queries
  • Transaction-safe migration
  • Migration tested locally on Neon

How Has This Been Tested?

  • Tested locally
  • Verified in development environment
  • Checked responsive layout (if UI-related)
  • Tested accessibility (keyboard / screen reader)

Checklist

Before submitting

  • Code has been self-reviewed
  • No TypeScript or console errors
  • Code follows project conventions
  • Scope is limited to this feature/fix
  • No unrelated refactors included
  • English used in code, commits, and docs
  • New dependencies discussed with team
  • Database migration tested locally (if applicable)
  • GitHub Projects card moved to In Review

Reviewers

Summary by CodeRabbit

  • New Features

    • Interactive Q&A flip cards with 3D tilt, keyboard navigation, auto-advance, and per-question tips
    • Animated floating code snippets and an interactive particle constellation background
    • Separate welcome and features hero sections with updated welcome visuals and CTA
  • Updates

    • Motion-enhanced CTA with hover/parallax text animations
    • Homepage copy expanded across locales (new feature/flipCard texts)
    • Added CSS utilities for 3D flip effects
    • LinkedIn follower display updated to 1.5k+
  • Removed

    • Legacy code-card hero visuals and related card components

- Rename LegacyHeroSection → WelcomeHeroSection
- Rename HeroSection → FeaturesHeroSection
- Add welcomeDescription translation key to eliminate duplication
- Translate all hardcoded text (headings, badges, CTAs)
- Improve Ukrainian/Polish translations for better readability
- Remove unused legacy components and images
Resolved conflicts in translation files by preserving both welcomeDescription and ogImageAlt keys
@netlify
Copy link

netlify bot commented Feb 13, 2026

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit dcf411e
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/698f5ae21711600008544ad9
😎 Deploy Preview https://deploy-preview-319--develop-devlovers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel
Copy link
Contributor

vercel bot commented Feb 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
devlovers-net Ready Ready Preview, Comment Feb 13, 2026 5:11pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Replaces the legacy homepage hero with two sections (WelcomeHeroSection then FeaturesHeroSection), removes legacy hero/code-card components, adds interactive visuals (FlipCardQA, FloatingCode, InteractiveConstellation), rewrites InteractiveCTAButton, adds 3D CSS utilities, updates i18n content, and bumps LinkedIn fallback/display value.

Changes

Cohort / File(s) Summary
Page entry
frontend/app/[locale]/page.tsx
Swapped hero rendering: removed HeroSection, now renders WelcomeHeroSection then FeaturesHeroSection.
Welcome hero
frontend/components/home/WelcomeHeroSection.tsx, frontend/components/home/WelcomeHeroBackground.tsx
Added WelcomeHeroSection (default export); renamed/rewrote background to WelcomeHeroBackground and adjusted decorative layers; composes constellation and CTA.
Features hero & interactions
frontend/components/home/FeaturesHeroSection.tsx, frontend/components/home/FlipCardQA.tsx, frontend/components/home/FloatingCode.tsx
Added FeaturesHeroSection; new FlipCardQA (3D tilt/flip quiz with keyboard/mouse, auto-advance, progress) and FloatingCode (animated, typewriter-like code snippets).
Interactive visuals / CTA
frontend/components/home/InteractiveConstellation.tsx, frontend/components/home/InteractiveCTAButton.tsx
Added InteractiveConstellation canvas particle system; significantly rewrote InteractiveCTAButton to use motion/ref-forwarding, mouse-tracking parallax, animated text variants and improved hover behavior.
Removed legacy hero cards
frontend/components/home/HeroSection.tsx, frontend/components/home/HeroCodeCards.tsx, frontend/components/home/CodeCard.tsx
Deleted legacy hero and code-card components and their exports as part of the hero refactor.
Styling
frontend/app/globals.css
Added CSS utilities for 3D card effects: perspective-1000, preserve-3d, backface-hidden.
About / stats
frontend/lib/about/stats.ts, frontend/components/about/HeroSection.tsx
Bumped LinkedIn follower fallback from 1400→1500; updated displayed default in About hero to 1.5k+.
i18n message updates
frontend/messages/en.json, frontend/messages/pl.json, frontend/messages/uk.json
Added homepage.welcomeDescription, featuresHeading, featureBadges, featuresCta, and a flipCard section (UI labels and sample questions 1–5) across locales; added a CTA variant entry.
Minor / formatting
frontend/components/ui/particle-canvas.tsx
Trailing newline added; no functional changes.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Page as "Page"
    participant Welcome as "WelcomeHeroSection"
    participant Constell as "InteractiveConstellation"
    participant CTA as "InteractiveCTAButton"
    participant Features as "FeaturesHeroSection"
    participant Flip as "FlipCardQA"
    participant Floating as "FloatingCode"

    User->>Page: Request homepage
    Page->>Welcome: Render WelcomeHeroSection
    Welcome->>Constell: Mount particle canvas
    Constell->>Constell: Animate, handle mouse
    Welcome->>CTA: Render interactive CTA
    User->>CTA: Hover / move
    CTA->>CTA: Track mouse, animate/parallax
    Page->>Features: Render FeaturesHeroSection
    Features->>Flip: Mount FlipCardQA
    User->>Flip: Interact (click/keys)
    Flip->>Flip: Tilt/flip, auto-advance
    Features->>Floating: Mount FloatingCode
    Floating->>Floating: Typewriter & motion animations
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

UI, refactor

Suggested reviewers

  • AM1007
  • ViktorSvertoka

Poem

🐰 I hopped onto the homepage bright,
Split the hero into day and night,
Cards that flip and code that sings,
Particles dance on gentle wings,
A carrot-cheer for shiny things! 🥕✨

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: redesign of the home hero section with a new welcome hero and addition of a features section with Q&A and quiz capabilities.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into develop

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ui-improvements

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
Contributor

@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: 8

🤖 Fix all issues with AI agents
In `@frontend/components/home/FeaturesHeroSection.tsx`:
- Around line 25-30: The heading concatenation in FeaturesHeroSection.tsx causes
"Ace Your NextTechnical Interview" on sm+ screens because the <br
className="sm:hidden" /> is hidden and the translation
t('featuresHeading.aceYourNext') lacks a trailing space; fix by inserting an
explicit space between the text node and the <span> (e.g. add {" "} or &nbsp;
after {t('featuresHeading.aceYourNext')}) inside the h1, or alternatively add a
trailing space to the EN translation key t('featuresHeading.aceYourNext') in
en.json if you prefer a locale-specific fix.

In `@frontend/components/home/FlipCardQA.tsx`:
- Around line 16-72: The questions array is hardcoded in English (Question
objects: id 1–5, fields question/answer/tip/category) and several UI labels
("Click to reveal"/"Tap to reveal", "Answer", "Pro Tip", "Click again to see
question") are not localized; replace hardcoded strings by wiring next-intl's
useTranslations hook in FlipCardQA.tsx, move each question/answer/tip/category
text to translation keys (e.g.,
flipCard.questions.[id].question/answer/tip/category) and use t('...') when
building the questions array and rendering the UI labels so all displayed
strings come from the t(...) calls rather than literals.
- Around line 119-150: The effect schedules two setTimeouts when progress
reaches 100% (inside useEffect) but only clears the interval `timer`; capture
the timeout IDs (e.g., `flipTimeoutId` and `indexTimeoutId`) when calling
setTimeout and store them in local variables, then clear them with
clearTimeout(...) in the effect cleanup alongside clearInterval(timer) to
prevent state updates after unmount; ensure you still call setIsFlipped,
setCurrentIndex, and setProgress from those timeouts but cancel both timeouts on
cleanup or before scheduling new ones.
- Around line 218-232: The rotateY value is being driven in two
places—style.rotateY (rotateYSpring) and animate.rotateY—which lets animate
override the mouse-tilt spring; update the flip logic so rotateYSpring includes
the flip offset instead of using animate: compute rotateYSpring as rotateYSpring
+ (isFlipped ? 180 : 0) (or set the spring target to 180/0 based on isFlipped)
and remove rotateY from the motion.div animate prop so the tilt (rotateYSpring
and rotateXSpring) and flip (isFlipped) are combined in one spring-driven
source; adjust usage around motion.div, rotateYSpring, rotateXSpring, isFlipped
and handleFlip accordingly.
- Around line 152-170: The global keydown handler in the useEffect is hijacking
Space/Enter/Arrow keys site-wide; remove the window-level listener and instead
attach a scoped onKeyDown to the card element (the focusable motion.div that
already uses tabIndex={0}), using a ref (e.g., cardRef) to verify focus
(document.activeElement === cardRef.current) or simply rely on the event target
being the card before calling e.preventDefault() and invoking setIsFlipped or
setCurrentIndex (which use questions); move the logic from handleKeyDown into
that onKeyDown handler and delete the useEffect-added
window.addEventListener/removeEventListener.

In `@frontend/components/home/FloatingCode.tsx`:
- Around line 77-107: The effect starts timers inside startTyping (typeInterval
and nested setTimeouts) but only cleans up the initial timeout; fix by tracking
all timer IDs in refs (e.g., store the initial timeout, typeInterval, and any
nested timeouts in a mutable ref like timersRef) and clear them all in the
useEffect cleanup using clearInterval/clearTimeout; update startTyping to push
each returned ID into timersRef.current and ensure you clear timersRef.current
and reset relevant state (isTyping/displayedCode) on unmount or dependency
change to prevent state updates after unmount.

In `@frontend/components/home/InteractiveConstellation.tsx`:
- Around line 186-194: Guard against division by zero in the particle-mouse and
particle-center force calculations by ensuring you never divide by dist or
distCenter when they are 0: in the block using mouseRef.current, replace the
direct normalization (dx / dist, dy / dist) with a safe normalization that first
checks dist (e.g., skip applying the force if dist === 0 or clamp dist to a
small epsilon) before updating p.vx and p.vy with (interactionRadius -
dist)/interactionRadius * magneticForce; apply the same guard/clamping to the
code that uses distCenter (the center-force code) so you never compute (dxCenter
/ distCenter) or (dyCenter / distCenter) when distCenter is zero.

In `@frontend/components/home/WelcomeHeroSection.tsx`:
- Around line 48-67: The scroll indicator uses hardcoded white colors which will
be invisible on light backgrounds; update the Tailwind classes on the outer
motion.div, the rounded container (border-...), the animated dot (bg-...), and
the ChevronDown (text-...) to theme-aware classes (e.g. use conditional dark:
and light: variants or neutral color tokens like text-white/50
dark:text-white/50 light:text-gray-600 / border-white/30 dark:border-white/30
light:border-gray-300 / bg-white/70 dark:bg-white/70 light:bg-gray-600) so the
element has sufficient contrast in both light and dark themes and retains the
same visual hierarchy and animations declared in the existing motion.div and
ChevronDown usage.
🧹 Nitpick comments (11)
frontend/components/home/WelcomeHeroBackground.tsx (1)

1-1: Unnecessary React import.

With the modern JSX transform (enabled in this project), import React from 'react' is not needed unless you reference React explicitly — which this file doesn't.

🧹 Remove unused import
-import React from 'react';
-
frontend/components/home/FloatingCode.tsx (1)

143-148: Math.random() in transition recalculates on every render.

duration: 5 + Math.random() * 3 inside the animate/transition props produces a new random value each render, potentially causing animation restarts or flicker. Memoize or hoist the random duration per snippet.

♻️ Suggested approach — add a stable random duration to each snippet

Extend the CodeSnippet interface or compute it once:

 const snippets: CodeSnippet[] = [
   {
     id: 'react-hook',
     code: 'useEffect(() => {\n  fetchData();\n}, []);',
     language: 'react',
     position: { x: '12%', y: '25%' },
     rotate: -6,
     delay: 0,
     color: '#61DAFB',
+    floatDuration: 6.2, // pre-computed random value
   },
   // ... same for other snippets
 ];

Then use snippet.floatDuration instead of 5 + Math.random() * 3 in the transition prop.

frontend/components/home/InteractiveConstellation.tsx (3)

30-125: Move icons map outside the component.

This object of pure drawing functions is recreated on every render. Since the functions capture no component state, hoist it to module scope. This also avoids a missing-dependency lint warning — icons is used inside the useEffect (line 264) but not listed in its dependency array.

♻️ Move to module scope
+const icons: Record<IconType, (ctx: CanvasRenderingContext2D, size: number) => void> = {
+  react: (ctx, size) => { /* ... */ },
+  // ... all other icon draw functions
+};
+
 export function InteractiveConstellation() {
   const canvasRef = useRef<HTMLCanvasElement>(null);
   // ...
-  const icons: Record<IconType, ...> = { ... };

179-179: Unused variable particleColor.

particleColor is assigned on line 179 but never referenced. currentParticleColor (line 234) is used instead. Remove it to avoid confusion.

🧹 Remove dead code
-      const particleColor = `rgba(${r}, ${g}, ${b}, 0.8)`;
-      
       particles.forEach((p, i) => {

253-266: Canvas shadowBlur on every particle is expensive.

shadowBlur forces the browser to apply a Gaussian blur per draw call. With ~50 particles per frame, this can degrade performance on lower-end devices. Consider batching icons without per-particle shadow, or using a single post-processing glow pass.

frontend/app/globals.css (1)

280-292: Consider using Tailwind CSS v4 built-in 3D utilities instead of custom classes.

Tailwind v4 ships with backface-hidden, backface-visible, and transform-3d (for preserve-3d) utilities natively. For perspective, use arbitrary values like perspective-[1000px]. These custom utility classes may be redundant.

frontend/app/[locale]/page.tsx (1)

60-61: Move import to the top of the file with the other imports.

The WelcomeHeroSection import is placed between generateMetadata and Home, separated from the other imports at the top. Consolidate it alongside the FeaturesHeroSection import on line 3 for consistency and readability.

♻️ Proposed fix
 import { getTranslations } from 'next-intl/server';
 
 import FeaturesHeroSection from '@/components/home/FeaturesHeroSection';
+import WelcomeHeroSection from '@/components/home/WelcomeHeroSection';
 
 export async function generateMetadata({

And remove lines 60-61.

frontend/components/home/FeaturesHeroSection.tsx (2)

36-60: Consider extracting repeated badge markup into a data-driven loop.

The three badges share identical DOM structure and differ only in color palette, icon, and translation key. A small array + .map() would eliminate ~40 lines of duplication.

♻️ Sketch
const badges = [
  { icon: MessageCircleQuestion, key: 'smartQA' as const, color: 'blue' },
  { icon: BrainCircuit, key: 'adaptiveQuizzes' as const, color: 'purple' },
  { icon: TrendingUp, key: 'performance' as const, color: 'emerald' },
] as const;

// Then in JSX:
{badges.map(({ icon: Icon, key, color }) => (
  <div key={key} className={`group relative overflow-hidden rounded-full border border-${color}-200/60 ...`}>
    {/* Note: Tailwind needs full class names for purging — use a color-class map or safelist */}
    ...
  </div>
))}

Note: Since Tailwind cannot purge dynamic class concatenation, you'd need a mapping object for the color class strings (e.g., colorClasses[color].border, etc.) to keep it purge-safe.


65-87: CTA links look good; consider using lucide icons instead of inline SVGs.

The lucide-react library is already imported. You could replace the inline SVG arrow (lines 71-73) and lightning bolt (lines 83-84) with <ArrowRight> and <Zap> from lucide-react for consistency with the badge icons above.

frontend/components/home/InteractiveCTAButton.tsx (2)

18-18: isFirstRender is dead state — set but never read.

isFirstRender is initialized to true, toggled to false in the mount effect, but its value is never consumed anywhere in the component. Remove it to avoid confusion.

♻️ Proposed fix
-    const [isFirstRender, setIsFirstRender] = useState(true);
...
-    useEffect(() => {
-        setIsFirstRender(false);
-    }, []);

Also applies to: 32-34


76-83: Consider requestAnimationFrame instead of setInterval for the gradient rotation.

The 16ms setInterval approximates 60fps but can drift and isn't synchronized with the browser's paint cycle. requestAnimationFrame is the standard approach for visual animations and will automatically pause when the tab is backgrounded.

♻️ Sketch using rAF
     useEffect(() => {
         if (isHovered) {
-             const interval = setInterval(() => {
-                 rotate.set((rotate.get() + 2) % 360);
-             }, 16);
-             return () => clearInterval(interval);
+             let frameId: number;
+             const step = () => {
+                 rotate.set((rotate.get() + 2) % 360);
+                 frameId = requestAnimationFrame(step);
+             };
+             frameId = requestAnimationFrame(step);
+             return () => cancelAnimationFrame(frameId);
         }
     }, [isHovered, rotate]);

Copy link
Contributor

@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: 3

🤖 Fix all issues with AI agents
In `@frontend/components/home/InteractiveConstellation.tsx`:
- Line 179: Remove the dead local constant by deleting the unused declaration
const particleColor = `rgba(${r}, ${g}, ${b}, 0.8)` (it is never read); the
component already uses currentParticleColor for per-particle alpha, so remove
the particleColor declaration to avoid unused variable warnings and keep
currentParticleColor as the sole color value.

In `@frontend/messages/en.json`:
- Around line 63-66: The "aceYourNext" value currently contains a trailing space
which is fragile; remove the trailing space from the "aceYourNext" string and
either add the separating space when rendering (concatenate "aceYourNext" + " "
+ "technicalInterview") or consolidate into a single key (e.g.,
"featuresHeading": { "aceYourNextTechnicalInterview": "Ace Your Next Technical
Interview" }) so that rendering does not rely on trailing whitespace.

In `@frontend/messages/uk.json`:
- Around line 63-66: The "aceYourNext" value in featuresHeading contains a
trailing space which makes localization fragile; remove the trailing space from
"aceYourNext" so it reads "Пройди наступну" and ensure any UI that concatenates
featuresHeading.aceYourNext and featuresHeading.technicalInterview inserts
spacing explicitly (or use a single combined string) to mirror the fix applied
to en.json; update the locale entry for featuresHeading (aceYourNext and/or
combine with technicalInterview) accordingly.
🧹 Nitpick comments (5)
frontend/components/home/FloatingCode.tsx (2)

146-161: Math.random() in the render path causes unstable animation parameters.

duration: 5 + Math.random() * 3 on line 157 is evaluated on every render. Since displayedCode state updates ~every 50-80 ms during the typing phase, this produces a new random duration each time. While Framer Motion generally won't restart an in-progress animate, feeding it a constantly changing transition object is wasteful (new object allocation each render) and risks subtle animation glitches if the component re-renders for other reasons mid-cycle.

Compute the random duration once (e.g., via useMemo or include it in the CodeSnippet data) so the value is stable across renders.

♻️ Option A – add to snippet data (simplest)

Add a floatDuration field to the snippet objects:

 interface CodeSnippet {
   id: string;
   code: string;
   language: string;
   position: { x: string; y: string };
   rotate: number;
   delay: number;
   color: string;
+  floatDuration: number;
 }

Precompute it in each snippet entry (e.g., floatDuration: 6.2) and reference it in the transition:

         transition={{
-          duration: 5 + Math.random() * 3, 
+          duration: snippet.floatDuration,
           repeat: Infinity,
♻️ Option B – useMemo inside CodeBlock
 function CodeBlock({ snippet }: { snippet: CodeSnippet }) {
   const [displayedCode, setDisplayedCode] = useState('');
   const [isTyping, setIsTyping] = useState(false);
+  const floatDuration = useMemo(() => 5 + Math.random() * 3, []);

Then reference floatDuration in the transition prop.


163-186: displayedCode.split('\n') is computed twice per render.

Line 165 splits the string for iteration, then line 176 splits it again inside the .map() callback (on every iteration) just to check if the current line is the last. This is an unnecessary repeated allocation.

♻️ Proposed fix – split once
         <pre className="font-mono text-[11px] leading-relaxed text-gray-600 dark:text-gray-400">
           <code>
-            {displayedCode.split('\n').map((line, i) => (
-              <div key={i} className="flex min-h-[1.5em]">
+            {(() => {
+              const lines = displayedCode.split('\n');
+              return lines.map((line, i) => (
+              <div key={i} className="flex min-h-[1.5em]">                
                 <span className="mr-3 w-3 select-none text-right opacity-30 text-xs">{i + 1}</span>
                 <span 
                   style={{ 
                     color: i === 0 ? snippet.color : 'inherit',
                     textShadow: i === 0 ? `0 0 10px ${snippet.color}40` : 'none',
                     fontWeight: i === 0 ? 600 : 400,
                   }}
                 >
                   {line}
-                  {i === displayedCode.split('\n').length - 1 && (
+                  {i === lines.length - 1 && (
                     <motion.span
                       animate={{ opacity: [1, 0, 1] }}
                       transition={{ duration: 0.8, repeat: Infinity }}
                       className="ml-0.5 inline-block h-3 w-1.5 align-middle bg-current opacity-70"
                     />
                   )}
                 </span>
               </div>
-            ))}
+              ));
+            })()}
           </code>
         </pre>

Alternatively, extract const lines = useMemo(() => displayedCode.split('\n'), [displayedCode]); at the top of the component for cleaner JSX.

frontend/components/home/InteractiveConstellation.tsx (2)

30-125: Move icons outside the component or into the useEffect to avoid recreation on every render.

The icons record is a pure mapping of drawing functions with no dependency on component state or props. Defining it in the component body means it's recreated each render and captured as a stale closure by the useEffect (which only lists theme as a dependency). Move it to module scope to fix the stale-closure lint warning and eliminate the per-render allocation.

♻️ Suggested approach
+const icons: Record<IconType, (ctx: CanvasRenderingContext2D, size: number) => void> = {
+  react: (ctx, size) => { /* ... */ },
+  // ... all icon entries ...
+};
+
 export function InteractiveConstellation() {
   const canvasRef = useRef<HTMLCanvasElement>(null);
   // ...
-  const icons: Record<IconType, ...> = { ... };

127-329: Theme change re-initializes all particles, causing a visual reset.

Because theme is in the useEffect dependency array (line 329), every theme toggle destroys and recreates all particles from random positions. Consider reading the theme inside draw() (you already do the document.documentElement.classList check on line 174) and removing theme from deps, so the animation persists across theme switches.

♻️ Suggested approach
-  const { theme } = useTheme();
+  const { resolvedTheme } = useTheme();
+  const themeRef = useRef(resolvedTheme);
+  useEffect(() => { themeRef.current = resolvedTheme; }, [resolvedTheme]);
   // ...
   useEffect(() => {
     // inside draw():
-    const isDark = theme === 'dark' || document.documentElement.classList.contains('dark');
+    const isDark = themeRef.current === 'dark';
     // ...
-  }, [theme]);
+  }, []); // stable — never re-initializes
frontend/components/home/FlipCardQA.tsx (1)

76-76: Consider narrowing the any[] type in useTransform callback.

The latest parameter is typed as any[], which bypasses type checking. Framer Motion's useTransform with multiple inputs has limited built-in typing, but you can cast more precisely:

-  const rotateY = useTransform([tiltY, flipRotation], (latest: any[]) => latest[0] + latest[1]);
+  const rotateY = useTransform([tiltY, flipRotation], (latest: number[]) => latest[0] + latest[1]);

Copy link
Contributor

@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: 3

🤖 Fix all issues with AI agents
In `@frontend/components/home/FlipCardQA.tsx`:
- Around line 25-31: The Next.js color in the categoryColors map (const
categoryColors) is set to '#FFFFFF', which becomes nearly invisible when used to
generate the badge background (`${categoryColor}15`) and border
(`${categoryColor}30`); update the 'Next.js' entry in categoryColors to a darker
value (e.g., '#000000' or a neutral gray) so the generated translucent
background and border are visible on light-mode cards, and run the UI to confirm
contrast for both light and dark themes.
- Around line 341-365: Replace the hardcoded English aria-label in FlipCardQA
(the button rendering inside the questions.map) with a localized string from
useTranslations: add a key like homepage.flipCard.ui.goToQuestion with
placeholder {index} to your locale files (en.json, pl.json, uk.json), then call
the translation function inside the component to compose the aria-label (pass
index + 1) instead of using the template literal `Go to question ${index + 1}`;
keep the rest of the props (key, onClick -> handleNavigate, aria-current, etc.)
unchanged.

In `@frontend/messages/en.json`:
- Line 61: The key ctaVariants.8 currently contains a debug/test string ("Hello,
new commit"); replace it with a meaningful CTA consistent with other variants
(e.g., a short motivational phrase) or remove the key entirely if unused, and
ensure any corresponding translation entries or references that expect
ctaVariants length are updated to avoid missing-text errors; look for the
ctaVariants object in en.json and change the value for "8" accordingly.
🧹 Nitpick comments (5)
frontend/components/home/InteractiveConstellation.tsx (4)

30-125: Move icons outside the component — it's a pure constant recreated every render.

This record has no dependency on props, state, or theme, yet it's defined inside the component body and captured by the useEffect closure. This causes two issues: (1) it's needlessly recreated on every render, and (2) ESLint's react-hooks/exhaustive-deps rule would flag it as a missing dependency of the effect — but adding it would trigger infinite re-runs since the object identity changes each render.

Hoist it to module scope to fix both.

♻️ Proposed fix
+'use client';
+
+import { useTheme } from 'next-themes';
+import React, { useEffect, useRef } from 'react';
+
+// ... interfaces ...
+
+const icons: Record<IconType, (ctx: CanvasRenderingContext2D, size: number) => void> = {
+  react: (ctx, size) => { /* ... same ... */ },
+  // ... all other icons ...
+};
+
 export function InteractiveConstellation() {
   const canvasRef = useRef<HTMLCanvasElement>(null);
   // ...
-  const icons: Record<IconType, ...> = { ... };
-
   useEffect(() => {
     // icons is now a stable module-level reference

144-148: Canvas will appear blurry on HiDPI / Retina displays.

The canvas dimensions are set to CSS pixels without accounting for window.devicePixelRatio. On 2x+ screens, this produces visibly blurred output.

♻️ Proposed fix
     const resize = () => {
-      canvas.width = container.clientWidth;
-      canvas.height = container.clientHeight;
+      const dpr = window.devicePixelRatio || 1;
+      canvas.width = container.clientWidth * dpr;
+      canvas.height = container.clientHeight * dpr;
+      canvas.style.width = `${container.clientWidth}px`;
+      canvas.style.height = `${container.clientHeight}px`;
+      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
       initParticles();
     };

With this change, all drawing coordinates remain in CSS-pixel space while the backing buffer is high-resolution. Note that canvas.width/canvas.height references later in the code (bounds checks, center calc, etc.) would need to use the CSS dimensions (container.clientWidth/clientHeight) instead, or you can store them separately.


210-226: Repulsion loop is O(n²) with redundant pair checks — consider halving iterations.

The repulsion loop iterates all j from 0..n for each i, meaning every pair (i, j) is visited twice. The connection loop at line 270 already optimizes this with j = i + 1. You could apply the same pattern here and apply equal-opposite forces to both particles in a single pass, halving the work per frame.

Not urgent at n ≤ 50, but a straightforward improvement if the particle count grows.


127-328: useEffect re-runs on every theme change, resetting all particles.

When theme changes, the cleanup runs and resize()initParticles() reinitializes particles at random positions, causing a visible "jump." Consider decoupling the theme read from the effect lifecycle — e.g., store theme-derived colors in a ref so the animation loop picks up changes without restarting.

♻️ Sketch
+ const themeRef = useRef(theme);
+ useEffect(() => { themeRef.current = theme; }, [theme]);
 
  useEffect(() => {
    // ...
    const draw = () => {
-     const isDark = theme === 'dark' || ...;
+     const isDark = themeRef.current === 'dark' || ...;
      // ...
    };
    // ...
-  }, [theme]);
+  }, []); // runs once; theme changes flow through themeRef
frontend/components/home/FeaturesHeroSection.tsx (1)

36-60: Consider extracting badge markup into a small helper.

The three feature badges share identical structure, differing only in color scheme, icon, and label. A small FeatureBadge component or a mapped array could reduce the ~24 lines of near-duplicate markup, improving maintainability.

♻️ Example refactor
const badges = [
  { icon: MessageCircleQuestion, label: 'featureBadges.smartQA', color: 'blue' },
  { icon: BrainCircuit, label: 'featureBadges.adaptiveQuizzes', color: 'purple' },
  { icon: TrendingUp, label: 'featureBadges.performance', color: 'emerald' },
] as const;

// Then map over badges in JSX:
{badges.map(({ icon: Icon, label, color }) => (
  <div key={label} className={`group relative overflow-hidden rounded-full border border-${color}-200/60 ...`}>
    ...
    <Icon className="h-4 w-4" />
    <span className="whitespace-nowrap">{t(label)}</span>
  </div>
))}

Note: Tailwind doesn't support dynamic class names via template literals out of the box — you'd need to use a mapping object or clsx for the color variants. This is just illustrative.

Comment on lines +25 to +31
const categoryColors: Record<string, string> = {
React: '#61DAFB',
JavaScript: '#F7DF1E',
'Node.js': '#339933',
'Next.js': '#FFFFFF',
TypeScript: '#3178C6',
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Next.js category color #FFFFFF may be invisible on light backgrounds.

The white color is used for the category badge background (${categoryColor}15) and border (${categoryColor}30). On light-mode cards, these will be nearly invisible since they produce near-white-on-white styling.

Consider using the Next.js brand dark color (#000000) or a neutral gray instead.

🤖 Prompt for AI Agents
In `@frontend/components/home/FlipCardQA.tsx` around lines 25 - 31, The Next.js
color in the categoryColors map (const categoryColors) is set to '#FFFFFF',
which becomes nearly invisible when used to generate the badge background
(`${categoryColor}15`) and border (`${categoryColor}30`); update the 'Next.js'
entry in categoryColors to a darker value (e.g., '#000000' or a neutral gray) so
the generated translucent background and border are visible on light-mode cards,
and run the UI to confirm contrast for both light and dark themes.

Comment on lines +341 to +365
<div className="mt-6 flex justify-center gap-2">
{questions.map((_, index) => (
<button
key={index}
onClick={() => handleNavigate(index)}
className="relative focus:outline-none focus:ring-2 focus:ring-[var(--accent-primary)] focus:ring-offset-2"
aria-label={`Go to question ${index + 1}`}
aria-current={index === currentIndex ? 'true' : 'false'}
>
{index === currentIndex ? (
<div className="relative h-2 w-8">
<div className="absolute inset-0 rounded-full bg-gray-300 dark:bg-gray-700" />
<motion.div
className="absolute inset-0 rounded-full bg-gradient-to-r from-[var(--accent-primary)] to-[var(--accent-hover)]"
initial={{ width: '0%' }}
animate={{ width: `${progress}%` }}
transition={{ duration: 0.05, ease: 'linear' }}
/>
</div>
) : (
<div className="h-2 w-2 rounded-full bg-gray-300 transition-colors hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600" />
)}
</button>
))}
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded English aria-label on navigation dots breaks i18n.

Line 347: aria-label={`Go to question ${index + 1}`} is hardcoded English while the rest of the component uses useTranslations. This is inconsistent with the PR's goal of full i18n support.

Add a translation key (e.g., flipCard.ui.goToQuestion) and use it here.

🌐 Suggested fix

In en.json (and pl.json, uk.json), add under homepage.flipCard.ui:

"goToQuestion": "Go to question {index}"

Then in the component:

-            aria-label={`Go to question ${index + 1}`}
+            aria-label={t('ui.goToQuestion', { index: index + 1 })}
📝 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 className="mt-6 flex justify-center gap-2">
{questions.map((_, index) => (
<button
key={index}
onClick={() => handleNavigate(index)}
className="relative focus:outline-none focus:ring-2 focus:ring-[var(--accent-primary)] focus:ring-offset-2"
aria-label={`Go to question ${index + 1}`}
aria-current={index === currentIndex ? 'true' : 'false'}
>
{index === currentIndex ? (
<div className="relative h-2 w-8">
<div className="absolute inset-0 rounded-full bg-gray-300 dark:bg-gray-700" />
<motion.div
className="absolute inset-0 rounded-full bg-gradient-to-r from-[var(--accent-primary)] to-[var(--accent-hover)]"
initial={{ width: '0%' }}
animate={{ width: `${progress}%` }}
transition={{ duration: 0.05, ease: 'linear' }}
/>
</div>
) : (
<div className="h-2 w-2 rounded-full bg-gray-300 transition-colors hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600" />
)}
</button>
))}
</div>
<div className="mt-6 flex justify-center gap-2">
{questions.map((_, index) => (
<button
key={index}
onClick={() => handleNavigate(index)}
className="relative focus:outline-none focus:ring-2 focus:ring-[var(--accent-primary)] focus:ring-offset-2"
aria-label={t('ui.goToQuestion', { index: index + 1 })}
aria-current={index === currentIndex ? 'true' : 'false'}
>
{index === currentIndex ? (
<div className="relative h-2 w-8">
<div className="absolute inset-0 rounded-full bg-gray-300 dark:bg-gray-700" />
<motion.div
className="absolute inset-0 rounded-full bg-gradient-to-r from-[var(--accent-primary)] to-[var(--accent-hover)]"
initial={{ width: '0%' }}
animate={{ width: `${progress}%` }}
transition={{ duration: 0.05, ease: 'linear' }}
/>
</div>
) : (
<div className="h-2 w-2 rounded-full bg-gray-300 transition-colors hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600" />
)}
</button>
))}
</div>
🤖 Prompt for AI Agents
In `@frontend/components/home/FlipCardQA.tsx` around lines 341 - 365, Replace the
hardcoded English aria-label in FlipCardQA (the button rendering inside the
questions.map) with a localized string from useTranslations: add a key like
homepage.flipCard.ui.goToQuestion with placeholder {index} to your locale files
(en.json, pl.json, uk.json), then call the translation function inside the
component to compose the aria-label (pass index + 1) instead of using the
template literal `Go to question ${index + 1}`; keep the rest of the props (key,
onClick -> handleNavigate, aria-current, etc.) unchanged.

@@ -58,6 +59,61 @@
"6": "Brave move, by the way",
"7": "Okay, now it's happening",
"8": "Hello, new commit"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

ctaVariants.8 looks like a debug placeholder.

"Hello, new commit" reads like a test string rather than a real CTA variant. If this is shipped to production, users will see it. Replace it with a meaningful motivational phrase consistent with the other variants, or remove it.

🤖 Prompt for AI Agents
In `@frontend/messages/en.json` at line 61, The key ctaVariants.8 currently
contains a debug/test string ("Hello, new commit"); replace it with a meaningful
CTA consistent with other variants (e.g., a short motivational phrase) or remove
the key entirely if unused, and ensure any corresponding translation entries or
references that expect ctaVariants length are updated to avoid missing-text
errors; look for the ctaVariants object in en.json and change the value for "8"
accordingly.

@ViktorSvertoka ViktorSvertoka merged commit 1165c15 into develop Feb 13, 2026
11 checks passed
@ViktorSvertoka ViktorSvertoka deleted the feat/ui-improvements branch February 13, 2026 17:17
ViktorSvertoka added a commit that referenced this pull request Feb 14, 2026
* feat(mobile): improve dashboard, leaderboard & AI helper UX for touch devices

  - Add touch drag support for AI helper modal and explained terms reorder
  - Position explain button below selected word on mobile
  - Show delete/restore buttons always visible on mobile (no hover)
  - Add user avatar to dashboard profile card (same as leaderboard)
  - Fix leaderboard page layout
  - Fix Tailwind v4 canonical class warnings

* Added touchcancel listener

* (SP: 2) [Frontend] Quiz results dashboard, review cache fix, UX improvements (#317)

* (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6) (#318)

* (SP: 2) [Frontend] Redesign Home Hero & Add Features Section (#319)

* refactor(home): rename hero sections and add complete i18n support

- Rename LegacyHeroSection → WelcomeHeroSection
- Rename HeroSection → FeaturesHeroSection
- Add welcomeDescription translation key to eliminate duplication
- Translate all hardcoded text (headings, badges, CTAs)
- Improve Ukrainian/Polish translations for better readability
- Remove unused legacy components and images

* feat(about): update LinkedIn follower count to reflect current stat (1.5k+)

* refactor(home): implement i18n for FlipCardQA & fix memory leaks

* fix(home): resolve rotateY conflict & scope keyboard events in FlipCardQA

* fix(home): resolve all issues

* chore(home): cleanup comments, remove dead code & fix trailing spaces

* (SP: 2) [Frontend] Quiz UX improvements: violations counter, breadcrumbs, status badges (#320)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* Header UX polish, quiz highlight fix, Blog button styling, shop i18n product    descriptions (#322)

* Header UX: reorder languages, swap controls, fix quiz highlight, style Blog button

* shop i18n product descriptions

* (SP: 1) [Frontend] Q&A: Next.js tab states + faster loader start (#324)

* fix(qa): align Next.js tab states and speed up loader startup

* feat(home,qa): improve home snap flow and add configurable Q&A page size

* fix(i18n,qa,seed): address review issues for locale handling and pagination state

* (SP: 1) [Frontend] Align quiz result messages with status badges, fix locale switch on result page (#325)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* fix(quiz): align result messages with status badges, persist result on locale switch

* chore(release): v1.0.0

---------

Co-authored-by: tetiana zorii <tanyusha.zoriy@gmail.com>
Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
Co-authored-by: liudmylasovetovs <127711697+liudmylasovetovs@users.noreply.github.com>
Co-authored-by: Yevhenii Datsenko <134847096+yevheniidatsenko@users.noreply.github.com>
Co-authored-by: Tetiana Zorii <131365289+TiZorii@users.noreply.github.com>
@coderabbitai coderabbitai bot mentioned this pull request Feb 14, 2026
ViktorSvertoka added a commit that referenced this pull request Feb 14, 2026
* feat(mobile): improve dashboard, leaderboard & AI helper UX for touch devices

  - Add touch drag support for AI helper modal and explained terms reorder
  - Position explain button below selected word on mobile
  - Show delete/restore buttons always visible on mobile (no hover)
  - Add user avatar to dashboard profile card (same as leaderboard)
  - Fix leaderboard page layout
  - Fix Tailwind v4 canonical class warnings

* Added touchcancel listener

* (SP: 2) [Frontend] Quiz results dashboard, review cache fix, UX improvements (#317)

* (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6) (#318)

* (SP: 2) [Frontend] Redesign Home Hero & Add Features Section (#319)

* refactor(home): rename hero sections and add complete i18n support

- Rename LegacyHeroSection → WelcomeHeroSection
- Rename HeroSection → FeaturesHeroSection
- Add welcomeDescription translation key to eliminate duplication
- Translate all hardcoded text (headings, badges, CTAs)
- Improve Ukrainian/Polish translations for better readability
- Remove unused legacy components and images

* feat(about): update LinkedIn follower count to reflect current stat (1.5k+)

* refactor(home): implement i18n for FlipCardQA & fix memory leaks

* fix(home): resolve rotateY conflict & scope keyboard events in FlipCardQA

* fix(home): resolve all issues

* chore(home): cleanup comments, remove dead code & fix trailing spaces

* (SP: 2) [Frontend] Quiz UX improvements: violations counter, breadcrumbs, status badges (#320)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* Header UX polish, quiz highlight fix, Blog button styling, shop i18n product    descriptions (#322)

* Header UX: reorder languages, swap controls, fix quiz highlight, style Blog button

* shop i18n product descriptions

* (SP: 1) [Frontend] Q&A: Next.js tab states + faster loader start (#324)

* fix(qa): align Next.js tab states and speed up loader startup

* feat(home,qa): improve home snap flow and add configurable Q&A page size

* fix(i18n,qa,seed): address review issues for locale handling and pagination state

* (SP: 1) [Frontend] Align quiz result messages with status badges, fix locale switch on result page (#325)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* fix(quiz): align result messages with status badges, persist result on locale switch

* chore(release): v1.0.0

* feat(jpg): add images for shop

---------

Co-authored-by: tetiana zorii <tanyusha.zoriy@gmail.com>
Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
Co-authored-by: liudmylasovetovs <127711697+liudmylasovetovs@users.noreply.github.com>
Co-authored-by: Yevhenii Datsenko <134847096+yevheniidatsenko@users.noreply.github.com>
Co-authored-by: Tetiana Zorii <131365289+TiZorii@users.noreply.github.com>
ViktorSvertoka added a commit that referenced this pull request Feb 15, 2026
* feat(mobile): improve dashboard, leaderboard & AI helper UX for touch devices

  - Add touch drag support for AI helper modal and explained terms reorder
  - Position explain button below selected word on mobile
  - Show delete/restore buttons always visible on mobile (no hover)
  - Add user avatar to dashboard profile card (same as leaderboard)
  - Fix leaderboard page layout
  - Fix Tailwind v4 canonical class warnings

* Added touchcancel listener

* (SP: 2) [Frontend] Quiz results dashboard, review cache fix, UX improvements (#317)

* (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6) (#318)

* (SP: 2) [Frontend] Redesign Home Hero & Add Features Section (#319)

* refactor(home): rename hero sections and add complete i18n support

- Rename LegacyHeroSection → WelcomeHeroSection
- Rename HeroSection → FeaturesHeroSection
- Add welcomeDescription translation key to eliminate duplication
- Translate all hardcoded text (headings, badges, CTAs)
- Improve Ukrainian/Polish translations for better readability
- Remove unused legacy components and images

* feat(about): update LinkedIn follower count to reflect current stat (1.5k+)

* refactor(home): implement i18n for FlipCardQA & fix memory leaks

* fix(home): resolve rotateY conflict & scope keyboard events in FlipCardQA

* fix(home): resolve all issues

* chore(home): cleanup comments, remove dead code & fix trailing spaces

* (SP: 2) [Frontend] Quiz UX improvements: violations counter, breadcrumbs, status badges (#320)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* Header UX polish, quiz highlight fix, Blog button styling, shop i18n product    descriptions (#322)

* Header UX: reorder languages, swap controls, fix quiz highlight, style Blog button

* shop i18n product descriptions

* (SP: 1) [Frontend] Q&A: Next.js tab states + faster loader start (#324)

* fix(qa): align Next.js tab states and speed up loader startup

* feat(home,qa): improve home snap flow and add configurable Q&A page size

* fix(i18n,qa,seed): address review issues for locale handling and pagination state

* (SP: 1) [Frontend] Align quiz result messages with status badges, fix locale switch on result page (#325)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* fix(quiz): align result messages with status badges, persist result on locale switch

* chore(release): v1.0.0

* feat(jpg): add images for shop

* (SP: 3) [Shop][Monobank] Janitor map + internal janitor endpoint stub + status UX + security/obs + J test gate (#328)

* (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6)

* (SP: 3) [Backend] add provider selector, fix payments gating, i18n checkout errors

* Add shop category images to public

* (SP: 3) [Shop][Monobank] I1 structured logging: codes + logging safety checks

* (SP: 3) [Shop][Monobank] Fail-closed non-browser origin posture for webhook + janitor (ORIGIN_BLOCKED)

* (SP: 3) [Shop][Monobank] [Shop][Monobank] J gate: add orders status ownership test and pass all pre-prod invariants

* (SP: 3) [Shop][Monobank]  review fixes (tests, logging, success UI)

* (SP: 1) [Shop][Monobank] Tighten webhook log-code typing; harden DB tests; minor security/log/UI cleanups

* (SP: 1) [Shop][Monobank] harden Monobank webhook (origin/PII-safe logs) and remove duplicate sha256 hashing

* (SP:2) [Frontend] Fix duplicated Q&A items after content updates (#330)

* fix(qa): prevent duplicate questions and improve cache invalidation

* fix(qa): keep pagination totals consistent after deduplication

* (SP: 1) [Frontend] Integrate online users counter popup and fix header (#331)

* feat(home): add online users counter + fix header breakpoint

* deleted scrollY in OnlineCounterPopup

* fixed fetch in OnlineCounterPopup

* Bug/fix qa (#332)

* fix(qa): prevent duplicate questions and improve cache invalidation

* fix(qa): keep pagination totals consistent after deduplication

* fix(qa): paginate by unique questions and bump cache namespace

* chore(release): v1.0.1

---------

Co-authored-by: tetiana zorii <tanyusha.zoriy@gmail.com>
Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
Co-authored-by: liudmylasovetovs <127711697+liudmylasovetovs@users.noreply.github.com>
Co-authored-by: Yevhenii Datsenko <134847096+yevheniidatsenko@users.noreply.github.com>
Co-authored-by: Tetiana Zorii <131365289+TiZorii@users.noreply.github.com>
Co-authored-by: Yuliia Nazymko <122815071+YNazymko12@users.noreply.github.com>
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.

2 participants