Skip to main content
All components live in components/. Server Components have no directive at the top; Client Components begin with "use client".
Components that only read from static data files or the filesystem render as React Server Components by default, producing zero client-side JavaScript. Components that use browser APIs, React state, or useEffect are opted into the client runtime with "use client".

Hero

File: components/Hero.tsx · Server Component Renders the full-viewport landing section. Displays a profile photo inside a decorative Japanese frame image, the developer’s name and title in both Spanish and Japanese (ウェブ開発者), quick-info badges (location, experience, email), a short biography, and two CTA buttons that anchor-scroll to #projects and #contact. Also renders a bouncing scroll indicator arrow at the bottom of the viewport and two decorative red circles (red-circle) positioned absolutely. Props: none — all content is hardcoded in the component. Notable details:
  • Uses next/image with priority on both the frame and profile images so they are preloaded.
  • The pattern-bg CSS class applies the asanoha tile pattern at 3% opacity as an absolute overlay.
  • The Japanese motto 美は、シンプルさの中に。 (“Beauty lies in simplicity”) is rendered with the .text-japanese class (Noto Serif JP).

Works

File: components/Works.tsx · Server Component Renders a responsive card grid of all portfolio projects imported from data/projects.ts. Props: none — data is imported directly. Each card shows:
  • A numbered badge (01, 02, …) in the top-left corner
  • A hover-zoom project screenshot (next/image)
  • GitHub and Demo links that fade in on hover
  • The project title, Japanese subtitle, description, and technology tags
Data shape consumed (Project type):
interface Project {
  title: string;         // Display name
  japanese: string;      // Japanese subtitle shown below title
  description: string;   // Short description paragraph
  image: StaticImageData; // Imported AVIF via next/image static import
  tags: string[];        // Technology labels
  github: string;        // GitHub URL (or "#" if private)
  demo: string;          // Live demo URL
}
Notable details:
  • Images are statically imported at the top of data/projects.ts, so Next.js generates correct width/height metadata automatically.
  • The uroko scale pattern (pattern-bg-uroko) is used as the section background texture.
  • The grid is md:grid-cols-2 lg:grid-cols-3.

Skills

File: components/Skills.tsx · Server Component Renders a grid of skill category cards, each containing a Lucide icon, a title, a Japanese subtitle, and a bullet list of specific technologies. Props: none — data is imported from data/skills.ts. Data shape consumed (Skill type):
interface Skill {
  icon: LucideIcon;   // Lucide React icon component
  title: string;      // English category name (e.g. "Frontend")
  japanese: string;   // Japanese label (e.g. "フロントエンド")
  techs: string[];    // List of specific technologies
}
Notable details:
  • The icon field is a Lucide icon component reference (not JSX). It is instantiated inside the .map() via const Icon = skill.icon; <Icon ... />.
  • Each card receives a staggered animationDelay via inline style (index * 100ms).
  • The wave pattern (pattern-bg-waves) is the section texture.
  • The grid is sm:grid-cols-2 lg:grid-cols-3.

Experience

File: components/Experience.tsx · Server Component Renders the professional background section with a two-column layout: Education on the left, Work Experience on the right. Both columns are powered by the TimelineSection component. Props: none — data is imported from data/experience.ts. Rendering structure:
<TimelineSection
  title="Educación"
  japanese="学歴"
  icon={<GraduationCap />}
  items={educations}
  variant="primary"
/>
<TimelineSection
  title="Experiencia Laboral"
  japanese="職歴"
  icon={<Briefcase />}
  items={experiences}
  variant="accent"
/>
Notable details:
  • A decorative vertical line and two animated dots are rendered absolutely on large screens to visually connect the two columns.
  • The kumo cloud pattern (pattern-bg-kumo) is the section texture.
  • Column layout switches from single to lg:grid-cols-2 at the lg breakpoint.

TimelineSection

File: components/TimelineSection.tsx · Server Component A reusable vertical timeline used by Experience. Renders a section header (icon + title + Japanese subtitle) and a list of timeline entry cards. Props:
interface TimelineSectionProps {
  title: string;                  // Section heading (e.g. "Educación")
  japanese: string;               // Japanese subtitle (e.g. "学歴")
  icon: ReactNode;                // Icon element rendered in the header badge
  items: TimelineItem[];          // Array of timeline entries
  variant: "primary" | "accent";  // Color theme — drives border and text colors
}
Each TimelineItem card renders:
  • Entry title and optional Japanese label
  • Date badge (item.years) with a Calendar icon
  • Subtitle (item.sub_title), e.g. institution or company name
  • Detail paragraph (item.details)
  • A ghost index number in the bottom-right corner
variant color mapping:
variantBorder colorText color
"primary"border-primarytext-primary
"accent"border-accenttext-accent
Notable details:
  • The left border of each card (border-l-4) uses the variant color, creating the visual timeline track.
  • The key for each item is ${item.title}-${item.years} to handle entries with the same title at different times.

File: components/Navigation.tsx · Server Component The fixed top navigation bar. Renders a logo area (profile image + Japanese motto) and the right-side controls. Props: none. Rendering structure:
<nav className="fixed top-0 ...">
  <Link href="/#hero">
    <ProfileImage size="small" />
    <span>美は、シンプルさの中に</span>  {/* hover reveals Spanish translation */}
  </Link>
  <NavLinks />
  <AnimatedThemeToggler />
</nav>
Notable details:
  • Navigation itself is a Server Component. The interactive pieces (NavLinks, AnimatedThemeToggler) are Client Components imported inside it — Next.js handles the boundary automatically.
  • The logo text swaps between the Japanese motto and its Spanish translation on hover using a CSS opacity/translate transition (no JavaScript).

File: components/NavLinks.tsx · "use client" Renders the navigation link list for both desktop and mobile. Tracks the currently visible section and highlights the matching link with an accent-colored SVG diamond indicator. Props: none. Navigation items (hardcoded):
hreflabel
#heroHOME
#projectsPROJECTS
#skillsSKILLS
#experienceEXPERIENCE
#contactCONTACT
Notable details:
  • Uses an IntersectionObserver-style scroll listener (getBoundingClientRect) to track which section is at top <= 150px.
  • Smooth-scrolls via element.scrollIntoView({ behavior: "smooth" }) and updates the URL hash with history.pushState on click.
  • On mobile, renders a hamburger toggle button that expands a vertical menu anchored to the bottom of the nav bar.
  • The active indicator is an inline SVG diamond shape rendered from currentColor (uses text-accent).

AIDrawer

File: components/AIDrawer.tsx · "use client" A slide-in chat drawer that lets visitors ask questions about the portfolio owner. Powered by the Vercel AI SDK’s useChat hook, which streams responses from /api/chat. Props:
{
  initialOpen?: boolean; // Default: false. Pass true to open the drawer immediately on mount.
}
Sub-components (private, not exported):
NameResponsibility
FloatingButtonFixed bottom-right button that opens the drawer
ChatHeaderDrawer title bar with bot icon and close button
MessageItemRenders a single chat bubble (user or assistant)
ChatInputTextarea + send/stop button at the bottom of the drawer
ErrorMessageRed alert bar shown when useChat returns an error
OverlaySemi-transparent backdrop; clicking it closes the drawer
Notable implementation details:
  • Uses TextStreamChatTransport from the AI SDK (v5 API) to connect to /api/chat.
  • The initial welcome message is injected as a pre-seeded UIMessage array (INITIAL_MESSAGES) — it is never sent to the server.
  • formatMessageContent() from lib/chat-utils.tsx post-processes assistant text to turn URLs into clickable links and **text** into <strong> elements.
  • Enter sends the message; Shift+Enter inserts a newline.
  • AbortController cancellation is exposed via the stop function from useChat, wired to a stop button that appears while streaming.
  • Focus is set on the textarea 300 ms after the drawer opens.

AIDrawerLazy

File: components/AIDrawerLazy.tsx · "use client" A thin lazy-loading wrapper around AIDrawer. Used in app/layout.tsx instead of AIDrawer directly to defer loading the chat bundle until the user first interacts with the floating button. Props: none. Behavior:
  • Before the user clicks: renders a placeholder floating button (identical appearance to AIDrawer’s own FloatingButton).
  • On mouseenter/focus: pre-fetches the AIDrawer chunk via a dynamic import() call (warm-up without mounting).
  • On click: sets loaded = true, which mounts <AIDrawer initialOpen /> (with the drawer already open).
  • AIDrawer is loaded with dynamic(..., { ssr: false }) to prevent server-side rendering of the chat UI.

File: components/Footer.tsx · Server Component A simple page footer with a copyright line, three social icon links, and a decorative red gradient rule. Props: none. The social links are defined in a private socialLinks array local to this file (separate from the data/socials.ts file used by the AI context). Each entry has href, alt, and src fields pointing to PNG icons under public/images/footer/.
The Footer component defines its own local social link data independently of data/socials.ts. The data/socials.ts file is used exclusively for the AI assistant context, while Footer manages its own icon assets in public/images/footer/.

ThemeProvider

File: components/ThemeProvider.tsx · "use client" A thin wrapper that re-exports next-themesThemeProvider as a named Client Component export. This pattern is required because next-themes uses useContext internally and cannot be used directly in a Server Component. Props: passes all props through to NextThemesProvider. Used in app/layout.tsx:
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
  ...
</ThemeProvider>
With attribute="class", the theme is applied by toggling the dark class on <html>, which activates the .dark CSS variable block in globals.css.

ThemeToggle

File: components/ThemeToggle.tsx · "use client" A button that toggles between dark and light mode using the useTheme hook from next-themes. Props: none. Notable details:
  • Renders a disabled placeholder on first render (before mount) to avoid a hydration mismatch — resolvedTheme is only available client-side.
  • Shows a Sun icon in dark mode and a Moon icon in light mode.
The actual toggle button in the navigation bar is rendered by AnimatedThemeToggler from components/ui/animated-theme-toggler.tsx, not ThemeToggle directly. ThemeToggle is available as a simpler alternative.

Shared/ProfileImage

File: components/Shared/ProfileImage.tsx · Server Component A reusable circular profile photo component used in Navigation (small variant) and available for use elsewhere. Props:
type Props = {
  size?: "small" | "medium" | "large"; // Default: "medium"
  className?: string;                   // Default: ""
  borderColor?: string;                 // Default: "#B94A48" (Japanese red)
};
Size mapping:
sizeContainer classsizes hint
"small"w-12 h-12 border-248px
"medium"w-32 h-32 border-4128px
"large"w-48 h-48 border-4192px
Notable details:
  • Image source is hardcoded to /images/yo.avif.
  • The border color defaults to the project’s primary red (#B94A48) applied as an inline style, so it works independently of Tailwind’s color utilities.

Project Structure Overview

Directory tree, App Router setup, and the chat API route.

Data Layer

TypeScript interfaces and data file reference.

Build docs developers (and LLMs) love