@ansiversa/components 0.0.105 → 0.0.117

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,215 @@
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 contactChips: string[] = [];
20
+ if (locationLabel) contactChips.push(locationLabel);
21
+ if (basics.contact.email) contactChips.push(basics.contact.email);
22
+ if (basics.contact.phone) contactChips.push(basics.contact.phone);
23
+ if (basics.contact.website) contactChips.push(formatUrlLabel(basics.contact.website));
24
+ for (const link of basics.links ?? []) {
25
+ contactChips.push(link.label ?? formatUrlLabel(link.url));
26
+ }
27
+
28
+ const experienceItems = data.experience ?? [];
29
+ const projects = data.projects ?? [];
30
+ const skills = data.skills ?? [];
31
+ const education = data.education ?? [];
32
+ const highlights = data.highlights ?? [];
33
+ const declaration = data.declaration ?? {};
34
+ const hasDeclaration = Boolean(declaration.text || declaration.place || declaration.name);
35
+ ---
36
+
37
+ <style>
38
+ @media print {
39
+ .resume-template a {
40
+ text-decoration: none;
41
+ color: inherit;
42
+ }
43
+ .resume-template {
44
+ background: white;
45
+ }
46
+ }
47
+ </style>
48
+
49
+ <div class="resume-template bg-slate-100 text-slate-900 print:bg-white">
50
+ <main class="mx-auto max-w-4xl p-4 sm:p-6">
51
+ <section class="rounded-2xl bg-white p-7 shadow-sm ring-1 ring-slate-200 sm:p-10 print:shadow-none print:ring-0">
52
+ <header class="flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between">
53
+ <div>
54
+ <h1 class="text-3xl font-bold tracking-tight">{basics.fullName}</h1>
55
+ <p class="mt-1 text-sm font-medium text-slate-700">{basics.headline}</p>
56
+ {contactChips.length > 0 && (
57
+ <div class="mt-3 flex flex-wrap gap-2 text-xs text-slate-600">
58
+ {contactChips.map((chip) => (
59
+ <span class="rounded-full bg-slate-100 px-3 py-1">{chip}</span>
60
+ ))}
61
+ </div>
62
+ )}
63
+ </div>
64
+
65
+ {basics.summary && (
66
+ <div class="rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700 sm:max-w-sm">
67
+ <div class="text-xs font-semibold tracking-widest text-slate-500">SUMMARY</div>
68
+ <p class="mt-2 leading-6">{basics.summary}</p>
69
+ </div>
70
+ )}
71
+ </header>
72
+
73
+ <div class="my-8 h-px bg-slate-200"></div>
74
+
75
+ <div class="grid gap-10 lg:grid-cols-3">
76
+ <div class="lg:col-span-2">
77
+ {experienceItems.length > 0 && (
78
+ <>
79
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">EXPERIENCE</h2>
80
+ <div class="mt-6 space-y-8">
81
+ {experienceItems.map((item) => (
82
+ <div class="relative pl-6">
83
+ <div class="absolute left-0 top-1.5 h-full w-px bg-slate-200"></div>
84
+ <div class="absolute left-[-5px] top-1.5 h-3 w-3 rounded-full bg-slate-900"></div>
85
+
86
+ <div class="flex flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between">
87
+ <div>
88
+ <h3 class="text-base font-semibold">{item.role}</h3>
89
+ <p class="text-sm text-slate-700">{item.company}</p>
90
+ </div>
91
+ <p class="text-sm text-slate-600">{formatDateRange(item)}</p>
92
+ </div>
93
+
94
+ {item.summary && (
95
+ <p class="mt-2 text-sm leading-6 text-slate-700">{item.summary}</p>
96
+ )}
97
+ {item.bullets && item.bullets.length > 0 && (
98
+ <ul class="mt-3 list-disc space-y-2 pl-5 text-sm leading-6 text-slate-700">
99
+ {item.bullets.map((bullet) => (
100
+ <li>{bullet}</li>
101
+ ))}
102
+ </ul>
103
+ )}
104
+ </div>
105
+ ))}
106
+ </div>
107
+ </>
108
+ )}
109
+
110
+ {projects.length > 0 && (
111
+ <div class="mt-10">
112
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">PROJECTS</h2>
113
+ <div class="mt-5 space-y-4">
114
+ {projects.map((project) => (
115
+ <div class="rounded-xl border border-slate-200 p-4">
116
+ <div class="flex items-baseline justify-between">
117
+ {project.link ? (
118
+ <a class="font-semibold underline underline-offset-4" href={project.link}>
119
+ {project.name}
120
+ </a>
121
+ ) : (
122
+ <h3 class="font-semibold">{project.name}</h3>
123
+ )}
124
+ {project.start && (
125
+ <span class="text-xs text-slate-500">
126
+ {formatDateRange({
127
+ start: project.start,
128
+ end: project.end,
129
+ present: project.present,
130
+ })}
131
+ </span>
132
+ )}
133
+ </div>
134
+ {project.summary && (
135
+ <p class="mt-2 text-sm leading-6 text-slate-700">{project.summary}</p>
136
+ )}
137
+ </div>
138
+ ))}
139
+ </div>
140
+ </div>
141
+ )}
142
+ </div>
143
+
144
+ <aside class="space-y-8">
145
+ {skills.length > 0 && (
146
+ <section class="rounded-xl border border-slate-200 p-4">
147
+ <h2 class="text-xs font-bold tracking-widest text-slate-500">SKILLS</h2>
148
+ <div class="mt-3 flex flex-wrap gap-2">
149
+ {skills.map((skill) => (
150
+ <span class="rounded-full bg-slate-100 px-3 py-1 text-xs">{skill.name}</span>
151
+ ))}
152
+ </div>
153
+ </section>
154
+ )}
155
+
156
+ {education.length > 0 && (
157
+ <section class="rounded-xl border border-slate-200 p-4">
158
+ <h2 class="text-xs font-bold tracking-widest text-slate-500">EDUCATION</h2>
159
+ <div class="mt-3 space-y-4 text-sm text-slate-700">
160
+ {education.map((edu) => (
161
+ <div>
162
+ <div class="font-semibold">{edu.degree}</div>
163
+ <div class="text-slate-600">{edu.school}</div>
164
+ {edu.location && <div class="text-slate-500">{edu.location}</div>}
165
+ {edu.start && (
166
+ <div class="text-slate-500">
167
+ {formatDateRange({ start: edu.start, end: edu.end })}
168
+ </div>
169
+ )}
170
+ </div>
171
+ ))}
172
+ </div>
173
+ </section>
174
+ )}
175
+
176
+ {highlights.length > 0 && (
177
+ <section class="rounded-xl border border-slate-200 p-4">
178
+ <h2 class="text-xs font-bold tracking-widest text-slate-500">HIGHLIGHTS</h2>
179
+ <ul class="mt-3 space-y-2 text-sm leading-6 text-slate-700">
180
+ {highlights.map((highlight) => (
181
+ <li>• {highlight}</li>
182
+ ))}
183
+ </ul>
184
+ </section>
185
+ )}
186
+ </aside>
187
+ </div>
188
+
189
+ {hasDeclaration && (
190
+ <>
191
+ <div class="my-8 h-px bg-slate-200"></div>
192
+ <section class="space-y-4">
193
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">DECLARATION</h2>
194
+ {declaration.text && (
195
+ <p class="text-sm leading-6 text-slate-700">{declaration.text}</p>
196
+ )}
197
+ {(declaration.place || declaration.name) && (
198
+ <div class="flex flex-wrap items-center justify-between gap-2 text-sm text-slate-700">
199
+ {declaration.place && (
200
+ <div>
201
+ <span class="font-semibold text-slate-900">Place:</span>{" "}
202
+ {declaration.place}
203
+ </div>
204
+ )}
205
+ {declaration.name && (
206
+ <div class="font-semibold text-slate-900">{declaration.name}</div>
207
+ )}
208
+ </div>
209
+ )}
210
+ </section>
211
+ </>
212
+ )}
213
+ </section>
214
+ </main>
215
+ </div>
@@ -0,0 +1,246 @@
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 contactLinks: Array<{ label: string; href?: string }> = [];
20
+ if (basics.contact.email) {
21
+ contactLinks.push({ label: basics.contact.email, href: `mailto:${basics.contact.email}` });
22
+ }
23
+ if (basics.contact.phone) {
24
+ const phoneHref = basics.contact.phone.replace(/\s+/g, "");
25
+ contactLinks.push({ label: basics.contact.phone, href: `tel:${phoneHref}` });
26
+ }
27
+ if (basics.contact.website) {
28
+ contactLinks.push({ label: formatUrlLabel(basics.contact.website), href: basics.contact.website });
29
+ }
30
+ for (const link of basics.links ?? []) {
31
+ contactLinks.push({ label: link.label ?? formatUrlLabel(link.url), href: link.url });
32
+ }
33
+
34
+ const headlineLine = [basics.headline, locationLabel].filter(Boolean).join(" · ");
35
+ const experienceItems = data.experience ?? [];
36
+ const projects = data.projects ?? [];
37
+ const skills = data.skills ?? [];
38
+ const education = data.education ?? [];
39
+ const certifications = data.certifications ?? [];
40
+ const showSkillsAs = data.settings?.showSkillsAs ?? "levels";
41
+ const declaration = data.declaration ?? {};
42
+ const hasDeclaration = Boolean(declaration.text || declaration.place || declaration.name);
43
+ ---
44
+
45
+ <style>
46
+ @media print {
47
+ .resume-template a {
48
+ text-decoration: none;
49
+ color: inherit;
50
+ }
51
+ .resume-template {
52
+ background: white;
53
+ }
54
+ }
55
+ </style>
56
+
57
+ <div class="resume-template bg-slate-100 text-slate-900 print:bg-white">
58
+ <main class="mx-auto max-w-4xl p-4 sm:p-6">
59
+ <section class="rounded-2xl bg-white p-8 shadow-sm ring-1 ring-slate-200 sm:p-12 print:shadow-none print:ring-0">
60
+ <header class="space-y-4">
61
+ <div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
62
+ <div>
63
+ <h1 class="text-4xl font-semibold tracking-tight">{basics.fullName}</h1>
64
+ {headlineLine && <p class="mt-2 text-sm text-slate-600">{headlineLine}</p>}
65
+ </div>
66
+ {contactLinks.length > 0 && (
67
+ <div class="text-sm text-slate-700 sm:text-right">
68
+ <div class="flex flex-col gap-1">
69
+ {contactLinks.map((item) => (
70
+ <a class="underline underline-offset-4 hover:text-slate-900" href={item.href}>
71
+ {item.label}
72
+ </a>
73
+ ))}
74
+ </div>
75
+ </div>
76
+ )}
77
+ </div>
78
+
79
+ {basics.summary && (
80
+ <p class="max-w-3xl text-sm leading-7 text-slate-700">{basics.summary}</p>
81
+ )}
82
+ </header>
83
+
84
+ <div class="my-10 h-px bg-slate-200"></div>
85
+
86
+ <div class="grid gap-10 lg:grid-cols-12">
87
+ <div class="space-y-10 lg:col-span-8">
88
+ {experienceItems.length > 0 && (
89
+ <section>
90
+ <h2 class="text-xs font-semibold tracking-[0.25em] text-slate-500">EXPERIENCE</h2>
91
+ <div class="mt-6 space-y-8">
92
+ {experienceItems.map((item) => (
93
+ <article>
94
+ <div class="flex items-start justify-between gap-4">
95
+ <div>
96
+ <h3 class="text-lg font-medium">{item.role}</h3>
97
+ <p class="text-sm text-slate-600">{item.company}</p>
98
+ </div>
99
+ <p class="text-sm text-slate-500">{formatDateRange(item)}</p>
100
+ </div>
101
+ {item.summary && (
102
+ <p class="mt-3 text-sm leading-7 text-slate-700">{item.summary}</p>
103
+ )}
104
+ {item.bullets && item.bullets.length > 0 && (
105
+ <ul class="mt-3 space-y-2 text-sm leading-7 text-slate-700">
106
+ {item.bullets.map((bullet) => (
107
+ <li>
108
+ <span class="font-medium">•</span> {bullet}
109
+ </li>
110
+ ))}
111
+ </ul>
112
+ )}
113
+ </article>
114
+ ))}
115
+ </div>
116
+ </section>
117
+ )}
118
+
119
+ {projects.length > 0 && (
120
+ <section>
121
+ <h2 class="text-xs font-semibold tracking-[0.25em] text-slate-500">PROJECTS</h2>
122
+ <div class="mt-6 space-y-5">
123
+ {projects.map((project) => (
124
+ <article class="rounded-xl border border-slate-200 p-5">
125
+ <div class="flex items-baseline justify-between gap-3">
126
+ {project.link ? (
127
+ <a class="text-base font-semibold underline underline-offset-4" href={project.link}>
128
+ {project.name}
129
+ </a>
130
+ ) : (
131
+ <h3 class="text-base font-semibold">{project.name}</h3>
132
+ )}
133
+ {project.start && (
134
+ <span class="text-xs text-slate-500">
135
+ {formatDateRange({
136
+ start: project.start,
137
+ end: project.end,
138
+ present: project.present,
139
+ })}
140
+ </span>
141
+ )}
142
+ </div>
143
+ {project.summary && (
144
+ <p class="mt-2 text-sm leading-7 text-slate-700">{project.summary}</p>
145
+ )}
146
+ {project.tags && project.tags.length > 0 && (
147
+ <div class="mt-3 flex flex-wrap gap-2">
148
+ {project.tags.map((tag) => (
149
+ <span class="rounded-full bg-slate-100 px-3 py-1 text-xs">{tag}</span>
150
+ ))}
151
+ </div>
152
+ )}
153
+ </article>
154
+ ))}
155
+ </div>
156
+ </section>
157
+ )}
158
+ </div>
159
+
160
+ <aside class="space-y-10 lg:col-span-4">
161
+ {skills.length > 0 && (
162
+ <section>
163
+ <h2 class="text-xs font-semibold tracking-[0.25em] text-slate-500">SKILLS</h2>
164
+ {showSkillsAs === "chips" ? (
165
+ <div class="mt-5 flex flex-wrap gap-2 text-sm text-slate-700">
166
+ {skills.map((skill) => (
167
+ <span class="rounded-full bg-slate-100 px-3 py-1 text-xs">{skill.name}</span>
168
+ ))}
169
+ </div>
170
+ ) : (
171
+ <div class="mt-5 space-y-3 text-sm text-slate-700">
172
+ {skills.map((skill) => (
173
+ <div class="flex items-center justify-between">
174
+ <span>{skill.name}</span>
175
+ {skill.level && <span class="text-slate-500">{skill.level}</span>}
176
+ </div>
177
+ ))}
178
+ </div>
179
+ )}
180
+ </section>
181
+ )}
182
+
183
+ {education.length > 0 && (
184
+ <section>
185
+ <h2 class="text-xs font-semibold tracking-[0.25em] text-slate-500">EDUCATION</h2>
186
+ <div class="mt-5 space-y-4 text-sm text-slate-700">
187
+ {education.map((edu) => (
188
+ <div>
189
+ <div class="font-medium">{edu.degree}</div>
190
+ <div class="text-slate-600">{edu.school}</div>
191
+ {edu.location && <div class="text-slate-500">{edu.location}</div>}
192
+ {edu.start && (
193
+ <div class="text-slate-500">
194
+ {formatDateRange({ start: edu.start, end: edu.end })}
195
+ </div>
196
+ )}
197
+ </div>
198
+ ))}
199
+ </div>
200
+ </section>
201
+ )}
202
+
203
+ {certifications.length > 0 && (
204
+ <section>
205
+ <h2 class="text-xs font-semibold tracking-[0.25em] text-slate-500">CERTIFICATIONS</h2>
206
+ <ul class="mt-5 space-y-2 text-sm leading-7 text-slate-700">
207
+ {certifications.map((cert) => (
208
+ <li>
209
+ • {cert.name}
210
+ {cert.issuer ? ` — ${cert.issuer}` : ""}
211
+ {cert.year ? ` (${cert.year})` : ""}
212
+ </li>
213
+ ))}
214
+ </ul>
215
+ </section>
216
+ )}
217
+ </aside>
218
+ </div>
219
+
220
+ {hasDeclaration && (
221
+ <>
222
+ <div class="my-10 h-px bg-slate-200"></div>
223
+ <section class="space-y-4">
224
+ <h2 class="text-xs font-semibold tracking-[0.25em] text-slate-500">DECLARATION</h2>
225
+ {declaration.text && (
226
+ <p class="text-sm leading-7 text-slate-700">{declaration.text}</p>
227
+ )}
228
+ {(declaration.place || declaration.name) && (
229
+ <div class="flex flex-wrap items-center justify-between gap-2 text-sm text-slate-700">
230
+ {declaration.place && (
231
+ <div>
232
+ <span class="font-semibold text-slate-900">Place:</span>{" "}
233
+ {declaration.place}
234
+ </div>
235
+ )}
236
+ {declaration.name && (
237
+ <div class="font-semibold text-slate-900">{declaration.name}</div>
238
+ )}
239
+ </div>
240
+ )}
241
+ </section>
242
+ </>
243
+ )}
244
+ </section>
245
+ </main>
246
+ </div>