HomeProjectsContactGitHub

Portfolio Leonie Bosshard

Repository

This is a professional portfolio created with Next.js portraying my client, Leonie Bosshard, and her professional work in photography, graphic design, and more.

Dynamic Project Card Generation

With this strongly typed export of projects, the frontend dynamically generates project cards by utilizing some of Next.js's core features.

1const suffix: string = "/projects/";
2const graphicDesign: string = `${suffix}graphic-design/#`;
3const photography: string = `${suffix}photography/#`;
4const uiUxInterface: string = `${suffix}ui-ux-interface/#`;
5const analogProjects: string = `${suffix}analog-projects/#`;
6
7export const projects: ProjectCardProps[] = [
8  {
9    coverImage: projectImages.graphicDesign[0].src,
10    name: "Poster Design",
11    category: "Grafik Design",
12    url: `${graphicDesign}posters`,
13  },
14  {
15    coverImage: projectImages.mora[0].src,
16    name: "Brand Concept: mora",
17    category: "Grafik Design",
18    url: `${graphicDesign}brand-concept`,
19  },
20  {
21    coverImage: projectImages.redentApp[0].src,
22    name: "Re:Dent App",
23    category: "UX / UI Interface",
24    url: `${uiUxInterface}redent-app`,
25  },
26  {
27    coverImage: projectImages.kaosKomplett[0].src,
28    name: "Kaos Komplett",
29    category: "Analoge Projekte",
30    url: `${analogProjects}kaos-komplett`,
31  },
32];

Custom Speedometer Icon

This component generates a speedometer icon with a specified tool in the center and a skill indicator on a 0-100 scale that dynamically changes based on the value.

1const SkillIndicator: React.FC<SkillIndicatorComponentProps> = ({
2  icon,
3  skill,
4  className,
5}) => {
6  const radius = 40;
7  const circumference = 2 * Math.PI * radius;
8  const offset = circumference - (skill / 100) * circumference;
9
10  const renderIcon = () => {
11    if (icon.startsWith("Adobe")) {
12      const toolName = icon.replace("Adobe ", "").replace(" ", "") as AdobeTool;
13      return <AdobeIcon tool={toolName} size={32} />;
14    }
15
16    if (icon === "Figma") {
17      return <Figma className="w-8 h-8 text-primary-text" />;
18    }
19
20    return <span className="text-white text-[0.625rem] uppercase">Icon</span>;
21  };
22
23  return (
24    <div className={cn("flex flex-col items-center", className)}>
25      <div className="relative w-24 h-24">
26        <svg className="w-full h-full" viewBox="0 0 100 100">
27          <circle
28            className="stroke-neutral-800"
29            strokeWidth="8"
30            fill="transparent"
31            r={radius}
32            cx="50"
33            cy="50"
34          />
35          <circle
36            className="stroke-primary-accent"
37            strokeWidth="8"
38            strokeDasharray={circumference}
39            strokeDashoffset={offset}
40            strokeLinecap="round"
41            fill="transparent"
42            r={radius}
43            cx="50"
44            cy="50"
45            transform="rotate(-90 50 50)"
46          />
47        </svg>
48
49        <div className="absolute inset-0 flex items-center justify-center">
50          {renderIcon()}
51        </div>
52      </div>
53
54      <span className="mt-2 text-primary-accent text-lg font-bold">
55        {skill}%
56      </span>
57
58      <span className="text-primary-text font-bold text-md capitalize">
59        {icon}
60      </span>
61    </div>
62  );
63};
64
65export default SkillIndicator;

Modifyable Headshot component

This component returns a headshot image with dynamic styling passed via parameters. The styling follows the modern standard of merging styles defined inside the component with those passed in as parameters by utilizing a cn utility function.

1import Image from "next/image";
2import { cn } from "@/lib/utils/cn";
3import { HeadshotProps } from "@/types/headshot";
4
5const Headshot = ({
6  topLeft,
7  topRight,
8  bottomLeft,
9  bottomRight,
10  className,
11}: HeadshotProps) => {
12  return (
13    <div
14      className={cn(
15        "relative overflow-hidden",
16        topLeft ? "rounded-tl-full" : "rounded-tl-xl",
17        topRight ? "rounded-tr-full" : "rounded-tr-xl",
18        bottomLeft ? "rounded-bl-full" : "rounded-bl-xl",
19        bottomRight ? "rounded-br-full" : "rounded-br-xl",
20        className,
21      )}
22    >
23      <Image
24        src="/images/headshot.avif"
25        alt="Headshot"
26        fill
27        priority
28        className="object-cover transition-all duration-300 bg-neutral-800"
29      />
30    </div>
31  );
32};
33
34export default Headshot;

Live Demonstration

Live Demo

You can view her portfolio by clicking the button and potentially hire her for your next photography needs.