Skip to content

Comments

feat: Complete visual redesign with new design system#21

Closed
Zheckan wants to merge 1 commit intomainfrom
redesign/visual-overhaul
Closed

feat: Complete visual redesign with new design system#21
Zheckan wants to merge 1 commit intomainfrom
redesign/visual-overhaul

Conversation

@Zheckan
Copy link
Owner

@Zheckan Zheckan commented Feb 8, 2026

Summary

  • New design system: Deep navy (#0b0f1a) + emerald/cyan accent gradient replacing the old dark/yellow theme
  • Redesigned all pages: Homepage with hero section, staggered animations, feature cards; Junior Frontend topic selector with numbered cards and icons; Content pages with glass morphism section cards
  • Fixed width mode selector bug: The "narrow/comfortable/wide/full" buttons now correctly show the saved preference on page load by syncing state from localStorage after hydration
  • Redesigned Table of Contents: Active section highlighting via IntersectionObserver, cleaner hierarchy with indented levels, "Contents" header with pin button, mobile FAB trigger button, thin styled scrollbar
  • New ambient background: CSS gradient mesh with animated orbs (respects prefers-reduced-motion) replacing the external image dependency
  • Updated all typography components: Gradient h3 headers, uppercase h4 subheaders, emerald-accented callouts and code spans, properly themed text colors
  • Animated width switcher: Spring-animated pill indicator that smoothly slides between options

Test plan

  • Verify homepage loads with hero, topic card, and feature grid at /
  • Verify junior frontend page loads with 5 topic cards at /frontend/junior
  • Verify content page loads with styled sections at /frontend/junior/html&css
  • Check width switcher shows correct selection on page load (set "wide", reload, verify "wide" is highlighted)
  • Open TOC by hovering left edge on desktop, verify active section highlighting while scrolling
  • On mobile (390px), verify TOC FAB button opens the panel
  • Check responsive layouts at 390px, 768px, and 1280px widths
  • Run bun run build — passes with 0 errors
  • Run bun jest — all 7 tests pass

🤖 Generated with Claude Code

Replace the dark/yellow theme with a deep navy + emerald/cyan design system.
Redesign all pages (homepage, topic selector, content pages) with glass
morphism cards, gradient accents, Framer Motion animations, and responsive
layouts. Fix width mode selector hydration bug so it correctly shows the
saved preference on page load. Redesign table of contents with active
section tracking via IntersectionObserver, cleaner hierarchy, mobile FAB
trigger, and pin/unpin UI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 8, 2026 20:51
@vercel
Copy link
Contributor

vercel bot commented Feb 8, 2026

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

Project Deployment Actions Updated (UTC)
prep Ready Ready Preview, Comment Feb 8, 2026 8:51pm

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR delivers a full UI refresh by introducing a new deep-navy + emerald/cyan design system, updating shared UI components to use theme variables, and improving navigation/UX elements (TOC, width switcher, ambient background) across the app.

Changes:

  • Introduces new global design tokens/utilities (CSS variables, glass/card styles, accent gradient text, thin scrollbar styling).
  • Redesigns key UI surfaces (Home, Junior Frontend selector, content sections/typography) with updated layout and motion.
  • Updates Table of Contents behavior and adds animated width switcher indicator + hydration sync for width preference.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/components/width-switcher/width-switcher.tsx Adds Framer Motion animated active indicator and refactors preset list/styles.
src/components/typography/text.tsx Moves text colors to CSS variables and adjusts paragraph rhythm.
src/components/typography/subheader.tsx Updates subheader sizing/weight/tracking to new design tokens.
src/components/typography/header.tsx Switches header styling to gradient text utility.
src/components/typography/code-span.tsx Rethemes inline code to use accent variables.
src/components/typography/callout.tsx Rethemes callout to new accent/border/card styles.
src/components/table-of-contents/table-of-contents.tsx Reworks TOC UI, adds active-section tracking, mobile FAB trigger, and thin scrollbar.
src/components/section-card/section-card.tsx Converts section cards to new glass-card style with accent line.
src/components/page-header/page-header.tsx Updates header styling and adds gradient accent line.
src/components/notes-area/notes-area.tsx Rethemes notes area to new dashed border/card background variables.
src/components/layout/page-container.tsx Syncs width state from localStorage/data attribute after hydration.
src/components/layout/content-page.tsx Updates page text color to use foreground variable.
src/components/code-block/code-block.tsx Rethemes code block chrome and highlight colors to new accents.
src/components/ambient-background/ambient-background.tsx Replaces external image with CSS gradient mesh + animated orbs respecting reduced motion.
src/app/globals.css Adds new design system variables/utilities and updates global styles (glass/card/scrollbar).
src/app/frontend/junior/frontend-junior.tsx Full redesign of junior frontend topic selector with motion and icon cards.
src/app/(main)/home.tsx Full redesign of homepage with hero, topic cards, and features grid + motion.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +28 to +35
useEffect(() => {
// First check localStorage (most reliable client-side)
const stored = localStorage.getItem(storageKey);
if (stored && validPresets.has(stored)) {
setWidth(stored as WidthPreset);
document.documentElement.dataset.contentWidth = stored;
return;
}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

This effect reads localStorage without a try/catch. In some browsers/environments (e.g., restricted storage, privacy mode), localStorage.getItem can throw and break rendering. Wrap the read in the same try/catch pattern used in applyWidthPreference, and fall back to the data attribute / initialWidth on failure.

Copilot uses AI. Check for mistakes.
Comment on lines +146 to +151
.glass-card:hover {
border-color: var(--border-strong);
background: rgba(15, 23, 42, 0.65);
box-shadow:
0 8px 32px rgba(6, 182, 212, 0.08),
0 0 0 1px var(--border-strong);
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

.glass-card:hover hard-codes a dark background (rgba(15, 23, 42, 0.65)), which will apply even in the prefers-color-scheme: light mode where --foreground is dark. This can significantly reduce contrast/readability on hover in light mode. Use a theme-aware variable (e.g., introduce --card-bg-hover) or derive the hover color from existing CSS vars so both color schemes remain consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +16
import { motion } from 'framer-motion';
import { useEffect, useState } from 'react';

export function AmbientBackground() {
const [prefersReduced, setPrefersReduced] = useState(false);

useEffect(() => {
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
setPrefersReduced(mq.matches);
const handler = (e: MediaQueryListEvent) => setPrefersReduced(e.matches);
mq.addEventListener('change', handler);
return () => mq.removeEventListener('change', handler);
}, []);

Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

prefersReduced initializes to false, so users with prefers-reduced-motion: reduce may still see animations briefly until the effect runs. Since you’re already using Framer Motion, consider useReducedMotion() (or initialize state from matchMedia when available) and conditionally disable animations from the first render to fully respect reduced-motion.

Suggested change
import { motion } from 'framer-motion';
import { useEffect, useState } from 'react';
export function AmbientBackground() {
const [prefersReduced, setPrefersReduced] = useState(false);
useEffect(() => {
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
setPrefersReduced(mq.matches);
const handler = (e: MediaQueryListEvent) => setPrefersReduced(e.matches);
mq.addEventListener('change', handler);
return () => mq.removeEventListener('change', handler);
}, []);
import { motion, useReducedMotion } from 'framer-motion';
export function AmbientBackground() {
const prefersReduced = useReducedMotion();

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +87
for (const entry of entries) {
if (entry.isIntersecting) {
setActiveId(entry.target.id);
}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

IntersectionObserver callback sets activeId for every intersecting entry; when multiple headings intersect, the last entry processed wins (entry order is not guaranteed), which can cause flickering/incorrect active section highlighting. Consider selecting a single “best” entry (e.g., the closest to the top / highest intersectionRatio / smallest positive boundingClientRect.top) before calling setActiveId once per callback.

Suggested change
for (const entry of entries) {
if (entry.isIntersecting) {
setActiveId(entry.target.id);
}
// Select a single "best" intersecting heading to avoid
// order-dependent updates and flickering.
const intersecting = entries.filter((entry) => entry.isIntersecting);
if (intersecting.length === 0) {
return;
}
const bestEntry = intersecting.reduce((best, entry) => {
const bestTop = best.boundingClientRect.top;
const currentTop = entry.boundingClientRect.top;
// Prefer the smallest non-negative top (closest to top in view).
const bestNonNegative = bestTop >= 0;
const currentNonNegative = currentTop >= 0;
if (bestNonNegative && currentNonNegative) {
return currentTop < bestTop ? entry : best;
}
if (bestNonNegative && !currentNonNegative) {
return best;
}
if (!bestNonNegative && currentNonNegative) {
return entry;
}
// Both are negative: pick the one closest to the top (largest top).
return currentTop > bestTop ? entry : best;
}, intersecting[0]);
if (bestEntry && bestEntry.target && bestEntry.target.id) {
setActiveId(bestEntry.target.id);

Copilot uses AI. Check for mistakes.
Comment on lines 181 to 184
const target = event.target as Element;
const tocNav = document.querySelector('nav[style*="top:"]');
const tocContent = tocNav?.querySelector('.scrollbar-hide');

const tocContent = tocNav?.querySelector('.scrollbar-thin');
if (tocContent && !tocContent.contains(target)) {
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The click-outside handler relies on document.querySelector('nav[style*="top:"]') and then queries for .scrollbar-thin. This is brittle (can match the wrong <nav> or break if styling changes) and may cause the TOC to not close on mobile. Prefer attaching a ref to the TOC panel/container and using ref.current.contains(event.target) for the outside-click check.

Copilot uses AI. Check for mistakes.
@Zheckan Zheckan closed this Feb 9, 2026
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