This commit is contained in:
@ -101,9 +101,14 @@ export default function NavigationBar({ currentPath }: NavigationBarProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={item.id} class="mx-0.5 sm:mx-1">
|
<li key={item.id} class="mx-0.5 sm:mx-1">
|
||||||
<a href={item.path} class={isActive ? "menu-active" : ""}>
|
<a
|
||||||
<div class="tooltip" data-tip={item.tooltip}>
|
href={item.path}
|
||||||
|
class={`min-h-[44px] min-w-[44px] inline-flex items-center justify-center ${isActive ? "menu-active" : ""}`}
|
||||||
|
aria-label={item.tooltip}
|
||||||
|
>
|
||||||
|
<div class="tooltip md:before:-translate-x-1/2 md:before:-translate-y-full md:before:top-auto md:before:bottom-full md:after:-translate-x-1/2 md:after:-translate-y-full md:after:top-auto md:after:bottom-full" data-tip={item.tooltip}>
|
||||||
<Icon size={18} class="sm:w-5 sm:h-5" />
|
<Icon size={18} class="sm:w-5 sm:h-5" />
|
||||||
|
<span class="sr-only">{item.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -11,7 +11,7 @@ const { slug } = post;
|
|||||||
---
|
---
|
||||||
|
|
||||||
<div class="card bg-accent shadow-lg w-full sm:w-[calc(50%-1rem)] md:w-96 min-w-[280px] max-w-sm shrink">
|
<div class="card bg-accent shadow-lg w-full sm:w-[calc(50%-1rem)] md:w-96 min-w-[280px] max-w-sm shrink">
|
||||||
<div class="card-body p-3">
|
<div class="card-body p-3 break-words">
|
||||||
<h2 class="card-title text-base-100 justify-center text-center break-words font-bold text-lg mb-2">
|
<h2 class="card-title text-base-100 justify-center text-center break-words font-bold text-lg mb-2">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -10,7 +10,7 @@ const { project } = Astro.props;
|
|||||||
---
|
---
|
||||||
|
|
||||||
<div class="card bg-accent shadow-lg w-full sm:w-[calc(50%-1rem)] md:w-96 min-w-[280px] max-w-sm shrink">
|
<div class="card bg-accent shadow-lg w-full sm:w-[calc(50%-1rem)] md:w-96 min-w-[280px] max-w-sm shrink">
|
||||||
<div class="card-body p-6">
|
<div class="card-body p-6 break-words">
|
||||||
<h2 class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words text-base-100">
|
<h2 class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words text-base-100">
|
||||||
{project.name}
|
{project.name}
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -21,7 +21,6 @@ export default function ResumeSkills({ skills }: ResumeSkillsProps) {
|
|||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
if (entry.isIntersecting && !hasAnimated.value) {
|
if (entry.isIntersecting && !hasAnimated.value) {
|
||||||
hasAnimated.value = true;
|
hasAnimated.value = true;
|
||||||
// Start animation for all skills
|
|
||||||
skills.forEach((skill) => {
|
skills.forEach((skill) => {
|
||||||
animateSkill(skill.id, skill.level);
|
animateSkill(skill.id, skill.level);
|
||||||
});
|
});
|
||||||
@ -67,19 +66,23 @@ export default function ResumeSkills({ skills }: ResumeSkillsProps) {
|
|||||||
<div id="skills-section" class="grid grid-cols-1 md:grid-cols-2 gap-3 sm:gap-4">
|
<div id="skills-section" class="grid grid-cols-1 md:grid-cols-2 gap-3 sm:gap-4">
|
||||||
{skills.map((skill) => {
|
{skills.map((skill) => {
|
||||||
const currentLevel = animatedLevels.value[skill.id] || 0;
|
const currentLevel = animatedLevels.value[skill.id] || 0;
|
||||||
const progressValue = currentLevel * 20; // Convert 1-5 scale to 0-100
|
const progressValue = currentLevel * 20;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={skill.id}>
|
<div key={skill.id}>
|
||||||
<label class="label p-1 sm:p-2">
|
<div class="flex justify-between items-center p-1 sm:p-2">
|
||||||
<span class="label-text text-sm sm:text-base">
|
<span class="text-sm sm:text-base font-medium">
|
||||||
{skill.name}
|
{skill.name}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
<span class="text-xs sm:text-sm text-base-content/70">
|
||||||
|
{Math.round(currentLevel)}/5
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<progress
|
<progress
|
||||||
class="progress progress-primary w-full h-2 sm:h-3 transition-all duration-100 ease-out"
|
class="progress progress-primary w-full h-2 sm:h-3 min-h-2 transition-all duration-100 ease-out"
|
||||||
value={progressValue}
|
value={progressValue}
|
||||||
max="100"
|
max="100"
|
||||||
|
aria-label={`${skill.name} skill level: ${Math.round(currentLevel)} out of 5`}
|
||||||
></progress>
|
></progress>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -31,7 +31,7 @@ export default function ScrollUpButton() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={scrollToTop}
|
onClick={scrollToTop}
|
||||||
class={`fixed bottom-20 right-4 z-20 bg-secondary hover:bg-primary
|
class={`fixed bottom-20 right-4 z-20 bg-secondary hover:bg-primary
|
||||||
p-3 rounded-full shadow-lg transition-all duration-300
|
p-3 rounded-full shadow-lg transition-all duration-300 min-h-[44px] min-w-[44px] inline-flex items-center justify-center
|
||||||
${
|
${
|
||||||
isVisible.value
|
isVisible.value
|
||||||
? "opacity-70 translate-y-0"
|
? "opacity-70 translate-y-0"
|
||||||
|
@ -12,7 +12,7 @@ const { talk } = Astro.props;
|
|||||||
<div
|
<div
|
||||||
class="card bg-accent shadow-lg w-full sm:w-[calc(50%-1rem)] md:w-96 min-w-[280px] max-w-sm shrink"
|
class="card bg-accent shadow-lg w-full sm:w-[calc(50%-1rem)] md:w-96 min-w-[280px] max-w-sm shrink"
|
||||||
>
|
>
|
||||||
<div class="card-body p-6">
|
<div class="card-body p-6 break-words">
|
||||||
<h2
|
<h2
|
||||||
class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words text-base-100"
|
class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words text-base-100"
|
||||||
>
|
>
|
||||||
|
@ -36,12 +36,6 @@ const Terminal = () => {
|
|||||||
}
|
}
|
||||||
}, [commandHistory]);
|
}, [commandHistory]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (inputRef.current) {
|
|
||||||
inputRef.current.focus();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Load command history from localStorage
|
// Load command history from localStorage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const history = loadCommandHistory();
|
const history = loadCommandHistory();
|
||||||
@ -219,7 +213,6 @@ __/ =| o |=-O=====O=====O=====O \\ ____Y___________|__|_________________________
|
|||||||
}
|
}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
className="flex-1 bg-transparent border-none outline-none text-accent ml-1"
|
className="flex-1 bg-transparent border-none outline-none text-accent ml-1"
|
||||||
autoFocus
|
|
||||||
spellcheck={false}
|
spellcheck={false}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
@ -289,7 +289,7 @@ export const navigationItems: NavigationItem[] = [
|
|||||||
path: "/resume",
|
path: "/resume",
|
||||||
tooltip: "Resume",
|
tooltip: "Resume",
|
||||||
icon: BriefcaseBusiness,
|
icon: BriefcaseBusiness,
|
||||||
enabled: true
|
enabled: !!(resumeConfig.jsonFile && resumeConfig.jsonFile.trim())
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "projects",
|
id: "projects",
|
||||||
|
@ -29,7 +29,7 @@ const pageDescription = description || siteConfig.meta.description;
|
|||||||
<title>{pageTitle}</title>
|
<title>{pageTitle}</title>
|
||||||
<ClientRouter />
|
<ClientRouter />
|
||||||
</head>
|
</head>
|
||||||
<body class="flex flex-col min-h-screen">
|
<body class="flex flex-col min-h-screen overflow-x-hidden">
|
||||||
<main class="flex-grow flex flex-col gap-4 items-center justify-center pb-24 sm:pb-20">
|
<main class="flex-grow flex flex-col gap-4 items-center justify-center pb-24 sm:pb-20">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
@ -12,6 +12,7 @@ import { personalInfo, homepageSections } from "../config/data";
|
|||||||
alt={personalInfo.profileImage.alt}
|
alt={personalInfo.profileImage.alt}
|
||||||
height={personalInfo.profileImage.height}
|
height={personalInfo.profileImage.height}
|
||||||
width={personalInfo.profileImage.width}
|
width={personalInfo.profileImage.width}
|
||||||
|
loading="eager"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h1
|
<h1
|
||||||
|
@ -63,11 +63,16 @@ interface ResumeData {
|
|||||||
let resumeData: ResumeData | undefined = undefined;
|
let resumeData: ResumeData | undefined = undefined;
|
||||||
let fetchError: string | null = null;
|
let fetchError: string | null = null;
|
||||||
|
|
||||||
|
// Check if resume JSON file is configured before attempting to fetch
|
||||||
|
if (!siteConfig.resume.jsonFile || !siteConfig.resume.jsonFile.trim()) {
|
||||||
|
return Astro.redirect('/');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the base URL for the current request
|
// Get the base URL
|
||||||
const baseUrl = Astro.url.origin;
|
const baseUrl = Astro.url.origin;
|
||||||
|
|
||||||
// Fetch the JSON file from the public directory using config
|
// Fetch the JSON file from the public directory
|
||||||
const response = await fetch(`${baseUrl}${siteConfig.resume.jsonFile}`);
|
const response = await fetch(`${baseUrl}${siteConfig.resume.jsonFile}`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -91,287 +96,276 @@ try {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading resume data:", error);
|
console.error("Error loading resume data:", error);
|
||||||
fetchError =
|
// Return to home page when resume data cannot be loaded
|
||||||
"Unable to load resume data. Please make sure the resume.json file exists in /public/files/";
|
return Astro.redirect('/');
|
||||||
resumeData = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = resumeData;
|
const data = resumeData;
|
||||||
const resumeConfig = siteConfig.resume;
|
const resumeConfig = siteConfig.resume;
|
||||||
|
|
||||||
|
// At this point, data is guaranteed to exist since we redirect on error
|
||||||
|
if (!data) {
|
||||||
|
return Astro.redirect('/');
|
||||||
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
{
|
<Layout title="Resume">
|
||||||
(!data || fetchError) && (
|
<div class="container mx-auto p-4 sm:p-6 lg:p-8 max-w-4xl w-full">
|
||||||
<Layout title="Resume">
|
<h1 class="text-3xl sm:text-4xl font-bold mb-4 sm:mb-6 text-center">
|
||||||
<div class="container mx-auto p-4 sm:p-6 lg:p-8 max-w-4xl text-center w-full">
|
{data.basics.name}
|
||||||
<h1 class="text-2xl font-bold text-red-600">
|
</h1>
|
||||||
Error loading resume data.
|
|
||||||
</h1>
|
<div class="flex justify-center items-center flex-wrap gap-x-3 sm:gap-x-4 gap-y-2 mb-4 sm:mb-6">
|
||||||
<p>{fetchError || "Please try refreshing the page."}</p>
|
{data.basics.email && (
|
||||||
|
<a
|
||||||
|
href={`mailto:${data.basics.email}`}
|
||||||
|
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:email" /> {data.basics.email}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{data.sections.profiles.items.find(
|
||||||
|
(p) => p.network === "GitHub",
|
||||||
|
) && (
|
||||||
|
<a
|
||||||
|
href={
|
||||||
|
data.sections.profiles.items.find(
|
||||||
|
(p) => p.network === "GitHub",
|
||||||
|
)!.url.href
|
||||||
|
}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
|
||||||
|
>
|
||||||
|
<Icon name="simple-icons:github" /> GitHub
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{data.sections.profiles.items.find(
|
||||||
|
(p) => p.network === "linkedin",
|
||||||
|
) && (
|
||||||
|
<a
|
||||||
|
href={
|
||||||
|
data.sections.profiles.items.find(
|
||||||
|
(p) => p.network === "linkedin",
|
||||||
|
)!.url.href
|
||||||
|
}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
|
||||||
|
>
|
||||||
|
<Icon name="simple-icons:linkedin" /> LinkedIn
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{resumeConfig.pdfFile?.path && (
|
||||||
|
<div class="text-center mb-6 sm:mb-8">
|
||||||
|
<a
|
||||||
|
href={resumeConfig.pdfFile.path}
|
||||||
|
download={resumeConfig.pdfFile.filename}
|
||||||
|
class="btn btn-primary inline-flex items-center gap-2 text-sm sm:text-base"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:download" /> {resumeConfig.pdfFile.displayText}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
)}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{data.sections.summary && resumeConfig.sections.summary?.enabled && (
|
||||||
data && !fetchError && (
|
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||||
<Layout title="Resume">
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
<div class="container mx-auto p-4 sm:p-6 lg:p-8 max-w-4xl w-full">
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
<h1 class="text-3xl sm:text-4xl font-bold mb-4 sm:mb-6 text-center">
|
{resumeConfig.sections.summary.title || data.sections.summary.name || "Summary"}
|
||||||
{data.basics.name}
|
</h2>
|
||||||
</h1>
|
<div set:html={data.sections.summary.content} />
|
||||||
|
|
||||||
<div class="flex justify-center items-center flex-wrap gap-x-3 sm:gap-x-4 gap-y-2 mb-4 sm:mb-6">
|
|
||||||
{data.basics.email && (
|
|
||||||
<a
|
|
||||||
href={`mailto:${data.basics.email}`}
|
|
||||||
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
|
|
||||||
>
|
|
||||||
<Icon name="mdi:email" /> {data.basics.email}
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
{data.sections.profiles.items.find(
|
|
||||||
(p) => p.network === "GitHub",
|
|
||||||
) && (
|
|
||||||
<a
|
|
||||||
href={
|
|
||||||
data.sections.profiles.items.find(
|
|
||||||
(p) => p.network === "GitHub",
|
|
||||||
)!.url.href
|
|
||||||
}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
|
|
||||||
>
|
|
||||||
<Icon name="simple-icons:github" /> GitHub
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
{data.sections.profiles.items.find(
|
|
||||||
(p) => p.network === "linkedin",
|
|
||||||
) && (
|
|
||||||
<a
|
|
||||||
href={
|
|
||||||
data.sections.profiles.items.find(
|
|
||||||
(p) => p.network === "linkedin",
|
|
||||||
)!.url.href
|
|
||||||
}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
|
|
||||||
>
|
|
||||||
<Icon name="simple-icons:linkedin" /> LinkedIn
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div class="text-center mb-6 sm:mb-8">
|
{data.sections.profiles &&
|
||||||
<a
|
data.sections.profiles.items &&
|
||||||
href={resumeConfig.pdfFile.path}
|
data.sections.profiles.items.length > 0 &&
|
||||||
download={resumeConfig.pdfFile.filename}
|
resumeConfig.sections.profiles?.enabled && (
|
||||||
class="btn btn-primary inline-flex items-center gap-2 text-sm sm:text-base"
|
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||||
>
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
<Icon name="mdi:download" /> {resumeConfig.pdfFile.displayText}
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
</a>
|
{resumeConfig.sections.profiles.title || data.sections.profiles.name || "Profiles"}
|
||||||
</div>
|
</h2>
|
||||||
|
<div class="flex flex-wrap gap-3 sm:gap-4">
|
||||||
|
{data.sections.profiles.items.map(
|
||||||
|
(profile) => {
|
||||||
|
let iconName = "mdi:web";
|
||||||
|
const networkLower =
|
||||||
|
profile.network.toLowerCase();
|
||||||
|
if (networkLower === "github") {
|
||||||
|
iconName = "simple-icons:github";
|
||||||
|
} else if (
|
||||||
|
networkLower === "linkedin"
|
||||||
|
) {
|
||||||
|
iconName =
|
||||||
|
"simple-icons:linkedin";
|
||||||
|
} else if (
|
||||||
|
networkLower === "twitter"
|
||||||
|
) {
|
||||||
|
iconName = "simple-icons:x";
|
||||||
|
} else if (
|
||||||
|
networkLower === "youtube"
|
||||||
|
) {
|
||||||
|
iconName =
|
||||||
|
"simple-icons:youtube";
|
||||||
|
}
|
||||||
|
|
||||||
{data.sections.summary && resumeConfig.sections.summary?.enabled && (
|
return (
|
||||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
<a
|
||||||
<div class="card-body p-4 sm:p-6">
|
href={profile.url.href}
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
target="_blank"
|
||||||
{resumeConfig.sections.summary.title || data.sections.summary.name || "Summary"}
|
rel="noopener noreferrer"
|
||||||
</h2>
|
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
|
||||||
<div set:html={data.sections.summary.content} />
|
>
|
||||||
|
<Icon name={iconName} />
|
||||||
|
{profile.network}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{data.sections.profiles &&
|
{data.sections.skills &&
|
||||||
data.sections.profiles.items &&
|
data.sections.skills.items &&
|
||||||
data.sections.profiles.items.length > 0 &&
|
data.sections.skills.items.length > 0 &&
|
||||||
resumeConfig.sections.profiles?.enabled && (
|
resumeConfig.sections.skills?.enabled && (
|
||||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||||
<div class="card-body p-4 sm:p-6">
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
{resumeConfig.sections.profiles.title || data.sections.profiles.name || "Profiles"}
|
{resumeConfig.sections.skills.title || data.sections.skills.name || "Skills"}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex flex-wrap gap-3 sm:gap-4">
|
<ResumeSkills
|
||||||
{data.sections.profiles.items.map(
|
skills={data.sections.skills.items}
|
||||||
(profile) => {
|
client:load
|
||||||
let iconName = "mdi:web";
|
/>
|
||||||
const networkLower =
|
</div>
|
||||||
profile.network.toLowerCase();
|
</div>
|
||||||
if (networkLower === "github") {
|
)}
|
||||||
iconName = "simple-icons:github";
|
|
||||||
} else if (
|
|
||||||
networkLower === "linkedin"
|
|
||||||
) {
|
|
||||||
iconName =
|
|
||||||
"simple-icons:linkedin";
|
|
||||||
} else if (
|
|
||||||
networkLower === "twitter"
|
|
||||||
) {
|
|
||||||
iconName = "simple-icons:x";
|
|
||||||
} else if (
|
|
||||||
networkLower === "youtube"
|
|
||||||
) {
|
|
||||||
iconName =
|
|
||||||
"simple-icons:youtube";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
{data.sections.experience &&
|
||||||
<a
|
data.sections.experience.items &&
|
||||||
href={profile.url.href}
|
data.sections.experience.items.length > 0 &&
|
||||||
target="_blank"
|
resumeConfig.sections.experience?.enabled && (
|
||||||
rel="noopener noreferrer"
|
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||||
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
>
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
<Icon name={iconName} />
|
{resumeConfig.sections.experience.title || data.sections.experience.name || "Experience"}
|
||||||
{profile.network}
|
</h2>
|
||||||
</a>
|
<div class="space-y-4 sm:space-y-6">
|
||||||
);
|
{data.sections.experience.items.map(
|
||||||
},
|
(experience) => (
|
||||||
)}
|
<div class="border-l-2 border-primary pl-4 sm:pl-6">
|
||||||
</div>
|
<h3 class="text-lg sm:text-xl font-semibold">
|
||||||
</div>
|
{experience.position}
|
||||||
|
</h3>
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-4 text-sm sm:text-base text-base-content/70 mb-2">
|
||||||
|
<span class="font-medium">
|
||||||
|
{experience.company}
|
||||||
|
</span>
|
||||||
|
<span>{experience.date}</span>
|
||||||
|
<span>
|
||||||
|
{experience.location}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="prose prose-sm sm:prose-base max-w-none"
|
||||||
|
set:html={experience.summary}
|
||||||
|
/>
|
||||||
|
{experience.url && experience.url.href && (
|
||||||
|
<a
|
||||||
|
href={experience.url.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center gap-1 text-primary hover:text-primary-focus text-sm mt-2"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:link" />
|
||||||
|
Company Website
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{data.sections.skills &&
|
{data.sections.education &&
|
||||||
data.sections.skills.items &&
|
data.sections.education.items &&
|
||||||
data.sections.skills.items.length > 0 &&
|
data.sections.education.items.length > 0 &&
|
||||||
resumeConfig.sections.skills?.enabled && (
|
resumeConfig.sections.education?.enabled && (
|
||||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||||
<div class="card-body p-4 sm:p-6">
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
{resumeConfig.sections.skills.title || data.sections.skills.name || "Skills"}
|
{resumeConfig.sections.education.title || data.sections.education.name || "Education"}
|
||||||
</h2>
|
</h2>
|
||||||
<ResumeSkills
|
<div class="space-y-4">
|
||||||
skills={data.sections.skills.items}
|
{data.sections.education.items.map(
|
||||||
client:load
|
(education) => (
|
||||||
/>
|
<div class="border-l-2 border-secondary pl-4 sm:pl-6">
|
||||||
</div>
|
<h3 class="text-lg sm:text-xl font-semibold">
|
||||||
|
{education.institution}
|
||||||
|
</h3>
|
||||||
|
<div class="text-sm sm:text-base text-base-content/70 mb-2">
|
||||||
|
<span class="font-medium">
|
||||||
|
{education.studyType} in{" "}
|
||||||
|
{education.area}
|
||||||
|
</span>
|
||||||
|
<span class="block sm:inline sm:ml-4">
|
||||||
|
{education.date}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{education.summary && (
|
||||||
|
<div
|
||||||
|
class="prose prose-sm sm:prose-base max-w-none"
|
||||||
|
set:html={education.summary}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{data.sections.experience &&
|
{data.sections.volunteer &&
|
||||||
data.sections.experience.items &&
|
data.sections.volunteer.items &&
|
||||||
data.sections.experience.items.length > 0 &&
|
data.sections.volunteer.items.length > 0 &&
|
||||||
resumeConfig.sections.experience?.enabled && (
|
resumeConfig.sections.volunteer?.enabled && (
|
||||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||||
<div class="card-body p-4 sm:p-6">
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
{resumeConfig.sections.experience.title || data.sections.experience.name || "Experience"}
|
{resumeConfig.sections.volunteer.title || data.sections.volunteer.name || "Volunteer Work"}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4 sm:space-y-6">
|
<div class="space-y-4">
|
||||||
{data.sections.experience.items.map(
|
{data.sections.volunteer.items.map(
|
||||||
(experience) => (
|
(volunteer) => (
|
||||||
<div class="border-l-2 border-primary pl-4 sm:pl-6">
|
<div class="border-l-2 border-accent pl-4 sm:pl-6">
|
||||||
<h3 class="text-lg sm:text-xl font-semibold">
|
<h3 class="text-lg sm:text-xl font-semibold">
|
||||||
{experience.position}
|
{volunteer.organization}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-4 text-sm sm:text-base text-base-content/70 mb-2">
|
<div class="text-sm sm:text-base text-base-content/70 mb-2">
|
||||||
<span class="font-medium">
|
<span class="font-medium">
|
||||||
{experience.company}
|
{volunteer.position}
|
||||||
</span>
|
</span>
|
||||||
<span>{experience.date}</span>
|
<span class="block sm:inline sm:ml-4">
|
||||||
<span>
|
{volunteer.date}
|
||||||
{experience.location}
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
),
|
||||||
class="prose prose-sm sm:prose-base max-w-none"
|
)}
|
||||||
set:html={experience.summary}
|
|
||||||
/>
|
|
||||||
{experience.url && experience.url.href && (
|
|
||||||
<a
|
|
||||||
href={experience.url.href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="inline-flex items-center gap-1 text-primary hover:text-primary-focus text-sm mt-2"
|
|
||||||
>
|
|
||||||
<Icon name="mdi:link" />
|
|
||||||
Company Website
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
</div>
|
||||||
{data.sections.education &&
|
)}
|
||||||
data.sections.education.items &&
|
</div>
|
||||||
data.sections.education.items.length > 0 &&
|
</Layout>
|
||||||
resumeConfig.sections.education?.enabled && (
|
|
||||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
|
||||||
<div class="card-body p-4 sm:p-6">
|
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
|
||||||
{resumeConfig.sections.education.title || data.sections.education.name || "Education"}
|
|
||||||
</h2>
|
|
||||||
<div class="space-y-4">
|
|
||||||
{data.sections.education.items.map(
|
|
||||||
(education) => (
|
|
||||||
<div class="border-l-2 border-secondary pl-4 sm:pl-6">
|
|
||||||
<h3 class="text-lg sm:text-xl font-semibold">
|
|
||||||
{education.institution}
|
|
||||||
</h3>
|
|
||||||
<div class="text-sm sm:text-base text-base-content/70 mb-2">
|
|
||||||
<span class="font-medium">
|
|
||||||
{education.studyType} in{" "}
|
|
||||||
{education.area}
|
|
||||||
</span>
|
|
||||||
<span class="block sm:inline sm:ml-4">
|
|
||||||
{education.date}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{education.summary && (
|
|
||||||
<div
|
|
||||||
class="prose prose-sm sm:prose-base max-w-none"
|
|
||||||
set:html={education.summary}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{data.sections.volunteer &&
|
|
||||||
data.sections.volunteer.items &&
|
|
||||||
data.sections.volunteer.items.length > 0 &&
|
|
||||||
resumeConfig.sections.volunteer?.enabled && (
|
|
||||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
|
||||||
<div class="card-body p-4 sm:p-6">
|
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
|
||||||
{resumeConfig.sections.volunteer.title || data.sections.volunteer.name || "Volunteer Work"}
|
|
||||||
</h2>
|
|
||||||
<div class="space-y-4">
|
|
||||||
{data.sections.volunteer.items.map(
|
|
||||||
(volunteer) => (
|
|
||||||
<div class="border-l-2 border-accent pl-4 sm:pl-6">
|
|
||||||
<h3 class="text-lg sm:text-xl font-semibold">
|
|
||||||
{volunteer.organization}
|
|
||||||
</h3>
|
|
||||||
<div class="text-sm sm:text-base text-base-content/70 mb-2">
|
|
||||||
<span class="font-medium">
|
|
||||||
{volunteer.position}
|
|
||||||
</span>
|
|
||||||
<span class="block sm:inline sm:ml-4">
|
|
||||||
{volunteer.date}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -34,52 +34,4 @@
|
|||||||
--border: 1px;
|
--border: 1px;
|
||||||
--depth: 1;
|
--depth: 1;
|
||||||
--noise: 1;
|
--noise: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure better text scaling and overflow handling */
|
|
||||||
* {
|
|
||||||
/* Allow text to scale with user preferences */
|
|
||||||
text-size-adjust: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prevent horizontal overflow on smaller screens or when zoomed */
|
|
||||||
body {
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure links and buttons remain accessible at all zoom levels */
|
|
||||||
a, button {
|
|
||||||
min-height: 44px;
|
|
||||||
min-width: 44px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make sure card content doesn't overflow */
|
|
||||||
.card-body {
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure progress bars scale properly */
|
|
||||||
.progress {
|
|
||||||
min-height: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive navigation improvements */
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.menu-horizontal .menu li {
|
|
||||||
margin: 0 0.125rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Better tooltip positioning for mobile */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.tooltip:before,
|
|
||||||
.tooltip:after {
|
|
||||||
transform: translateX(-50%) translateY(-100%);
|
|
||||||
top: auto;
|
|
||||||
bottom: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user