Retour au catalogue

Portfolio Awards Wall

Portfolio affiche avec badges de recompenses et rubans. Layout style mur de trophees avec compteur de prix et mise en avant des distinctions.

portfoliomedium Both Responsive a11y
elegantboldagencysaasportfoliogrid
Theme
"use client";

import { motion } from "framer-motion";
import { Trophy, Award, Star } from "lucide-react";

interface AwardItem {
  title: string;
  client: string;
  image: string;
  awards: string[];
  year: string;
}

interface PortfolioAwardsWallProps {
  title?: string;
  subtitle?: string;
  items?: AwardItem[];
}

const EASE = [0.16, 1, 0.3, 1] as const;
const ICONS = [Trophy, Award, Star];

export default function PortfolioAwardsWall({
  title = "Projets primes",
  subtitle = "Palmares",
  items = [],
}: PortfolioAwardsWallProps) {
  const totalAwards = items.reduce((acc, item) => acc + item.awards.length, 0);

  return (
    <section style={{ paddingTop: "var(--section-padding-y, 6rem)", paddingBottom: "var(--section-padding-y, 6rem)", background: "var(--color-background)" }}>
      <div style={{ maxWidth: "var(--container-max-width, 72rem)", margin: "0 auto", padding: "0 var(--container-padding-x, 1.5rem)" }}>
        <motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.6, ease: EASE }} style={{ textAlign: "center", marginBottom: "1.5rem" }}>
          <Trophy style={{ width: 32, height: 32, color: "var(--color-accent)", marginBottom: "1rem", display: "inline-block" }} />
          <p style={{ fontSize: "0.8125rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.1em", color: "var(--color-accent)", marginBottom: "0.5rem" }}>{subtitle}</p>
          <h2 style={{ fontFamily: "var(--font-sans)", fontSize: "clamp(2rem, 4vw, 3rem)", fontWeight: 700, letterSpacing: "-0.03em", color: "var(--color-foreground)" }}>{title}</h2>
        </motion.div>

        <motion.div initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} viewport={{ once: true }} transition={{ delay: 0.2 }} style={{ textAlign: "center", marginBottom: "3.5rem" }}>
          <span style={{ fontSize: "3rem", fontWeight: 800, color: "var(--color-accent)", letterSpacing: "-0.03em" }}>{totalAwards}</span>
          <span style={{ fontSize: "1rem", color: "var(--color-foreground-muted)", marginLeft: "0.5rem" }}>distinctions obtenues</span>
        </motion.div>

        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(320px, 1fr))", gap: "1.5rem" }}>
          {items.map((item, i) => {
            const Icon = ICONS[i % ICONS.length];
            return (
              <motion.div
                key={item.title + i}
                initial={{ opacity: 0, y: 24 }}
                whileInView={{ opacity: 1, y: 0 }}
                viewport={{ once: true }}
                transition={{ duration: 0.5, delay: i * 0.1, ease: EASE }}
                style={{ position: "relative", borderRadius: "var(--radius-xl, 1.5rem)", overflow: "hidden", border: "1px solid var(--color-border)", background: "var(--color-background-card)" }}
              >
                {/* Ribbon */}
                <div style={{ position: "absolute", top: "1rem", right: "-2rem", transform: "rotate(45deg)", padding: "0.25rem 2.5rem", background: "var(--color-accent)", zIndex: 5 }}>
                  <span style={{ fontSize: "0.625rem", fontWeight: 700, color: "var(--color-background)", textTransform: "uppercase", letterSpacing: "0.05em" }}>{item.year}</span>
                </div>

                <div style={{ aspectRatio: "16/10", overflow: "hidden" }}>
                  <img src={item.image} alt={item.title} style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
                </div>

                <div style={{ padding: "1.5rem" }}>
                  <span style={{ fontSize: "0.6875rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--color-foreground-muted)" }}>{item.client}</span>
                  <h3 style={{ fontSize: "1.125rem", fontWeight: 700, color: "var(--color-foreground)", marginTop: "0.25rem" }}>{item.title}</h3>

                  <div style={{ display: "flex", flexWrap: "wrap", gap: "0.5rem", marginTop: "1rem" }}>
                    {item.awards.map((award) => (
                      <span key={award} style={{ display: "inline-flex", alignItems: "center", gap: "0.25rem", fontSize: "0.6875rem", fontWeight: 500, padding: "0.25rem 0.625rem", borderRadius: "var(--radius-full, 9999px)", background: "color-mix(in srgb, var(--color-accent) 10%, var(--color-background))", color: "var(--color-accent)", border: "1px solid color-mix(in srgb, var(--color-accent) 20%, transparent)" }}>
                        <Icon style={{ width: 12, height: 12 }} /> {award}
                      </span>
                    ))}
                  </div>
                </div>
              </motion.div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

Avis

Portfolio Awards Wall — React Portfolio Section — Incubator