Skip to content

Commit 1ccde20

Browse files
committed
feat(experience): surface current role with pulsing Present indicator
Timeline cards whose date range ends in 'Present' now render a green pulsing dot next to the end-date label, turn the center-track node green with a subtle glow ring (desktop), and switch the card's left border to green (mobile). Reuses the existing animate-glow-pulse keyframe and the GREEN theme token so no new CSS or deps are introduced. Adds an isPresent() helper in dateRange.ts for a single source of truth. Accessible via aria-label on the indicator.
1 parent f7eb2b7 commit 1ccde20

3 files changed

Lines changed: 229 additions & 140 deletions

File tree

src/pages/experience/TimelineCardDesktop.tsx

Lines changed: 133 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { motion } from "motion/react";
22
import { MapPin } from "lucide-react";
33
import type { ProfessionalExperience, PositionOfResponsibility } from "@/types";
44
import { slideInLeft, slideInRight } from "@utils/animations";
5-
import { splitDateRange } from "@utils/dateRange";
6-
import { MONO_FONT } from "@/constants/theme";
5+
import { splitDateRange, isPresent } from "@utils/dateRange";
6+
import { MONO_FONT, GREEN } from "@/constants/theme";
77
import TimelineCardContent from "./TimelineCardContent";
88

99
interface TimelineCardDesktopProps {
@@ -18,114 +18,153 @@ const TimelineCardDesktop = ({
1818
index,
1919
accentColor,
2020
onClick,
21-
}: TimelineCardDesktopProps) => (
22-
<motion.div
23-
layout="position"
24-
style={{
25-
display: "grid",
26-
gridTemplateColumns: "160px 40px 1fr",
27-
gap: 0,
28-
}}
29-
variants={index % 2 === 0 ? slideInLeft : slideInRight}
30-
custom={index}
31-
transition={{ layout: { duration: 0.4, ease: [0.4, 0, 0.2, 1] } }}
32-
>
33-
{/* Left: Date + Location */}
34-
<div style={{ paddingTop: 4, textAlign: "right", paddingRight: 20 }}>
35-
<span
36-
style={{
37-
fontFamily: MONO_FONT,
38-
fontSize: 12,
39-
fontWeight: 600,
40-
color: accentColor,
41-
}}
42-
>
43-
{splitDateRange(item.date).start}
44-
</span>
45-
<span
46-
style={{
47-
display: "block",
48-
fontFamily: MONO_FONT,
49-
fontSize: 11,
50-
color: "#6e6e90",
51-
marginTop: 2,
52-
}}
53-
>
54-
{item.date.split(" - ").at(1) ?? ""}
55-
</span>
56-
{item.location && (
57-
<p
21+
}: TimelineCardDesktopProps) => {
22+
const { start, end } = splitDateRange(item.date);
23+
const active = isPresent(item.date);
24+
25+
return (
26+
<motion.div
27+
layout="position"
28+
style={{
29+
display: "grid",
30+
gridTemplateColumns: "160px 40px 1fr",
31+
gap: 0,
32+
}}
33+
variants={index % 2 === 0 ? slideInLeft : slideInRight}
34+
custom={index}
35+
transition={{ layout: { duration: 0.4, ease: [0.4, 0, 0.2, 1] } }}
36+
>
37+
{/* Left: Date + Location */}
38+
<div style={{ paddingTop: 4, textAlign: "right", paddingRight: 20 }}>
39+
<span
5840
style={{
59-
color: "#6e6e90",
41+
fontFamily: MONO_FONT,
6042
fontSize: 12,
61-
display: "flex",
62-
alignItems: "center",
63-
justifyContent: "flex-end",
64-
gap: 4,
65-
marginTop: 12,
43+
fontWeight: 600,
44+
color: accentColor,
6645
}}
6746
>
68-
<MapPin size={11} style={{ flexShrink: 0 }} />
69-
{item.location}
70-
</p>
71-
)}
72-
</div>
47+
{start}
48+
</span>
49+
{active ? (
50+
<span
51+
style={{
52+
display: "inline-flex",
53+
alignItems: "center",
54+
justifyContent: "flex-end",
55+
gap: 5,
56+
width: "100%",
57+
marginTop: 2,
58+
fontFamily: MONO_FONT,
59+
fontSize: 11,
60+
fontWeight: 600,
61+
color: GREEN,
62+
letterSpacing: "0.02em",
63+
}}
64+
aria-label="Currently active role"
65+
>
66+
<span
67+
className="animate-glow-pulse"
68+
aria-hidden="true"
69+
style={{
70+
width: 6,
71+
height: 6,
72+
borderRadius: "50%",
73+
backgroundColor: GREEN,
74+
boxShadow: `0 0 6px ${GREEN}99`,
75+
flexShrink: 0,
76+
}}
77+
/>
78+
Present
79+
</span>
80+
) : (
81+
<span
82+
style={{
83+
display: "block",
84+
fontFamily: MONO_FONT,
85+
fontSize: 11,
86+
color: "#6e6e90",
87+
marginTop: 2,
88+
}}
89+
>
90+
{end ?? ""}
91+
</span>
92+
)}
93+
{item.location && (
94+
<p
95+
style={{
96+
color: "#6e6e90",
97+
fontSize: 12,
98+
display: "flex",
99+
alignItems: "center",
100+
justifyContent: "flex-end",
101+
gap: 4,
102+
marginTop: 12,
103+
}}
104+
>
105+
<MapPin size={11} style={{ flexShrink: 0 }} />
106+
{item.location}
107+
</p>
108+
)}
109+
</div>
73110

74-
{/* Center: Timeline track */}
75-
<div
76-
style={{
77-
display: "flex",
78-
flexDirection: "column",
79-
alignItems: "center",
80-
position: "relative",
81-
}}
82-
>
111+
{/* Center: Timeline track */}
83112
<div
84113
style={{
85-
width: 16,
86-
height: 16,
87-
borderRadius: "50%",
88-
border: `2px solid ${accentColor}`,
89-
backgroundColor: "rgba(6, 6, 16, 0.6)",
90-
marginTop: 4,
114+
display: "flex",
115+
flexDirection: "column",
116+
alignItems: "center",
91117
position: "relative",
92-
zIndex: 2,
93-
flexShrink: 0,
94118
}}
95119
>
96120
<div
97-
className="animate-glow-pulse"
98121
style={{
99-
position: "absolute",
100-
inset: 3,
122+
width: 16,
123+
height: 16,
101124
borderRadius: "50%",
102-
backgroundColor: accentColor,
125+
border: `2px solid ${active ? GREEN : accentColor}`,
126+
backgroundColor: "rgba(6, 6, 16, 0.6)",
127+
marginTop: 4,
128+
position: "relative",
129+
zIndex: 2,
130+
flexShrink: 0,
131+
boxShadow: active ? `0 0 0 4px ${GREEN}22` : undefined,
132+
}}
133+
>
134+
<div
135+
className="animate-glow-pulse"
136+
style={{
137+
position: "absolute",
138+
inset: 3,
139+
borderRadius: "50%",
140+
backgroundColor: active ? GREEN : accentColor,
141+
}}
142+
/>
143+
</div>
144+
<div
145+
style={{
146+
width: 2,
147+
flex: 1,
148+
background: `linear-gradient(to bottom, ${accentColor}40, ${accentColor}10)`,
149+
borderRadius: 4,
103150
}}
104151
/>
105152
</div>
106-
<div
107-
style={{
108-
width: 2,
109-
flex: 1,
110-
background: `linear-gradient(to bottom, ${accentColor}40, ${accentColor}10)`,
111-
borderRadius: 4,
112-
}}
113-
/>
114-
</div>
115153

116-
{/* Right: Content card */}
117-
<div
118-
className="glass-card"
119-
style={{ padding: "24px 24px", marginBottom: 20 }}
120-
>
121-
<TimelineCardContent
122-
item={item}
123-
accentColor={accentColor}
124-
isMobile={false}
125-
onClick={onClick}
126-
/>
127-
</div>
128-
</motion.div>
129-
);
154+
{/* Right: Content card */}
155+
<div
156+
className="glass-card"
157+
style={{ padding: "24px 24px", marginBottom: 20 }}
158+
>
159+
<TimelineCardContent
160+
item={item}
161+
accentColor={accentColor}
162+
isMobile={false}
163+
onClick={onClick}
164+
/>
165+
</div>
166+
</motion.div>
167+
);
168+
};
130169

131170
export default TimelineCardDesktop;

0 commit comments

Comments
 (0)