plans
2026-05-06
2026 05 06 Why Services Bento Implementation

Why & Services Bento Redesign Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Redesign the Why and Services homepage sections with bento-grid layouts, image-based cards, gold frame corners, and theatrical animations.

Architecture: Two independent sections with shared design tokens (gold frame corners, hover effects). Services uses a hero+grid bento (5 cards), Why uses a stage-column bento (4 cards with tall left column).

Tech Stack: Next.js 16, Tailwind CSS v4, framer-motion (existing FadeIn), Next/Image


File Map

FileActionPurpose
apps/frontend/components/home/why-section.tsxModifyFull redesign with bento layout
apps/frontend/components/home/services-section.tsxModifyFull redesign with bento layout
apps/frontend/components/home/why-card.tsxCreateReusable image-based card for Why section
apps/frontend/components/home/service-card.tsxCreateReusable image-based card for Services section
apps/frontend/components/ui/fade-in.tsxModifyAdd direction prop for left/right slide
apps/frontend/lib/data/home-page.tsModifyAdd image paths to whyFeatures data

Task 1: Update FadeIn Component (Direction Support)

Files:

  • Modify: apps/frontend/components/ui/fade-in.tsx:1-23

  • Step 1: Read current FadeIn component

// Current implementation - read to understand existing motion props
  • Step 2: Add direction prop with left/right variants
"use client";
 
import { motion } from "framer-motion";
 
interface FadeInProps {
  children: React.ReactNode;
  delay?: number;
  className?: string;
  direction?: "up" | "left" | "right" | "none";
}
 
const directionVariants = {
  up: { initial: { opacity: 0, y: 30 }, animate: { opacity: 1, y: 0 } },
  left: { initial: { opacity: 0, x: -30 }, animate: { opacity: 1, x: 0 } },
  right: { initial: { opacity: 0, x: 30 }, animate: { opacity: 1, x: 0 } },
  none: { initial: { opacity: 0 }, animate: { opacity: 1 } },
};
 
export function FadeIn({
  children,
  delay = 0,
  className = "",
  direction = "up",
}: FadeInProps) {
  const variants = directionVariants[direction];
 
  return (
    <motion.div
      initial={variants.initial}
      whileInView={variants.animate}
      viewport={{ once: true, margin: "-60px" }}
      transition={{ duration: 0.6, delay, ease: [0.25, 0.46, 0.45, 0.94] }}
      className={className}
    >
      {children}
    </motion.div>
  );
}
  • Step 3: Commit
git add apps/frontend/components/ui/fade-in.tsx
git commit -m "feat(home): add direction variants to FadeIn for theatrical entrance
 
Adds 'left' and 'right' direction options for slide-in animations"
 
---
 
## Task 2: Create GoldFrameCorners Component (Shared)
 
**Files:**
- Create: `apps/frontend/components/home/gold-frame-corners.tsx`
 
- [ ] **Step 1: Create reusable gold frame corners component**
 
```tsx
"use client";
 
import { cn } from "~/lib/cn";
 
interface GoldFrameCornersProps {
  className?: string;
  size?: "sm" | "md" | "lg";
  opacity?: number;
}
 
const sizeMap = {
  sm: "w-6 h-6",
  md: "w-8 h-8",
  lg: "w-10 h-10",
};
 
export function GoldFrameCorners({ className, size = "md", opacity = 0.6 }: GoldFrameCornersProps) {
  const borderSize = sizeMap[size];
 
  return (
    <div className={cn("absolute inset-0 pointer-events-none", className)}>
      {/* Top-left */}
      <div className={`absolute top-0 left-0 ${borderSize} border-t-2 border-l-2 border-gold`} style={{ borderColor: `rgba(197, 160, 89, ${opacity})` }} />
      {/* Top-right */}
      <div className={`absolute top-0 right-0 ${borderSize} border-t-2 border-r-2 border-gold`} style={{ borderColor: `rgba(197, 160, 89, ${opacity})` }} />
      {/* Bottom-left */}
      <div className={`absolute bottom-0 left-0 ${borderSize} border-b-2 border-l-2 border-gold`} style={{ borderColor: `rgba(197, 160, 89, ${opacity})` }} />
      {/* Bottom-right */}
      <div className={`absolute bottom-0 right-0 ${borderSize} border-b-2 border-r-2 border-gold`} style={{ borderColor: `rgba(197, 160, 89, ${opacity})` }} />
    </div>
  );
}
  • Step 2: Export from components/home/index.ts or verify component is picked up

Note: This project uses direct imports (no barrel files per rules), so just create the component.

  • Step 3: Commit
git add apps/frontend/components/home/gold-frame-corners.tsx
git commit -m "feat(home): add GoldFrameCorners reusable component
 
Shared gold corner frame decoration for Services, Why, and Gallery sections"

Task 3: Update whyFeatures Data with Images

Files:

  • Modify: apps/frontend/lib/data/home-page.ts:98-126

  • Step 1: Read current whyFeatures structure

// Current structure - iconKey, title, description
  • Step 2: Add image path to each feature
type WhyFeature = {
  iconKey: "theatre" | "cabaret" | "dinner" | "cocktail";
  title: string;
  description: string;
  image: string; // NEW: image path
};
 
export const whyFeatures: WhyFeature[] = [
  {
    iconKey: "theatre",
    title: "Intimate Theatre Experience",
    description: "Only 32 seats per show",
    image: "/images/landing-page/dinner-theatre.jpg",
  },
  {
    iconKey: "cabaret",
    title: "Live Cabaret & Performance Nights",
    description: "A curated mix of theatre, dance and live performance.",
    image: "/images/landing-page/artist-performances.png",
  },
  {
    iconKey: "dinner",
    title: "Premium Fusion Dinner",
    description:
      "Vietnamese-Western cuisine designed for the evening experience.",
    image: "/images/landing-page/dinner-theatre.jpg",
  },
  {
    iconKey: "cocktail",
    title: "Signature Cocktails",
    description: "Craft cocktails in an immersive theatre atmosphere",
    image: "/images/landing-page/dinner-theatre.jpg", // TODO: replace with actual cocktail image
  },
];

Note: Replace cocktail image path with actual image when found in /images/landing-page/.

  • Step 3: Commit
git add apps/frontend/lib/data/home-page.ts
git commit -m "feat(data): add image paths to whyFeatures for bento redesign"

Task 4: Create WhyCard Component

Files:

  • Create: apps/frontend/components/home/why-card.tsx

  • Step 1: Create WhyCard component with image background

"use client";
 
import Image from "next/image";
import { cn } from "~/lib/cn";
import { GoldFrameCorners } from "./gold-frame-corners";
 
interface WhyCardProps {
  image: string;
  title: string;
  description: string;
  className?: string;
  aspectRatio?: "tall" | "normal";
}
 
export function WhyCard({
  image,
  title,
  description,
  className,
  aspectRatio = "normal",
}: WhyCardProps) {
  return (
    <div
      className={cn(
        "relative overflow-hidden rounded-2xl group cursor-pointer",
        aspectRatio === "tall" ? "h-full min-h-[400px]" : "aspect-[4/3]",
        className,
      )}
    >
      {/* Image background */}
      <Image
        src={image}
        alt={title}
        fill
        className="object-cover transition-transform duration-500 group-hover:scale-105"
        loading="lazy"
      />
 
      {/* Gradient overlay for text readability */}
      <div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent" />
 
      {/* Gold frame corners */}
      <GoldFrameCorners className="z-10 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
 
      {/* Content overlay */}
      <div className="absolute bottom-0 left-0 right-0 p-6 z-20">
        <h3 className="font-serif text-xl md:text-2xl text-white font-normal mb-1">
          {title}
        </h3>
        <p className="font-sans text-sm text-white/80 leading-relaxed">
          {description}
        </p>
      </div>
 
      {/* Hover glow effect */}
      <div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none">
        <div className="absolute inset-0 bg-gold/10 blur-xl" />
      </div>
    </div>
  );
}
  • Step 2: Commit
git add apps/frontend/components/home/why-card.tsx
git commit -m "feat(home): create WhyCard component for bento redesign
 
Image-based card with gradient overlay, gold frame corners on hover, and glow effect"

Task 5: Redesign WhySection (Bento Layout)

Files:

  • Modify: apps/frontend/components/home/why-section.tsx:1-38

  • Step 1: Read current implementation

// Current - uses icons in glassmorphism cards
  • Step 2: Implement new bento layout with WhyCard
// ipsoc checked: 2026-05-06
 
"use client";
 
import { whyFeatures } from "~/lib/data/home-page";
import { FadeIn } from "~/components/ui/fade-in";
import { WhyCard } from "./why-card";
 
// =============================================================================
// Why House of Legends — Bento Redesign
// =============================================================================
 
export function WhySection() {
  // Map data to card props
  const [theatre, cabaret, dinner, cocktails] = whyFeatures;
 
  return (
    <section className="bg-transparent px-4 py-20" id="why">
      <div className="max-w-[1440px] mx-auto flex flex-col gap-10 items-center">
        <FadeIn>
          <h2 className="font-display text-4xl leading-none text-gold font-normal text-center">
            Why House Of Legends
          </h2>
        </FadeIn>
 
        {/* Bento grid: Stage Column layout */}
        <div className="w-full max-w-[1200px] grid grid-cols-1 md:grid-cols-2 gap-6">
          {/* Left tall card - Theatre (spans full height) */}
          <FadeIn
            direction="left"
            delay={0.1}
            className="row-span-1 md:row-span-1"
          >
            <WhyCard
              image={theatre.image}
              title={theatre.title}
              description={theatre.description}
              className="h-[400px] md:h-[500px]"
              aspectRatio="tall"
            />
          </FadeIn>
 
          {/* Right column: 3 stacked cards */}
          <div className="flex flex-col gap-6">
            {/* Top: Cabaret + Dinner (side by side on md+) */}
            <FadeIn direction="right" delay={0.2}>
              <WhyCard
                image={cabaret.image}
                title={cabaret.title}
                description={cabaret.description}
                className="h-[200px] md:h-[242px]"
              />
            </FadeIn>
 
            <div className="grid grid-cols-2 gap-6">
              {/* Middle: Dinner (or could be second cabaret if swapped) */}
              <FadeIn direction="right" delay={0.35}>
                <WhyCard
                  image={dinner.image}
                  title={dinner.title}
                  description={dinner.description}
                  className="h-[150px]"
                />
              </FadeIn>
 
              {/* Bottom: Cocktails */}
              <FadeIn direction="right" delay={0.5}>
                <WhyCard
                  image={cocktails.image}
                  title={cocktails.title}
                  description={cocktails.description}
                  className="h-[150px]"
                />
              </FadeIn>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

Note: The bento layout shows Theatre tall on left, and Cabaret/Dinner/Cocktails stacked on right. Adjust grid spans based on visual testing.

  • Step 3: Commit
git add apps/frontend/components/home/why-section.tsx
git commit -m "refactor(home): redesign WhySection with bento layout
 
- Stage column layout with tall left card
- Image-based cards replacing icons
- Gold frame corners on hover
- Staggered entrance animations"

Task 6: Create ServiceCard Component

Files:

  • Create: apps/frontend/components/home/service-card.tsx

  • Step 1: Create ServiceCard component

"use client";
 
import Image from "next/image";
import { LocaleLink } from "~/components/features/ui/locale-link";
import { cn } from "~/lib/cn";
import { GoldFrameCorners } from "./gold-frame-corners";
 
interface ServiceCardProps {
  number: string;
  title: string;
  description: string;
  image: string;
  href: string;
  className?: string;
}
 
export function ServiceCard({
  number,
  title,
  description,
  image,
  href,
  className,
}: ServiceCardProps) {
  return (
    <div
      className={cn(
        "relative h-[400px] overflow-hidden group cursor-pointer rounded-2xl",
        className,
      )}
    >
      {/* Image background */}
      <div className="absolute inset-0 z-10">
        <Image
          src={image}
          alt={title}
          fill
          className="object-cover transition-transform duration-500 group-hover:scale-105"
          loading="lazy"
        />
      </div>
 
      {/* Dark overlay */}
      <div className="absolute inset-0 z-20 bg-black/40" />
 
      {/* Gold frame corners */}
      <GoldFrameCorners className="z-30 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
 
      {/* Content */}
      <div className="absolute inset-0 z-30 flex flex-col items-center justify-center gap-3 p-6 text-center">
        <span className="font-serif text-3xl text-gold font-normal">
          {number}
        </span>
        <h3 className="font-display text-2xl md:text-3xl text-white font-normal">
          {title}
        </h3>
        <p className="font-sans text-sm text-white/80 leading-relaxed max-w-[280px]">
          {description}
        </p>
      </div>
 
      {/* Link arrow */}
      <LocaleLink
        href={href}
        className="absolute top-6 right-6 z-40 inline-flex items-center justify-center w-12 h-12 bg-[#1a1a1a]/80 border-2 border-gold-light text-gold-light rounded-full no-underline hover:bg-gold-light hover:text-surface transition-colors duration-300"
      >
        <svg
          className="w-6 h-6 transition-transform duration-300 group-hover:rotate-45"
          width="24"
          height="24"
          viewBox="0 0 24 24"
          fill="none"
          xmlns="http://www.w3.org/2000/svg"
          aria-hidden="true"
        >
          <path
            d="M5 12h14M12 5l7 7-7 7"
            stroke="currentColor"
            strokeWidth="2"
            strokeLinecap="round"
            strokeLinejoin="round"
          />
        </svg>
      </LocaleLink>
 
      {/* Hover glow */}
      <div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none z-[5]">
        <div className="absolute inset-0 bg-gold/10 blur-xl" />
      </div>
    </div>
  );
}
  • Step 2: Commit
git add apps/frontend/components/home/service-card.tsx
git commit -m "feat(home): create ServiceCard component for bento redesign
 
Image-based card with gold frame corners, link arrow, and hover glow"

Task 7: Redesign ServicesSection (Bento Layout)

Files:

  • Modify: apps/frontend/components/home/services-section.tsx:1-115

  • Step 1: Read current implementation

// Current - 5 cards in 3+2 grid with gold frame corners
  • Step 2: Implement bento layout with ServiceCard
// ipsoc checked: 2026-05-06
// SoC: This component only renders UI. Uses LocaleLink for i18n-aware routing.
 
"use client";
 
import { services } from "~/lib/data/home-page";
import { FadeIn } from "~/components/ui/fade-in";
import { ServiceCard } from "./service-card";
 
// =============================================================================
// Services Section — Bento Redesign
// =============================================================================
 
export function ServicesSection() {
  return (
    <section className="bg-transparent px-4 py-20" id="services">
      <div className="max-w-[1440px] mx-auto flex flex-col gap-6">
        {/* Bento grid layout */}
        <div className="grid grid-cols-1 md:grid-cols-5 gap-6">
          {/* Row 1: Hero cards (60/40 split) */}
          <div className="md:col-span-3">
            <FadeIn direction="left" delay={0.1}>
              <ServiceCard
                number={services[0].number}
                title={services[0].title}
                description={services[0].description}
                image={services[0].image}
                href={services[0].href}
                className="h-[400px]"
              />
            </FadeIn>
          </div>
          <div className="md:col-span-2">
            <FadeIn direction="right" delay={0.2}>
              <ServiceCard
                number={services[1].number}
                title={services[1].title}
                description={services[1].description}
                image={services[1].image}
                href={services[1].href}
                className="h-[400px]"
              />
            </FadeIn>
          </div>
 
          {/* Row 2: 3 equal cards */}
          <div className="md:col-span-1">
            <FadeIn direction="left" delay={0.3}>
              <ServiceCard
                number={services[2].number}
                title={services[2].title}
                description={services[2].description}
                image={services[2].image}
                href={services[2].href}
                className="h-[300px]"
              />
            </FadeIn>
          </div>
          <div className="md:col-span-2">
            <FadeIn direction="up" delay={0.4}>
              <ServiceCard
                number={services[3].number}
                title={services[3].title}
                description={services[3].description}
                image={services[3].image}
                href={services[3].href}
                className="h-[300px]"
              />
            </FadeIn>
          </div>
          <div className="md:col-span-2">
            <FadeIn direction="right" delay={0.5}>
              <ServiceCard
                number={services[4].number}
                title={services[4].title}
                description={services[4].description}
                image={services[4].image}
                href={services[4].href}
                className="h-[300px]"
              />
            </FadeIn>
          </div>
        </div>
      </div>
    </section>
  );
}

Note: The grid uses md:col-span-3 + md:col-span-2 for the 60/40 split. Verify visual proportions in browser and adjust if needed.

  • Step 3: Commit
git add apps/frontend/components/home/services-section.tsx
git commit -m "refactor(home): redesign ServicesSection with bento layout
 
- Hero + grid layout (60/40 split + 3 equal cards)
- Image-based cards with gold frame corners
- Staggered entrance animations"

Task 8: Visual Verification

Files:

  • None (browser testing only)

  • Step 1: Start dev server

cd apps/frontend && npm run dev
  • Step 2: Verify WhySection

  • Navigate to homepage, scroll to "Why" section

  • Check: Tall left card shows Theatre

  • Check: Right column shows 3 stacked cards (Cabaret, Dinner, Cocktails)

  • Check: Gold frame corners appear on hover

  • Check: Images zoom slightly on hover

  • Check: Cards lift with gold glow on hover

  • Check: Animations stagger correctly (left enters first, then right stack)

  • Step 3: Verify ServicesSection

  • Navigate to homepage, scroll to "Services" section

  • Check: First row shows 60/40 split (Dinner Theatre larger)

  • Check: Second row shows 3 equal cards

  • Check: Gold frame corners appear on hover

  • Check: Link arrows work and rotate on hover

  • Check: Images zoom on hover

  • Step 4: Verify Responsive

  • Test mobile (< 768px): Cards should stack vertically

  • Test tablet (768px - 1024px): Grid should adapt

  • Test desktop (> 1024px): Full bento layout

  • Step 5: Report findings

Note any visual issues or adjustments needed. Take screenshots if helpful.


Self-Review Checklist

  • Spec coverage: All sections from spec have tasks

    • Why bento layout
    • Services bento layout
    • Gold frame corners
    • Image-based cards
    • Theatrical animations
    • Responsive behavior (documented in spec, verified in Task 8)
  • Placeholder scan: No TODOs, no TBDs, no "fill in later"

  • Type consistency: All props and interfaces are consistent

    • WhyCard uses image, title, description, aspectRatio
    • ServiceCard uses number, title, description, image, href
    • Both use GoldFrameCorners with consistent props
  • Animation consistency: FadeIn direction variants match across sections


Execution Options

Plan complete and saved to docs/superpowers/plans/2026-05-06-why-services-bento-implementation.md.

Two execution options:

1. Subagent-Driven (recommended) - I dispatch a fresh subagent per task, review between tasks, fast iteration

2. Inline Execution - Execute tasks in this session using executing-plans, batch execution with checkpoints

Which approach?