Skip to main content

Overview

The CaseStudyCard is a reusable component for displaying individual project case studies. It features a structured layout with an icon, title, challenge description, solution description, and technology badges.

Source Location

/src/components/CaseStudyCard.astro

Features

  • Challenge/Solution structured format
  • Custom SVG icons for different project types
  • Technology stack badges
  • Gradient border hover effects
  • Glass morphism background
  • Lift animation on hover
  • Dark mode support

Props

title
string
required
The case study title/heading
challenge
string
required
Description of the problem or challenge faced
solution
string
required
Description of how the challenge was solved
tech
string[]
required
Array of technology names used in the project
icon
string
required
Icon identifier: ‘shield’, ‘search’, ‘migrate’, ‘cost’, or ‘document’
challengeLabel
string
required
Label text for the challenge section (for i18n)
solutionLabel
string
required
Label text for the solution section (for i18n)
stackLabel
string
required
Label text for the tech stack section (for i18n)

Code Example

import CaseStudyCard from '../components/CaseStudyCard.astro';

<CaseStudyCard
  title="Security Permissions Optimization"
  challenge="Complex RBAC structure needed simplification and audit"
  solution="Implemented PowerShell automation for role assignment analysis"
  tech={['Azure RBAC', 'PowerShell', 'Azure AD']}
  icon="shield"
  challengeLabel="Challenge"
  solutionLabel="Solution"
  stackLabel="Tech Stack"
/>

Available Icons

The component includes 5 pre-defined SVG icons:

shield

Security and permissions projects
{
  viewBox: '0 0 24 24',
  path: 'M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z',
}
Discovery and governance projects
{
  viewBox: '0 0 24 24',
  path: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z',
}

migrate

Migration and transfer projects
{
  viewBox: '0 0 24 24',
  path: 'M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4',
}

cost

Cost optimization and FinOps projects
{
  viewBox: '0 0 24 24',
  path: 'M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
}

document

Documentation and writing projects
{
  viewBox: '0 0 24 24',
  path: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z',
}

Component Structure

<div class="group relative p-[1px] rounded-2xl bg-gradient-to-br ...">
  <div class="bg-white/90 dark:bg-slate-800/90 backdrop-blur-sm rounded-2xl p-6 ...">
    
    <!-- Header with icon and title -->
    <div class="flex items-start gap-3 mb-5">
      <div class="w-11 h-11 rounded-xl bg-gradient-to-br from-primary-500 to-accent-500">
        <svg><!-- Icon --></svg>
      </div>
      <h3>{title}</h3>
    </div>

    <!-- Challenge section -->
    <div class="mb-4">
      <div class="flex items-center gap-2 mb-1.5">
        <span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span>
        <span class="text-xs font-semibold text-amber-600 uppercase">{challengeLabel}</span>
      </div>
      <p class="pl-3.5 border-l-2 border-amber-400/40">{challenge}</p>
    </div>

    <!-- Solution section -->
    <div class="mb-5 flex-grow">
      <div class="flex items-center gap-2 mb-1.5">
        <span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
        <span class="text-xs font-semibold text-emerald-600 uppercase">{solutionLabel}</span>
      </div>
      <p class="pl-3.5 border-l-2 border-emerald-400/40">{solution}</p>
    </div>

    <!-- Tech badges -->
    <div>
      <span class="text-xs font-semibold uppercase">{stackLabel}</span>
      <div class="flex flex-wrap gap-1.5 mt-2">
        {tech.map((t) => (
          <span class="px-2.5 py-1 text-xs rounded-lg bg-primary-50 ...">{t}</span>
        ))}
      </div>
    </div>

  </div>
</div>

Styling Details

Gradient Border

<div class="group relative p-[1px] rounded-2xl bg-gradient-to-br from-slate-200 to-slate-300 dark:from-slate-700 dark:to-slate-800 hover:from-primary-400 hover:to-accent-400">
  • 1px border created with padding
  • Neutral gradient by default
  • Primary/accent gradient on hover

Glass Morphism Background

<div class="bg-white/90 dark:bg-slate-800/90 backdrop-blur-sm rounded-2xl">
  • Semi-transparent background (90% opacity)
  • Backdrop blur for depth
  • Dark mode variant

Hover Effects

hover:-translate-y-2 transition-[transform,box-shadow] duration-300 shadow-lg hover:shadow-2xl
  • Lifts 8px on hover
  • Shadow intensifies
  • 300ms smooth transition

Section Indicators

Challenge (Amber):
<span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span>
<p class="border-l-2 border-amber-400/40">...</p>
Solution (Emerald):
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
<p class="border-l-2 border-emerald-400/40">...</p>

Technology Badges

<span class="px-2.5 py-1 text-xs font-medium rounded-lg bg-primary-50 dark:bg-primary-900/20 text-primary-700 dark:text-primary-300 border border-primary-100 dark:border-primary-800/30">
  {t}
</span>

Accessibility

  • Semantic HTML with proper heading levels
  • Color is not the only indicator (uses labels + icons)
  • Sufficient color contrast for all text
  • Keyboard navigation support (if made interactive)

Customization

Adding Custom Icons

Extend the icons object:
const icons: Record<string, { viewBox: string; path: string }> = {
  // existing icons...
  cloud: {
    viewBox: '0 0 24 24',
    path: 'M3 15a4 4 0 004 4h10a5 5 0 001.5-9.7A6 6 0 007.5 6.7 4 4 0 003 15z',
  },
};

Changing Section Colors

Modify the indicator colors:
<!-- Challenge: Amber -> Red -->
<span class="bg-red-500"></span>
<p class="border-red-400/40">...</p>

<!-- Solution: Emerald -> Blue -->
<span class="bg-blue-500"></span>
<p class="border-blue-400/40">...</p>

Adjusting Card Height

The card uses flex-grow on the solution section to maintain equal heights in grid:
<div class="mb-5 flex-grow"><!-- Solution --></div>

Integration Example

Used in the Projects component:
{caseStudies.map((cs, i) => (
  <CaseStudyCard
    title={t(cs.titleKey)}
    challenge={t(cs.challengeKey)}
    solution={t(cs.solutionKey)}
    tech={cs.tech}
    icon={cs.icon}
    challengeLabel={t('casestudies.challengeLabel')}
    solutionLabel={t('casestudies.solutionLabel')}
    stackLabel={t('casestudies.stackLabel')}
  />
))}
  • Projects - Parent component that renders multiple cards
  • Skills - Similar card-based layout pattern

Build docs developers (and LLMs) love