Skip to content

Commit 48edab6

Browse files
committed
feat: add speakers for 2025
1 parent d0a0546 commit 48edab6

98 files changed

Lines changed: 1302 additions & 60 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pwa/app/(con)/[locale]/con/[edition]/conferences/[slug]/components/ConferenceSpeaker.tsx

Lines changed: 56 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Link from "components/common/Link";
77
import SpeakerImage from "components/con/speakers/SpeakerImage";
88
import Button from "components/con/common/Button";
99
import { LanguageContext } from "contexts/con/LanguageContext";
10+
import SpeakerImage2025 from "components/con/speakers/SpeakerImage2025";
1011

1112
interface SpeakerProps {
1213
conference: Conference;
@@ -21,69 +22,71 @@ const ConferenceSpeaker = ({ conference }: SpeakerProps) => {
2122
{speakers.map((speaker: Speaker) => {
2223
const { name, job, company, image, placeholder, url } = speaker;
2324
return (
24-
<div
25-
key={speaker.name}
26-
className={classNames(
27-
"bg-blue-gradient bg-blue text-center px-5 py-5 border-b-2 border-dotted border-b-white/50 last:border-b-0 group hover:bg-blue-dark transition-colors",
28-
speakers.length === 1 && "md:hover:bg-blue md:pt-10 md:pb-5"
29-
)}
30-
>
31-
<Link
32-
href={url}
25+
<>
26+
<div
27+
key={speaker.name}
3328
className={classNames(
34-
"flex flex-row items-center",
35-
speakers.length === 1 && "md:flex-col"
29+
"bg-blue-gradient bg-blue text-center px-5 py-5 border-b-2 border-dotted border-b-white/50 last:border-b-0 group hover:bg-blue-dark transition-colors",
30+
speakers.length === 1 && "md:hover:bg-blue md:pt-10 md:pb-5"
3631
)}
3732
>
38-
<div
33+
<Link
34+
href={url}
3935
className={classNames(
40-
"w-20 h-20",
41-
speakers.length === 1 && "md:mx-auto md:w-60 md:h-60"
36+
"flex flex-row items-center",
37+
speakers.length === 1 && "md:flex-col"
4238
)}
4339
>
44-
<SpeakerImage
45-
image={image}
46-
placeholder={placeholder}
47-
hoverable={false}
48-
/>
49-
</div>
40+
<div
41+
className={classNames(
42+
"w-20 h-20 relative",
43+
speakers.length === 1 && "md:mx-auto md:w-60 md:h-60"
44+
)}
45+
>
46+
{conference.edition === "2025" ? (
47+
<SpeakerImage2025 speaker={speaker} image={image} />
48+
) : (
49+
<SpeakerImage image={image} placeholder={placeholder} />
50+
)}
51+
</div>
5052

51-
<div
52-
className={classNames(
53-
"text-inherit uppercase font-title ml-5 flex-1 transition-colors group-hover:text-white",
54-
speakers.length === 1 &&
55-
"md:mt-7 md:ml-0 md:group-hover:text-blue-black"
56-
)}
57-
>
58-
<span
53+
<div
5954
className={classNames(
60-
"inline-block leading-tight font-semibold text-lg",
61-
speakers.length === 1 && "md:text-xl"
55+
"text-inherit uppercase font-title ml-5 flex-1 transition-colors group-hover:text-white",
56+
speakers.length === 1 &&
57+
"md:mt-7 md:ml-0 md:group-hover:text-blue-black"
6258
)}
6359
>
64-
{name}
65-
</span>
66-
<Overline className="text-white lined-center lined-white/50">
67-
{job}
68-
<br />
69-
{company ? (
70-
<>
71-
@ <strong className="font-normal">{company}</strong>
72-
</>
73-
) : null}
74-
</Overline>
75-
</div>
76-
</Link>
77-
{speakers.length === 1 && (
78-
<Button
79-
className="mt-5 white square hidden md:inline-block"
80-
size="small"
81-
to={speaker.url}
82-
>
83-
{t("conferences.see_speaker_details")}
84-
</Button>
85-
)}
86-
</div>
60+
<span
61+
className={classNames(
62+
"inline-block leading-tight font-semibold text-lg",
63+
speakers.length === 1 && "md:text-xl"
64+
)}
65+
>
66+
{name}
67+
</span>
68+
<Overline className="text-white lined-center lined-white/50">
69+
{job}
70+
<br />
71+
{company ? (
72+
<>
73+
@ <strong className="font-normal">{company}</strong>
74+
</>
75+
) : null}
76+
</Overline>
77+
</div>
78+
</Link>
79+
{speakers.length === 1 && (
80+
<Button
81+
className="mt-5 white square hidden md:inline-block"
82+
size="small"
83+
to={speaker.url}
84+
>
85+
{t("conferences.see_speaker_details")}
86+
</Button>
87+
)}
88+
</div>
89+
</>
8790
);
8891
})}
8992
</div>

pwa/app/(con)/[locale]/con/[edition]/speakers/[slug]/SpeakerPage.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import SpeakerConferenceSlot from "./SpeakerConferenceSlot";
88
import { LanguageContext } from "contexts/con/LanguageContext";
99
import Button from "components/con/common/Button";
1010
import SpeakerList from "components/con/speakers/SpeakerList";
11+
import SpeakerImage2025 from "components/con/speakers/SpeakerImage2025";
1112

1213
interface SpeakerProps {
1314
speakerData: Speaker;
@@ -45,7 +46,11 @@ export default function SpeakerPageTemplate({
4546
</div>
4647
<div className="flex flex-col relative flex-wrap items-center bg-grey px-10 pb-10 pt-28 | lg:flex-row lg:items-start | sm:pb-20">
4748
<div className="w-72 h-72 | md:w-80 md:h-80 | lg:w-[400px] lg:h-[400px]">
48-
<SpeakerImage big image={image} placeholder={placeholder} />
49+
{edition === "2025" ? (
50+
<SpeakerImage2025 speaker={speakerData} big image={image} />
51+
) : (
52+
<SpeakerImage big image={image} placeholder={placeholder} />
53+
)}
4954
</div>
5055
<div className="flex-1 | sm:px-6">
5156
<SpeakerDescription speaker={speakerData} />

pwa/app/(con)/[locale]/con/[edition]/speakers/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const generateStaticParams = async () => {
2424
{ edition: "2022" },
2525
{ edition: "2023" },
2626
{ edition: "2024" },
27+
{ edition: "2025" },
2728
];
2829
};
2930

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import React from "react";
2+
import classNames from "classnames";
3+
import { Speaker } from "types/con";
4+
5+
interface SpeakerImageProps {
6+
image: string;
7+
placeholder?: string;
8+
hoverable?: boolean;
9+
big?: boolean;
10+
speaker: Speaker;
11+
}
12+
13+
function nameToAngle(name: string): number {
14+
// Calcule l'angle (en degrés) de 0 à 360
15+
let hash = 0;
16+
for (let i = 0; i < name.length; i++) {
17+
hash += name.charCodeAt(i);
18+
}
19+
let angle = hash % 360;
20+
21+
// Plages interdites (en degrés)
22+
const forbiddenZones: [number, number][] = [
23+
[60, 120],
24+
[240, 300],
25+
];
26+
27+
// Corrige si dans une zone interdite
28+
for (const [start, end] of forbiddenZones) {
29+
if (angle >= start && angle < end) {
30+
angle = end; // Déplace à la fin de la zone interdite
31+
}
32+
}
33+
34+
return angle * (Math.PI / 180);
35+
}
36+
37+
function nameToSize(name: string, min = 10, max = 30) {
38+
let hash = 0;
39+
for (let i = 0; i < name.length; i++) {
40+
hash += (i + 1) * name.charCodeAt(i);
41+
}
42+
const range = max - min;
43+
return min + (hash % range);
44+
}
45+
46+
function cssPositionOnCircle(angle: number) {
47+
// angle en radians
48+
return {
49+
x: `calc(50% + ${Math.cos(angle) * 50}%)`,
50+
y: `calc(50% + ${Math.sin(angle) * 50}%)`,
51+
x2: `calc(50% + ${Math.cos(angle + Math.PI) * 50}%)`,
52+
y2: `calc(50% + ${Math.sin(angle + Math.PI) * 50}%)`,
53+
};
54+
}
55+
56+
export default function SpeakerImage({
57+
image,
58+
placeholder,
59+
big = false,
60+
hoverable = true,
61+
speaker,
62+
}: SpeakerImageProps) {
63+
if (speaker.edition === "2025") {
64+
const { name } = speaker;
65+
const angle = nameToAngle(name);
66+
const size = nameToSize(name);
67+
const pos = cssPositionOnCircle(angle);
68+
return (
69+
<>
70+
<svg width="0" height="0">
71+
<clipPath id={`clip-${speaker.id}`} clipPathUnits="objectBoundingBox">
72+
<path
73+
d={
74+
speaker.path ||
75+
"M1,0 H0 V0.55125 H0.13225 c-0.00475,0.02385,-0.00725,0.0485,-0.00725,0.07375 c0,0.2071,0.1679,0.375,0.375,0.375 s0.375,-0.1679,0.375,-0.375 c0,-0.02525,-0.0025,-0.0499,-0.00725,-0.07375 H1 V0 Z"
76+
}
77+
/>
78+
</clipPath>
79+
</svg>
80+
<div
81+
className={classNames(
82+
"w-full h-full relative bg-white rounded-full transition-all duration-500",
83+
hoverable && "group-hover:bg-blue-light "
84+
)}
85+
>
86+
<div
87+
className={classNames(
88+
"relative w-full h-full before:absolute before:w-[110%] before:h-[110%] before:left-1/2 before:top-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:pointer-events-none before:transition-all before:bg-circle before:bg-no-repeat before:duration-700 before:ease-out",
89+
hoverable && "group-hover:before:rotate-45"
90+
)}
91+
>
92+
<div
93+
className="aspect-square overflow-hidden w-[calc(400%/3)] pointer-events-none max-w-none absolute z-10 bottom-0 left-1/2 -translate-x-1/2"
94+
style={{ clipPath: `url(#clip-${speaker.id})` }}
95+
>
96+
<img
97+
src={image}
98+
className={classNames(
99+
"w-full h-full transition-all duration-500 will-change-transform origin-center",
100+
big && "scale-110",
101+
hoverable && "group-hover:-rotate-2 group-hover:scale-110 "
102+
)}
103+
alt=""
104+
/>
105+
</div>
106+
<div
107+
className={classNames(
108+
"absolute aspect-square -translate-x-1/2 -translate-y-1/2 transition-all duration-700",
109+
big && "scale-[150%]",
110+
hoverable && "group-hover:scale-[200%]"
111+
)}
112+
style={{
113+
left: pos.x,
114+
top: pos.y,
115+
width: `${size}%`,
116+
}}
117+
>
118+
<div
119+
className={classNames(
120+
"bg-pink/20 animate-float rounded-full size-full",
121+
big && "scale-[150%]"
122+
)}
123+
/>
124+
</div>
125+
<div
126+
className={classNames(
127+
"absolute z-20 aspect-square -translate-x-1/2 -translate-y-1/2 transition-all duration-700",
128+
hoverable && "group-hover:scale-[40%]"
129+
)}
130+
style={{
131+
left: pos.x2,
132+
top: pos.y2,
133+
width: `${50 - size}%`,
134+
}}
135+
>
136+
<div
137+
className={classNames(
138+
"bg-blue/30 animate-float2 rounded-full size-full",
139+
big && "scale-[60%]"
140+
)}
141+
/>
142+
</div>
143+
</div>
144+
</div>
145+
</>
146+
);
147+
}
148+
}

pwa/components/con/speakers/SpeakerItem.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import SpeakerSocialList from "./SpeakerSocialList";
55
import SpeakerImage from "./SpeakerImage";
66
import Overline from "components/con/common/typography/Overline";
77
import Link from "components/common/Link";
8+
import SpeakerImage2025 from "./SpeakerImage2025";
89

910
interface SpeakerProps {
1011
speaker: Speaker;
@@ -23,6 +24,8 @@ export default function SpeakerItem({
2324
speaker;
2425
const withSocial = social && (github || twitter);
2526

27+
const edition = speaker.edition;
28+
2629
return (
2730
<div
2831
className={classNames(
@@ -38,11 +41,23 @@ export default function SpeakerItem({
3841
minified ? "flex flex-row items-center" : "text-center"
3942
)}
4043
>
41-
<div
42-
className={classNames(minified ? "w-20 h-20" : "mx-auto w-60 h-60")}
43-
>
44-
<SpeakerImage image={image} placeholder={placeholder} />
45-
</div>
44+
{edition === "2025" ? (
45+
<div
46+
className={classNames(minified ? "w-20 h-20" : "mx-auto w-64 h-64")}
47+
>
48+
<SpeakerImage2025
49+
speaker={speaker}
50+
image={image}
51+
placeholder={placeholder}
52+
/>
53+
</div>
54+
) : (
55+
<div
56+
className={classNames(minified ? "w-20 h-20" : "mx-auto w-60 h-60")}
57+
>
58+
<SpeakerImage image={image} placeholder={placeholder} />
59+
</div>
60+
)}
4661

4762
<div
4863
className={classNames(

pwa/components/con/speakers/SpeakerSocialList.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ function SocialIcon({ url, iconName }: SocialIconProps) {
2424
}
2525

2626
export default function SpeakerSocialList({ speaker }: SpeakerSocialListProps) {
27-
const { github, twitter, mastodon } = speaker;
27+
const { github, twitter, mastodon, bluesky } = speaker;
2828
return (
2929
<div className="flex flex-row justify-center gap-1.5">
3030
{github && <SocialIcon url={github} iconName="github" />}
3131
{twitter && <SocialIcon url={twitter} iconName="twitter" />}
3232
{mastodon && <SocialIcon url={mastodon} iconName="mastodon" />}
33+
{bluesky && <SocialIcon url={bluesky} iconName="bluesky" />}
3334
</div>
3435
);
3536
}

pwa/con.tailwind.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,23 @@ module.exports = {
7575
"0%": { transform: "translateX(calc(-100%/3 - 1rem / 3))" },
7676
"100%": { transform: "translateX(calc(2 * (-100%/3 - 1rem / 3)))" },
7777
},
78+
float: {
79+
"0%": {
80+
transform: "rotate(0deg) translate3d(-3px, 0, 0) rotate(0deg)",
81+
},
82+
"100%": {
83+
transform: "rotate(360deg) translate3d(-3px, 0, 0) rotate(-360deg);",
84+
},
85+
},
7886
},
7987
animation: {
8088
web: "web 1s linear infinite",
8189
"web-invert": "web 1s linear infinite reverse",
8290
draw: "draw 2.5s linear infinite, draw-opacity 3s linear infinite",
8391
fade: "svg-shadow 2s ease-in-out infinite alternate",
8492
defile: "defile 60s linear infinite",
93+
float: "float 4s linear infinite",
94+
float2: "float 4.5s linear infinite reverse",
8595
},
8696
backgroundImage: {
8797
"conf-gradient":

0 commit comments

Comments
 (0)