@ansiversa/components 0.0.104 → 0.0.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Resume Template 4 - Executive Timeline</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ @media print {
10
+ .no-print { display: none !important; }
11
+ a { text-decoration: none; color: inherit; }
12
+ body { background: white !important; }
13
+ }
14
+ </style>
15
+ </head>
16
+ <body class="bg-slate-100 text-slate-900">
17
+ <div class="no-print sticky top-0 z-10 border-b border-slate-200 bg-white/90 backdrop-blur">
18
+ <div class="mx-auto flex max-w-4xl items-center justify-between px-4 py-3">
19
+ <div class="text-sm text-slate-600">Template 4 — Executive Timeline</div>
20
+ <button class="rounded-lg border border-slate-200 bg-white px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-slate-50" onclick="window.print()">
21
+ Print / Save PDF
22
+ </button>
23
+ </div>
24
+ </div>
25
+
26
+ <main class="mx-auto max-w-4xl p-4 sm:p-6">
27
+ <section class="rounded-2xl bg-white p-7 sm:p-10 shadow-sm ring-1 ring-slate-200 print:shadow-none print:ring-0">
28
+ <header class="flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between">
29
+ <div>
30
+ <h1 class="text-3xl font-bold tracking-tight">Karthikeyan Ramalingam</h1>
31
+ <p class="mt-1 text-sm font-medium text-slate-700">Senior Software Developer</p>
32
+ <div class="mt-3 flex flex-wrap gap-2 text-xs text-slate-600">
33
+ <span class="rounded-full bg-slate-100 px-3 py-1">Abu Dhabi, UAE</span>
34
+ <span class="rounded-full bg-slate-100 px-3 py-1">you@email.com</span>
35
+ <span class="rounded-full bg-slate-100 px-3 py-1">+971 50 000 0000</span>
36
+ <span class="rounded-full bg-slate-100 px-3 py-1">linkedin.com/in/yourname</span>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700 sm:max-w-sm">
41
+ <div class="text-xs font-semibold tracking-widest text-slate-500">SUMMARY</div>
42
+ <p class="mt-2 leading-6">
43
+ I build scalable products with strict standards, predictable architecture, and premium UX.
44
+ I prefer verification-first delivery and long-term maintainability.
45
+ </p>
46
+ </div>
47
+ </header>
48
+
49
+ <div class="my-8 h-px bg-slate-200"></div>
50
+
51
+ <div class="grid gap-10 lg:grid-cols-3">
52
+ <!-- Main timeline -->
53
+ <div class="lg:col-span-2">
54
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">EXPERIENCE</h2>
55
+
56
+ <div class="mt-6 space-y-8">
57
+ <!-- Timeline item -->
58
+ <div class="relative pl-6">
59
+ <div class="absolute left-0 top-1.5 h-full w-px bg-slate-200"></div>
60
+ <div class="absolute left-[-5px] top-1.5 h-3 w-3 rounded-full bg-slate-900"></div>
61
+
62
+ <div class="flex flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between">
63
+ <div>
64
+ <h3 class="text-base font-semibold">Senior Software Developer</h3>
65
+ <p class="text-sm text-slate-700">Global Aerospace Logistics Company</p>
66
+ </div>
67
+ <p class="text-sm text-slate-600">2023 — Present</p>
68
+ </div>
69
+
70
+ <ul class="mt-3 list-disc space-y-2 pl-5 text-sm leading-6 text-slate-700">
71
+ <li>Architected internal platforms to improve operational visibility and reliability.</li>
72
+ <li>Built a reusable UI system that reduced feature delivery time.</li>
73
+ <li>Strengthened production quality with standards and verification.</li>
74
+ </ul>
75
+ </div>
76
+
77
+ <div class="relative pl-6">
78
+ <div class="absolute left-0 top-1.5 h-full w-px bg-slate-200"></div>
79
+ <div class="absolute left-[-5px] top-1.5 h-3 w-3 rounded-full bg-slate-900"></div>
80
+
81
+ <div class="flex flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between">
82
+ <div>
83
+ <h3 class="text-base font-semibold">Software Developer</h3>
84
+ <p class="text-sm text-slate-700">HCL Technologies</p>
85
+ </div>
86
+ <p class="text-sm text-slate-600">2019 — 2023</p>
87
+ </div>
88
+
89
+ <ul class="mt-3 list-disc space-y-2 pl-5 text-sm leading-6 text-slate-700">
90
+ <li>Delivered enterprise modules with maintainable architecture and stable releases.</li>
91
+ <li>Improved reporting and data handling for business critical operations.</li>
92
+ </ul>
93
+ </div>
94
+ </div>
95
+
96
+ <div class="mt-10">
97
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">PROJECTS</h2>
98
+ <div class="mt-5 space-y-4">
99
+ <div class="rounded-xl border border-slate-200 p-4">
100
+ <div class="flex items-baseline justify-between">
101
+ <h3 class="font-semibold">Ansiversa</h3>
102
+ <span class="text-xs text-slate-500">2024—Present</span>
103
+ </div>
104
+ <p class="mt-2 text-sm leading-6 text-slate-700">
105
+ Multi-app ecosystem with shared auth + shared UI components under strict standards.
106
+ </p>
107
+ </div>
108
+ <div class="rounded-xl border border-slate-200 p-4">
109
+ <div class="flex items-baseline justify-between">
110
+ <h3 class="font-semibold">Quiz Institute</h3>
111
+ <span class="text-xs text-slate-500">2024—Present</span>
112
+ </div>
113
+ <p class="mt-2 text-sm leading-6 text-slate-700">
114
+ Step-based quiz playthrough with dynamic metadata and results saving.
115
+ </p>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Sidebar -->
122
+ <aside class="space-y-8">
123
+ <section class="rounded-xl border border-slate-200 p-4">
124
+ <h2 class="text-xs font-bold tracking-widest text-slate-500">SKILLS</h2>
125
+ <div class="mt-3 flex flex-wrap gap-2">
126
+ <span class="rounded-full bg-slate-100 px-3 py-1 text-xs">TypeScript</span>
127
+ <span class="rounded-full bg-slate-100 px-3 py-1 text-xs">Astro</span>
128
+ <span class="rounded-full bg-slate-100 px-3 py-1 text-xs">Alpine</span>
129
+ <span class="rounded-full bg-slate-100 px-3 py-1 text-xs">SQL</span>
130
+ <span class="rounded-full bg-slate-100 px-3 py-1 text-xs">Architecture</span>
131
+ </div>
132
+ </section>
133
+
134
+ <section class="rounded-xl border border-slate-200 p-4">
135
+ <h2 class="text-xs font-bold tracking-widest text-slate-500">EDUCATION</h2>
136
+ <div class="mt-3 text-sm text-slate-700">
137
+ <div class="font-semibold">B.E. / B.Tech — CSE</div>
138
+ <div class="text-slate-600">Your University Name</div>
139
+ <div class="text-slate-500">2015 — 2019</div>
140
+ </div>
141
+ </section>
142
+
143
+ <section class="rounded-xl border border-slate-200 p-4">
144
+ <h2 class="text-xs font-bold tracking-widest text-slate-500">HIGHLIGHTS</h2>
145
+ <ul class="mt-3 space-y-2 text-sm leading-6 text-slate-700">
146
+ <li>• Premium design systems</li>
147
+ <li>• Verification-first delivery</li>
148
+ <li>• Maintainable architecture</li>
149
+ </ul>
150
+ </section>
151
+ </aside>
152
+ </div>
153
+ </section>
154
+ </main>
155
+ </body>
156
+ </html>
@@ -0,0 +1,115 @@
1
+ export type ResumeTemplateType = "classic" | "modern" | "minimal" | "timeline";
2
+
3
+ export type ResumeData = {
4
+ version: "1.0";
5
+
6
+ basics: {
7
+ fullName: string;
8
+ headline: string; // e.g., "Senior Software Developer"
9
+ summary?: string;
10
+
11
+ location?: {
12
+ city?: string;
13
+ country?: string;
14
+ label?: string; // e.g., "Abu Dhabi, UAE" (preferred display)
15
+ };
16
+
17
+ contact: {
18
+ email?: string;
19
+ phone?: string;
20
+ website?: string;
21
+ };
22
+
23
+ links?: Array<{
24
+ label: string; // e.g., "LinkedIn", "GitHub"
25
+ url: string;
26
+ }>;
27
+ };
28
+
29
+ skills?: Array<{
30
+ name: string; // e.g., "TypeScript"
31
+ level?: "Beginner" | "Intermediate" | "Advanced" | "Expert";
32
+ keywords?: string[]; // optional extra tags
33
+ }>;
34
+
35
+ highlights?: string[]; // short bullets for sidebar or summary
36
+
37
+ experience?: Array<{
38
+ id: string; // stable ID for reorder/edit
39
+ role: string; // "Senior Software Developer"
40
+ company: string; // "Global Aerospace Logistics Company"
41
+ location?: string; // "UAE" or "Abu Dhabi, UAE"
42
+ start: { year: number; month?: number };
43
+ end?: { year: number; month?: number }; // omit when present=true
44
+ present?: boolean;
45
+ summary?: string; // optional 1-line intro
46
+ bullets?: string[];
47
+ tags?: string[]; // chips like "Architecture", "UI Systems"
48
+ }>;
49
+
50
+ projects?: Array<{
51
+ id: string;
52
+ name: string;
53
+ link?: string;
54
+ start?: { year: number; month?: number };
55
+ end?: { year: number; month?: number };
56
+ present?: boolean;
57
+ summary?: string;
58
+ bullets?: string[];
59
+ tags?: string[];
60
+ }>;
61
+
62
+ education?: Array<{
63
+ id: string;
64
+ degree: string; // "B.E. / B.Tech — Computer Science"
65
+ school: string;
66
+ location?: string;
67
+ start?: { year: number; month?: number };
68
+ end?: { year: number; month?: number };
69
+ grade?: string; // optional
70
+ bullets?: string[];
71
+ }>;
72
+
73
+ certifications?: Array<{
74
+ id: string;
75
+ name: string;
76
+ issuer?: string;
77
+ year?: number;
78
+ link?: string;
79
+ }>;
80
+
81
+ languages?: Array<{
82
+ name: string; // "English"
83
+ proficiency?: "Native" | "Fluent" | "Professional" | "Intermediate" | "Basic";
84
+ }>;
85
+
86
+ awards?: Array<{
87
+ id: string;
88
+ title: string;
89
+ year?: number;
90
+ by?: string;
91
+ summary?: string;
92
+ }>;
93
+
94
+ // Template feature flags (optional)
95
+ settings?: {
96
+ showSkillsAs?: "chips" | "levels"; // chips for classic/modern, levels for minimal
97
+ paper?: "A4" | "Letter";
98
+ };
99
+ };
100
+
101
+ /** Helpers (optional for Codex) */
102
+ export function formatDateRange(item: {
103
+ start: { year: number; month?: number };
104
+ end?: { year: number; month?: number };
105
+ present?: boolean;
106
+ }): string {
107
+ const m = (x?: number) =>
108
+ typeof x === "number" ? String(x).padStart(2, "0") : undefined;
109
+
110
+ const start = item.start.month ? `${m(item.start.month)}/${item.start.year}` : `${item.start.year}`;
111
+ if (item.present) return `${start} — Present`;
112
+ if (!item.end) return `${start}`;
113
+ const end = item.end.month ? `${m(item.end.month)}/${item.end.year}` : `${item.end.year}`;
114
+ return `${start} — ${end}`;
115
+ }
@@ -18,6 +18,7 @@ interface User {
18
18
  interface Props {
19
19
  className?: string;
20
20
  user?: User;
21
+ notificationUnreadCount?: number;
21
22
 
22
23
  /** Additional HTML attributes */
23
24
  [attrs: string]: any;
@@ -26,6 +27,7 @@ interface Props {
26
27
  const {
27
28
  className = "",
28
29
  user,
30
+ notificationUnreadCount,
29
31
  ...attrs
30
32
  } = Astro.props as Props;
31
33
 
@@ -99,6 +101,9 @@ const authLink: NavLink = {
99
101
 
100
102
  const userInitial =
101
103
  (user?.name?.charAt(0) ?? user?.email?.charAt(0) ?? "?").toUpperCase();
104
+
105
+ const unreadCount = Number(notificationUnreadCount ?? 0);
106
+ const unreadBadge = unreadCount > 99 ? "99+" : `${unreadCount}`;
102
107
  ---
103
108
 
104
109
  <div class={classAttr} {...attrs}>
@@ -162,6 +167,11 @@ const userInitial =
162
167
 
163
168
  <a href={`${ROOT_URL}/notifications`} class="av-user-menu-item av-dropdown-item" role="menuitem">
164
169
  <span>Notifications</span>
170
+ {unreadCount > 0 && (
171
+ <span class="av-badge av-badge-soft" aria-label={`${unreadBadge} unread notifications`}>
172
+ {unreadBadge}
173
+ </span>
174
+ )}
165
175
  </a>
166
176
 
167
177
  <a href={`${ROOT_URL}/favorites`} class="av-user-menu-item av-dropdown-item" role="menuitem">
@@ -7,11 +7,13 @@ import AvNavbarActions from "../AvNavbarActions.astro";
7
7
  interface Props {
8
8
  title?: string;
9
9
  description?: string;
10
+ notificationUnreadCount?: number;
10
11
  }
11
12
 
12
13
  const {
13
14
  title = "Ansiversa",
14
15
  description = "Ansiversa – A unified ecosystem of premium Apps designed to feel simple, powerful, and delightful.",
16
+ notificationUnreadCount = 0,
15
17
  } = Astro.props;
16
18
 
17
19
  const user = Astro.locals.user;
@@ -72,7 +74,7 @@ const ROOT_URL = rawDomain.match(/^https?:\/\//i)
72
74
  <!-- Top Navigation -->
73
75
  <AvNavbar>
74
76
  <AvBrand />
75
- <AvNavbarActions user={user} />
77
+ <AvNavbarActions user={user} notificationUnreadCount={notificationUnreadCount} />
76
78
  </AvNavbar>
77
79
 
78
80
  <!-- Main content -->
@@ -0,0 +1,25 @@
1
+ ---
2
+ import type { ResumeData, ResumeTemplateType } from "../../resume-templates/typescript-schema";
3
+ import ResumeTemplateClassic from "./ResumeTemplateClassic.astro";
4
+ import ResumeTemplateExecutiveTimeline from "./ResumeTemplateExecutiveTimeline.astro";
5
+ import ResumeTemplateMinimal from "./ResumeTemplateMinimal.astro";
6
+ import ResumeTemplateModernTwoTone from "./ResumeTemplateModernTwoTone.astro";
7
+
8
+ interface Props {
9
+ type: ResumeTemplateType;
10
+ data: ResumeData;
11
+ }
12
+
13
+ const { type, data } = Astro.props as Props;
14
+
15
+ const templates = {
16
+ classic: ResumeTemplateClassic,
17
+ modern: ResumeTemplateModernTwoTone,
18
+ minimal: ResumeTemplateMinimal,
19
+ timeline: ResumeTemplateExecutiveTimeline,
20
+ } satisfies Record<ResumeTemplateType, typeof ResumeTemplateClassic>;
21
+
22
+ const Template = templates[type];
23
+ ---
24
+
25
+ {Template ? <Template data={data} /> : null}
@@ -0,0 +1,234 @@
1
+ ---
2
+ import type { ResumeData } from "../../resume-templates/typescript-schema";
3
+ import { formatDateRange } from "../../resume-templates/typescript-schema";
4
+
5
+ interface Props {
6
+ data: ResumeData;
7
+ }
8
+
9
+ const { data } = Astro.props as Props;
10
+ const { basics } = data;
11
+
12
+ const locationLabel =
13
+ basics.location?.label ||
14
+ [basics.location?.city, basics.location?.country].filter(Boolean).join(", ");
15
+
16
+ const formatUrlLabel = (value: string) =>
17
+ value.replace(/^https?:\/\//, "").replace(/\/$/, "");
18
+
19
+ const contactItems: Array<{ label: string; href?: string }> = [];
20
+ if (locationLabel) contactItems.push({ label: locationLabel });
21
+ if (basics.contact.email) {
22
+ contactItems.push({ label: basics.contact.email, href: `mailto:${basics.contact.email}` });
23
+ }
24
+ if (basics.contact.phone) {
25
+ const phoneHref = basics.contact.phone.replace(/\s+/g, "");
26
+ contactItems.push({ label: basics.contact.phone, href: `tel:${phoneHref}` });
27
+ }
28
+ if (basics.contact.website) {
29
+ contactItems.push({ label: formatUrlLabel(basics.contact.website), href: basics.contact.website });
30
+ }
31
+ for (const link of basics.links ?? []) {
32
+ contactItems.push({ label: link.label ?? formatUrlLabel(link.url), href: link.url });
33
+ }
34
+
35
+ const experienceItems = data.experience ?? [];
36
+ const projectItems = data.projects ?? [];
37
+ const educationItems = data.education ?? [];
38
+ const skills = data.skills ?? [];
39
+ const highlights = data.highlights ?? [];
40
+ const languages = data.languages ?? [];
41
+ ---
42
+
43
+ <style>
44
+ @media print {
45
+ .resume-template a {
46
+ text-decoration: none;
47
+ color: inherit;
48
+ }
49
+ }
50
+ </style>
51
+
52
+ <div class="resume-template bg-slate-100 text-slate-900 print:bg-white">
53
+ <main class="mx-auto max-w-4xl p-4 sm:p-6">
54
+ <section class="rounded-2xl bg-white p-6 shadow-sm ring-1 ring-slate-200 sm:p-10 print:shadow-none print:ring-0">
55
+ <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
56
+ <div>
57
+ <h1 class="text-3xl font-bold tracking-tight">{basics.fullName}</h1>
58
+ <p class="mt-1 text-base font-medium text-slate-700">{basics.headline}</p>
59
+ {basics.summary && (
60
+ <p class="mt-3 max-w-2xl text-sm leading-6 text-slate-600">{basics.summary}</p>
61
+ )}
62
+ </div>
63
+
64
+ {contactItems.length > 0 && (
65
+ <div class="text-sm text-slate-700 sm:text-right">
66
+ <div class="flex flex-col gap-1">
67
+ {contactItems.map((item) =>
68
+ item.href ? (
69
+ <a
70
+ class="text-slate-900 underline underline-offset-2 hover:text-slate-700"
71
+ href={item.href}
72
+ >
73
+ {item.label}
74
+ </a>
75
+ ) : (
76
+ <span>{item.label}</span>
77
+ )
78
+ )}
79
+ </div>
80
+ </div>
81
+ )}
82
+ </header>
83
+
84
+ <div class="my-8 h-px bg-slate-200"></div>
85
+
86
+ <div class="grid gap-10 lg:grid-cols-3">
87
+ <div class="space-y-10 lg:col-span-2">
88
+ {experienceItems.length > 0 && (
89
+ <section>
90
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">EXPERIENCE</h2>
91
+ <div class="mt-4 space-y-6">
92
+ {experienceItems.map((item) => {
93
+ const range = formatDateRange(item);
94
+ const meta = item.location ? `${range} · ${item.location}` : range;
95
+ return (
96
+ <article>
97
+ <div class="flex flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between">
98
+ <div>
99
+ <h3 class="text-base font-semibold">{item.role}</h3>
100
+ <p class="text-sm text-slate-700">{item.company}</p>
101
+ </div>
102
+ <p class="text-sm text-slate-600">{meta}</p>
103
+ </div>
104
+ {item.summary && (
105
+ <p class="mt-2 text-sm leading-6 text-slate-700">{item.summary}</p>
106
+ )}
107
+ {item.bullets && item.bullets.length > 0 && (
108
+ <ul class="mt-3 list-disc space-y-2 pl-5 text-sm leading-6 text-slate-700">
109
+ {item.bullets.map((bullet) => (
110
+ <li>{bullet}</li>
111
+ ))}
112
+ </ul>
113
+ )}
114
+ </article>
115
+ );
116
+ })}
117
+ </div>
118
+ </section>
119
+ )}
120
+
121
+ {projectItems.length > 0 && (
122
+ <section>
123
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">PROJECTS</h2>
124
+ <div class="mt-4 space-y-5">
125
+ {projectItems.map((project) => {
126
+ const range = project.start
127
+ ? formatDateRange({ start: project.start, end: project.end, present: project.present })
128
+ : undefined;
129
+ return (
130
+ <article>
131
+ <div class="flex items-baseline justify-between gap-3">
132
+ {project.link ? (
133
+ <a class="text-base font-semibold underline underline-offset-4" href={project.link}>
134
+ {project.name}
135
+ </a>
136
+ ) : (
137
+ <h3 class="text-base font-semibold">{project.name}</h3>
138
+ )}
139
+ {range && <p class="text-sm text-slate-600">{range}</p>}
140
+ </div>
141
+ {project.summary && (
142
+ <p class="mt-2 text-sm leading-6 text-slate-700">{project.summary}</p>
143
+ )}
144
+ {project.bullets && project.bullets.length > 0 && (
145
+ <ul class="mt-3 list-disc space-y-2 pl-5 text-sm leading-6 text-slate-700">
146
+ {project.bullets.map((bullet) => (
147
+ <li>{bullet}</li>
148
+ ))}
149
+ </ul>
150
+ )}
151
+ {project.tags && project.tags.length > 0 && (
152
+ <div class="mt-2 flex flex-wrap gap-2">
153
+ {project.tags.map((tag) => (
154
+ <span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-700">
155
+ {tag}
156
+ </span>
157
+ ))}
158
+ </div>
159
+ )}
160
+ </article>
161
+ );
162
+ })}
163
+ </div>
164
+ </section>
165
+ )}
166
+
167
+ {educationItems.length > 0 && (
168
+ <section>
169
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">EDUCATION</h2>
170
+ <div class="mt-4 space-y-4">
171
+ {educationItems.map((edu) => {
172
+ const range = edu.start ? formatDateRange({ start: edu.start, end: edu.end }) : undefined;
173
+ return (
174
+ <div class="flex flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between">
175
+ <div>
176
+ <h3 class="text-base font-semibold">{edu.degree}</h3>
177
+ <p class="text-sm text-slate-700">{edu.school}</p>
178
+ {edu.location && (
179
+ <p class="text-sm text-slate-600">{edu.location}</p>
180
+ )}
181
+ {edu.grade && <p class="text-sm text-slate-600">{edu.grade}</p>}
182
+ </div>
183
+ {range && <p class="text-sm text-slate-600">{range}</p>}
184
+ </div>
185
+ );
186
+ })}
187
+ </div>
188
+ </section>
189
+ )}
190
+ </div>
191
+
192
+ <aside class="space-y-10">
193
+ {skills.length > 0 && (
194
+ <section>
195
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">SKILLS</h2>
196
+ <div class="mt-4 flex flex-wrap gap-2">
197
+ {skills.map((skill) => (
198
+ <span class="rounded-lg bg-slate-100 px-3 py-1.5 text-sm text-slate-800">
199
+ {skill.name}
200
+ </span>
201
+ ))}
202
+ </div>
203
+ </section>
204
+ )}
205
+
206
+ {highlights.length > 0 && (
207
+ <section>
208
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">HIGHLIGHTS</h2>
209
+ <ul class="mt-4 list-disc space-y-2 pl-5 text-sm leading-6 text-slate-700">
210
+ {highlights.map((item) => (
211
+ <li>{item}</li>
212
+ ))}
213
+ </ul>
214
+ </section>
215
+ )}
216
+
217
+ {languages.length > 0 && (
218
+ <section>
219
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">LANGUAGES</h2>
220
+ <ul class="mt-4 space-y-2 text-sm text-slate-700">
221
+ {languages.map((language) => (
222
+ <li>
223
+ {language.name}
224
+ {language.proficiency ? ` · ${language.proficiency}` : ""}
225
+ </li>
226
+ ))}
227
+ </ul>
228
+ </section>
229
+ )}
230
+ </aside>
231
+ </div>
232
+ </section>
233
+ </main>
234
+ </div>