Manyue-DataScientist's picture
updated code for pdf viewer
2c93f53
raw
history blame
48.5 kB
import gradio as gr
import base64
import os
import json
# --- Helper Functions ---
def load_json(filename):
"""Load JSON data from content folder"""
try:
with open(f"content/{filename}.json", "r", encoding="utf-8") as f:
return json.load(f)
except Exception as e:
print(f"Error loading {filename}.json: {e}")
return {}
def file_to_data_uri(filepath, mime_type="application/pdf"):
"""Convert file to data URI"""
try:
with open(filepath, "rb") as f:
data = f.read()
b64 = base64.b64encode(data).decode("utf-8")
return f"data:{mime_type};base64,{b64}"
except Exception as e:
print(f"Error converting file to data URI: {e}")
return None
def image_to_data_uri(filepath, mime_type="image/jpeg"):
"""Convert image to data URI"""
try:
with open(filepath, "rb") as f:
data = f.read()
b64 = base64.b64encode(data).decode("utf-8")
return f"data:{mime_type};base64,{b64}"
except Exception as e:
print(f"Error converting image to data URI: {e}")
return None
# --- Navigation Functions ---
def show_data_analytics():
return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
def show_machine_learning():
return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
def show_computer_vision():
return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
def go_home():
return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
def toggle_resume(is_visible):
"""Toggle the visibility of the resume section."""
new_state = not is_visible
new_label = "Hide Resume" if new_state else "View Resume"
return new_state, gr.update(visible=new_state), gr.update(value=new_label)
# --- Icons (SVG) ---
data_analytics_icon = """<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path><path d="M8 10h.01"></path><path d="M12 10h.01"></path><path d="M16 10h.01"></path></svg>"""
machine_learning_icon = """<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>"""
computer_vision_icon = """<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path><circle cx="12" cy="13" r="4"></circle></svg>"""
home_icon = """<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>"""
linkedin_icon = """<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>"""
github_icon = """<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>"""
mail_icon = """<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>"""
link_icon = """<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>"""
document_icon = """<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><line x1="10" y1="9" x2="8" y2="9"></line></svg>"""
# Dictionary for icon access
icons = {
"data_analytics_icon": data_analytics_icon,
"machine_learning_icon": machine_learning_icon,
"computer_vision_icon": computer_vision_icon,
"home_icon": home_icon,
"linkedin_icon": linkedin_icon,
"github_icon": github_icon,
"mail_icon": mail_icon,
"link_icon": link_icon,
"document_icon": document_icon
}
# --- Helper functions for generating HTML ---
def generate_profile_html():
"""Generate HTML for the profile section"""
try:
profile_img_uri = image_to_data_uri(profile_data.get("photo", "data/My_photo.jpeg"))
# Skills HTML
skills_html = ""
for skill in profile_data.get("skills", []):
skills_html += f'<div class="skill-pill">{skill}</div>\n'
# Social links HTML
social_links_html = ""
for link in profile_data.get("social_links", []):
icon = icons.get(link.get("icon", ""), "")
id_attr = f' id="{link["id"]}"' if "id" in link else ""
social_links_html += f'''
<a href="{link["url"]}" target="_blank" class="social-button social-{link["name"].lower()}" aria-label="{link.get("aria_label", link["name"])}"{ id_attr }>
{ icon }
</a>
'''
return f'''
<div class="profile-container">
<div class="profile-pic">
<img src="{profile_img_uri}" alt="{profile_data.get("name", "Profile")}" />
</div>
<div class="name-text">{profile_data.get("name", "")}</div>
</div>
<h2>{profile_data.get("title", "")}</h2>
<div class="about-text">
{profile_data.get("about", "")}
</div>
<div class="skills-container">
{skills_html}
</div>
<div class="social-links">
{social_links_html}
</div>
'''
except Exception as e:
# Fallback if error occurs
print(f"Error generating profile HTML: {e}")
return f'''
<div class="profile-container">
<div class="profile-pic">
<img src="/api/placeholder/400/400" alt="Profile" />
</div>
<div class="name-text">{profile_data.get("name", "Name")}</div>
</div>
<h2>{profile_data.get("title", "Title")}</h2>
<div class="about-text">
{profile_data.get("about", "About text")}</div>
<div class="skills-container">
{', '.join(profile_data.get("skills", ["Skills"]))}
</div>
'''
def generate_resume_html():
"""Generate HTML for the collapsible resume section"""
return '''
<div class="resume-section glass-container">
<button class="resume-toggle-button" onclick="toggleResume()">View Resume</button>
<div id="resume-content" class="resume-content" style="display: none;">
<p>Download my resume or view it below:</p>
<a id="resume-download-link" class="resume-download-button" target="_blank">Download Resume</a>
<div class="resume-preview">
<iframe id="resume-iframe" frameborder="0" class="resume-iframe"></iframe>
</div>
</div>
</div>
<script>
function toggleResume() {
const resumeContent = document.getElementById('resume-content');
const isHidden = resumeContent.style.display === 'none';
resumeContent.style.display = isHidden ? 'block' : 'none';
if (isHidden) {
const resumeIframe = document.getElementById('resume-iframe');
const resumeDownloadLink = document.getElementById('resume-download-link');
fetch('/file/data/resume.pdf')
.then(response => response.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
resumeIframe.src = url;
resumeDownloadLink.href = url;
})
.catch(err => console.error('Error loading resume:', err));
}
}
</script>
'''
def generate_cards_html():
"""Generate HTML for the specialization cards"""
cards_html = ""
for card in sections_data.get("cards", []):
icon = icons.get(card.get("icon", ""), "")
cards_html += f'''
<div class="card-container {card.get("class", "")}">
<div class="card-inner">
<div class="card-content">
{icon}
<span>{card.get("title", "")}</span>
</div>
<div class="card-description">
{card.get("description", "")}
</div>
</div>
</div>
'''
return cards_html
def generate_contact_html():
"""Generate HTML for the contact section"""
contact = profile_data.get("contact", {})
footer = profile_data.get("footer", {})
return f'''
<!-- Contact section -->
<div id="contact_section">
<h2>{contact.get("heading", "Contact Me")}</h2>
<div class="contact-container">
<p>{contact.get("text", "")}</p>
<a href="mailto:{contact.get("email", "")}" class="hire-me-button">{contact.get("button_text", "Contact")}</a>
</div>
</div>
<!-- Footer -->
<div class="footer">
<p>{footer.get("copyright", "")}</p>
<p>{footer.get("credits", "")}</p>
</div>
'''
def generate_skills_html(skills_data, section_class):
"""Generate HTML for skills in a section"""
skills_html = ""
for skill_category in skills_data:
items_html = ""
for item in skill_category.get("items", []):
items_html += f"<li>{item}</li>\n"
skills_html += f'''
<div class="skill-category">
<h4>{skill_category.get("category", "")}</h4>
<ul>
{items_html}
</ul>
</div>
'''
return skills_html
def generate_projects_html(projects_data, section_class):
"""Generate HTML for projects in a section"""
projects_html = ""
for project in projects_data:
projects_html += f'''
<div class="project-card">
<div class="project-title">
<span class="project-title-text">{project.get("title", "")}</span>
<a href="{project.get("url", "#")}" target="_blank" class="project-link">
{icons.get("link_icon", "")}
<span>View Project</span>
</a>
</div>
<div class="project-description">
{project.get("description", "")}
<span class="tech-stack"><strong>Tech Stack:</strong> {project.get("tech_stack", "")}</span>
</div>
</div>
'''
return projects_html
def generate_section_html(section_data, section_class):
"""Generate HTML for a complete section"""
skills_html = generate_skills_html(section_data.get("skills", []), section_class)
projects_html = generate_projects_html(section_data.get("projects", []), section_class)
return f'''
<h1 class="section-heading">{section_data.get("heading", "")}</h1>
<div class="section-intro">
{section_data.get("intro", "")}
</div>
<h3 class="section-subheading {section_class}">Skills</h3>
<div class="skills-list">
{skills_html}
</div>
<h3 class="section-subheading {section_class}">Projects</h3>
{projects_html}
'''
# --- CSS ---
portfolio_css = """
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&family=Montserrat:wght@700;800&display=swap');
:root {
--primary-da: #8a2be2;
--secondary-da: #2575fc;
--primary-ml: #00b4db;
--secondary-ml: #0083b0;
--primary-cv: #ff4d7e;
--secondary-cv: #fd3e58;
--dark-bg: #0f1118;
--card-bg: #1a1d29;
--text-primary: #ffffff;
--text-secondary: #e0e0e0;
--text-muted: #a0a0a0;
--shadow-sm: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-md: 0 8px 16px rgba(0, 0, 0, 0.2);
--shadow-lg: 0 12px 24px rgba(0, 0, 0, 0.2);
--border-radius-sm: 8px;
--border-radius-md: 12px;
--border-radius-lg: 20px;
--transition-fast: 0.2s ease;
--transition-med: 0.3s ease;
--transition-slow: 0.5s ease;
}
body {
font-family: 'Poppins', sans-serif;
background: var(--dark-bg);
background-image:
radial-gradient(circle at 25% 25%, rgba(53, 53, 113, 0.05) 0%, transparent 50%),
radial-gradient(circle at 75% 75%, rgba(113, 53, 53, 0.05) 0%, transparent 50%);
color: var(--text-primary);
margin: 0;
padding: 0;
overflow-x: hidden;
line-height: 1.6;
letter-spacing: 0.5px;
font-weight: 400;
}
.gr-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
/* Landing section */
.landing-section {
text-align: center;
margin-bottom: 60px;
padding: 40px 20px;
position: relative;
}
.landing-section:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 500px;
background: linear-gradient(180deg, rgba(0,0,0,0.7) 0%, transparent 100%);
z-index: -1;
}
.landing-section h1, .landing-section h2 {
color: var(--text-primary) !important;
margin-top: 0;
}
.landing-section h1 {
font-family: 'Montserrat', sans-serif;
font-size: 3.2rem;
font-weight: 800;
margin-bottom: 0.5rem;
background: linear-gradient(90deg, var(--primary-da), var(--primary-ml), var(--primary-cv));
-webkit-background-clip: text;
background-clip: text;
color: transparent !important;
letter-spacing: -0.5px;
}
.landing-section h2 {
font-size: 2rem;
font-weight: 600;
margin-bottom: 1.5rem;
}
.profile-container {
margin: 30px auto;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.profile-pic {
width: 180px;
height: 180px;
border-radius: 50%;
object-fit: cover;
border: 4px solid rgba(255, 255, 255, 0.2);
box-shadow: var(--shadow-md);
margin-bottom: 20px;
position: relative;
background: linear-gradient(45deg, var(--primary-da), var(--primary-ml), var(--primary-cv));
padding: 4px;
}
.profile-pic img {
border-radius: 50%;
width: 100%;
height: 100%;
object-fit: cover;
}
.name-text {
font-size: 2.6rem;
font-weight: 700;
margin-top: 10px;
margin-bottom: 10px;
}
@keyframes float {
0% { transform: translateY(0px) }
50% { transform: translateY(-10px) }
100% { transform: translateY(0px) }
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.about-text {
max-width: 800px;
margin: 0 auto 40px;
font-size: 1.25rem;
line-height: 1.6;
color: var(--text-secondary);
}
.skills-container {
margin-top: 20px;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-bottom: 40px;
}
.skill-pill {
background: rgba(255, 255, 255, 0.1);
padding: 8px 16px;
border-radius: 30px;
font-size: 0.9rem;
font-weight: 500;
color: var(--text-secondary);
}
.social-links {
display: flex;
justify-content: center;
gap: 20px;
margin: 30px 0;
}
.social-button {
background: rgba(255, 255, 255, 0.1);
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
transition: all var(--transition-med);
color: var(--text-primary);
font-size: 1.2rem;
box-shadow: var(--shadow-sm);
}
.social-button:hover {
transform: translateY(-5px);
background: rgba(255, 255, 255, 0.2);
box-shadow: var(--shadow-md);
}
.social-linkedin:hover { background: #0077b5; }
.social-github:hover { background: #333; }
.social-email:hover { background: #ea4335; }
.social-button svg {
width: 24px;
height: 24px;
}
/* Card styling */
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
margin: 40px 0;
}
.card-container {
position: relative; /* Important for button positioning */
margin-bottom: 20px;
height: 100%;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.card-container.da:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 6px;
background: linear-gradient(90deg, var(--primary-da), var(--secondary-da));
z-index: 5;
border-radius: var(--border-radius-md) var(--border-radius-md) 0 0;
}
.card-container.ml:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 6px;
background: linear-gradient(90deg, var(--primary-ml), var(--secondary-ml));
z-index: 5;
transition: all var(--transition-med);
border-radius: var(--border-radius-md) var(--border-radius-md) 0 0;
}
.card-container.cv:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 6px;
background: linear-gradient(90deg, var(--primary-cv), var(--secondary-cv));
z-index: 5;
border-radius: var(--border-radius-md) var(--border-radius-md) 0 0;
}
.card-content {
padding: 30px;
min-height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 26px;
font-weight: 700;
position: relative;
z-index: 2;
transition: all var(--transition-med);
}
.card-content svg {
width: 60px;
height: 60px;
margin-bottom: 20px;
opacity: 0.9;
transition: all var(--transition-med);
}
.card-inner {
transition: transform var(--transition-med), box-shadow var(--transition-med), background-color var(--transition-med);
text-align: center;
border-radius: var(--border-radius-md);
background: var(--card-bg);
overflow: hidden;
box-shadow: var(--shadow-md);
height: 100%;
cursor: pointer; /* Indicates the card is clickable */
position: relative; /* Ensure child elements are positioned relative to the card */
}
.card-inner:hover {
transform: translateY(-10px) scale(1.05); /* Adds a slight zoom effect */
box-shadow: var(--shadow-lg); /* Increases shadow for emphasis */
background: rgba(255, 255, 255, 0.1); /* Subtle background change */
border: 2px solid var(--primary-da); /* Optional: Add a border to emphasize hover */
}
.card-inner:hover .card-content svg {
transform: scale(1.1); /* Slightly enlarges the icon */
opacity: 1;
}
.card-inner:hover .card-description {
color: var(--text-primary); /* Optional: changes text color for emphasis */
}
/* Add a subtle glow effect */
.card-inner:hover:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: var(--border-radius-md);
box-shadow: 0 0 15px rgba(255, 255, 255, 0.3);
z-index: -1;
}
.card-description {
padding: 0 20px 20px;
color: var(--text-secondary);
font-size: 1.1rem;
line-height: 1.5;
}
/* Card button styling - crucial for making cards clickable */
.card-button {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
opacity: 0 !important;
z-index: 10 !important;
cursor: pointer !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
transform: scale(1.05) !important;
transition: transform 0.2s ease !important;
background: none !important;
}
.click-to-view {
margin-top: 15px;
padding: 8px 15px;
border-radius: 20px;
display: inline-block;
font-weight: 600;
font-size: 0.9rem;
transition: all var(--transition-med);
background: rgba(255, 255, 255, 0.1);
box-shadow: var(--shadow-sm);
margin-left: auto;
margin-right: auto;
color: var(--text-primary);
text-align: center;
}
/* Add glow effect */
.click-to-view:hover {
transform: translateY(-2px);
box-shadow: 0 0 15px rgba(255, 255, 255, 0.5), var(--shadow-md);
background: rgba(255, 255, 255, 0.2);
color: var(--text-primary);
filter: brightness(1.2);
}
/* Add animation for subtle pulsing effect */
@keyframes glow-pulse {
0% {
box-shadow: 0 0 5px rgba(255, 255, 255, 0.2);
}
50% {
box-shadow: 0 0 15px rgba(255, 255, 255, 0.4);
}
100% {
box-shadow: 0 0 5px rgba(255, 255, 255, 0.2);
}
}
.click-to-view {
animation: glow-pulse 3s infinite;
}
.card-description p {
margin-bottom: 15px;
margin-top: 0;
}
.card-description {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0 20px 20px;
color: var(--text-secondary);
font-size: 1.1rem;
line-height: 1.5;
}
/* Different colors for each card type */
.da-click {
color: var(--primary-da);
border: 1px solid var(--primary-da);
}
.ml-click {
color: var(--primary-ml);
border: 1px solid var(--primary-ml);
}
.cv-click {
color: var(--primary-cv);
border: 1px solid var(--primary-cv);
}
/* Hover effects */
.click-to-view:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
background: rgba(255, 255, 255, 0.15);
}
.card-inner:hover .click-to-view {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.card-inner:hover .da-click {
background-color: rgba(138, 43, 226, 0.1);
}
.card-inner:hover .ml-click {
background-color: rgba(0, 180, 219, 0.1);
}
.card-inner:hover .cv-click {
background-color: rgba(255, 77, 126, 0.1);
}
.experience-timeline {
margin: 80px 0 60px;
padding: 20px;
position: relative;
}
.experience-timeline h2 {
text-align: center;
margin-bottom: 60px;
position: relative;
display: inline-block;
left: 50%;
transform: translateX(-50%);
font-family: 'Montserrat', sans-serif;
font-size: 2.5rem;
font-weight: 700;
background: linear-gradient(90deg, var(--primary-da), var(--primary-ml), var(--primary-cv));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.experience-timeline h2:after {
content: '';
position: absolute;
bottom: -10px;
left: 0;
width: 100%;
height: 3px;
border-radius: 3px;
background: linear-gradient(90deg, var(--primary-da), var(--primary-ml), var(--primary-cv));
}
/* Horizontal Timeline Styling */
.timeline-container {
display: flex;
justify-content: space-between;
align-items: flex-start;
position: relative;
margin: 40px auto;
max-width: 100%;
overflow-x: auto;
padding: 20px 0;
}
.timeline-track {
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(to right, var(--primary-da), var(--primary-ml), var(--primary-cv));
border-radius: 2px;
z-index: 1;
}
.timeline-node {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 150px;
margin: 0 20px;
z-index: 2;
}
.timeline-dot {
width: 20px;
height: 20px;
background: var(--primary-da);
border-radius: 50%;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
margin-bottom: 10px;
transition: transform 0.3s ease;
}
.timeline-year {
font-size: 1rem;
font-weight: 600;
margin-bottom: 10px;
color: var(--text-primary);
}
.timeline-content {
background: var(--card-bg);
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-sm);
padding: 15px;
text-align: center;
width: 100%;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.timeline-node:hover .timeline-dot {
transform: scale(1.3);
}
.timeline-node:hover .timeline-content {
transform: translateY(-10px);
box-shadow: var(--shadow-md);
}
/* Responsive adjustments for smaller screens */
@media (max-width: 768px) {
.timeline-container {
flex-direction: column;
align-items: center;
}
.timeline-track {
display: none;
}
.timeline-node {
margin: 20px 0;
}
}
/* Section styling */
.section-container {
padding: 60px 20px;
position: relative;
}
.section-container:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 300px;
background: radial-gradient(ellipse at top, rgba(255,255,255,0.05) 0%, transparent 70%);
z-index: 0;
}
.da-section h1.section-heading {
color: var(--primary-da);
position: relative;
display: inline-block;
}
.ml-section h1.section-heading {
color: var(--primary-ml);
position: relative;
display: inline-block;
}
.cv-section h1.section-heading {
color: var(--primary-cv);
position: relative;
display: inline-block;
}
.section-heading:after {
content: '';
position: absolute;
bottom: -10px;
left: 0;
width: 100%;
height: 3px;
border-radius: 3px;
}
.da-section .section-heading:after { background: var(--primary-da); }
.ml-section .section-heading:after { background: var(--primary-ml); }
.cv-section .section-heading:after { background: var(--primary-cv); }
/* Subheadings color-coded */
.section-subheading.da { color: var(--primary-da); }
.section-subheading.ml { color: var(--primary-ml); }
.section-subheading.cv { color: var(--primary-cv); }
/* Back buttons */
.back-button {
border: none;
border-radius: var(--border-radius-lg);
padding: 10px 20px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
margin-bottom: 30px;
display: flex;
align-items: center;
gap: 8px;
}
.back-button:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
}
.back-button-da {
background: linear-gradient(45deg, var(--primary-da), var(--secondary-da));
color: #fff;
}
.back-button-ml {
background: linear-gradient(45deg, var(--primary-ml), var(--secondary-ml));
color: #fff;
}
.back-button-cv {
background: linear-gradient(45deg, var(--primary-cv), var(--secondary-cv));
color: #fff;
}
.back-button svg {
width: 20px;
height: 20px;
}
/* Contact form */
.contact-container {
background: var(--card-bg);
border-radius: var(--border-radius-md);
padding: 30px;
max-width: 600px;
margin: 0 auto;
box-shadow: var(--shadow-md);
}
.hire-me-button {
background: linear-gradient(45deg, var(--primary-da), var(--primary-cv));
color: white;
border: none;
border-radius: var(--border-radius-lg);
padding: 12px 25px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all var(--transition-med);
box-shadow: var(--shadow-sm);
display: inline-block;
text-decoration: none;
}
.hire-me-button:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
filter: brightness(1.1);
}
/* Project cards */
.project-card {
background: var(--card-bg);
border-radius: var(--border-radius-md);
padding: 25px;
margin-bottom: 20px;
box-shadow: var(--shadow-sm);
transition: all var(--transition-med);
border-left: 4px solid transparent;
}
.da-section .project-card { border-left-color: var(--primary-da); }
.ml-section .project-card { border-left-color: var(--primary-ml); }
.cv-section .project-card { border-left-color: var(--primary-cv); }
.project-card:hover {
transform: translateX(5px);
box-shadow: var(--shadow-md);
}
.project-title {
font-size: 1.3rem;
font-weight: 600;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.project-title-text {
flex: 1;
}
.project-link {
color: var(--text-secondary);
transition: all var(--transition-med);
text-decoration: none;
display: inline-flex;
align-items: center;
margin-left: 10px;
}
.project-link svg {
width: 16px;
height: 16px;
}
.da-section .project-title-text { color: var(--primary-da); }
.ml-section .project-title-text { color: var(--primary-ml); }
.cv-section .project-title-text { color: var(--primary-cv); }
.da-section .project-link:hover { color: var(--primary-da); }
.ml-section .project-link:hover { color: var(--primary-ml); }
.cv-section .project-link:hover { color: var(--primary-cv); }
.project-description {
color: var(--text-secondary);
line-height: 1.5;
}
.tech-stack {
display: block;
margin-top: 10px;
font-style: italic;
color: var(--text-muted);
}
/* Skills list */
.skills-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 15px;
margin-top: 20px;
margin-bottom: 40px; /* Added margin to create space between skills and projects */
}
.skill-category {
background: rgba(255, 255, 255, 0.05);
border-radius: var(--border-radius-sm);
padding: 15px;
transition: all var(--transition-med);
}
.skill-category:hover {
background: rgba(255, 255, 255, 0.08);
transform: translateY(-3px);
}
.skill-category h4 {
margin-top: 0;
margin-bottom: 10px;
font-size: 1.1rem;
}
.da-section .skill-category h4 { color: var(--primary-da); }
.ml-section .skill-category h4 { color: var(--primary-ml); }
.cv-section .skill-category h4 { color: var(--primary-cv); }
.skill-category ul {
margin: 0;
padding-left: 20px;
color: var(--text-secondary);
}
.skill-category li {
margin-bottom: 5px;
}
/* Section intro text */
.section-intro {
max-width: 800px;
margin-bottom: 30px;
line-height: 1.6;
color: var(--text-secondary);
font-size: 1.1rem;
}
/* Footer */
.footer {
text-align: center;
padding: 40px 20px;
margin-top: 60px;
color: var(--text-muted);
font-size: 0.9rem;
}
/* Animations for scroll */
.animate-on-scroll {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.animate-on-scroll.show {
animation: fadeInUp 0.6s ease forwards;
}
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* Loading states */
.loading-spinner {
border: 4px solid rgba(255, 255, 255, 0.2);
border-top: 4px solid var(--primary-da);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Glassmorphism effects */
.glass-container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: var(--border-radius-md);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
/* Subtle background patterns */
body {
background-image: url('https://www.transparenttextures.com/patterns/noise.png');
}
/* Gradient refinements */
button {
background: linear-gradient(45deg, var(--primary-da), var(--primary-ml));
}
/* Shadow depth variations */
.card-container:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
/* ARIA attributes */
button {
aria-label: "Interactive button";
}
/* Keyboard navigation */
button:focus {
outline: 2px solid var(--primary-da);
outline-offset: 2px;
}
/* Mobile-first refinements */
@media (max-width: 768px) {
.cards-grid {
grid-template-columns: 1fr;
}
}
/* Touch-friendly targets */
button {
padding: 12px 20px;
}
/* Responsive typography */
h1 {
font-size: calc(2rem + 1vw);
}
/* Parallax scrolling effects */
.section-container:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 300px;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.5), transparent);
transform: translateY(-50%);
will-change: transform;
transition: transform 0.3s ease;
}
body.onscroll .section-container:before {
transform: translateY(0);
}
/* Mouse-follow glow effect */
.interactive:hover {
box-shadow: 0 0 15px rgba(255, 255, 255, 0.5);
}
/* Staggered animation sequences */
@keyframes staggeredFadeIn {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.list-item {
opacity: 0;
animation: staggeredFadeIn 0.6s ease forwards;
}
.list-item:nth-child(1) {
animation-delay: 0.2s;
}
.list-item:nth-child(2) {
animation-delay: 0.4s;
}
.list-item:nth-child(3) {
animation-delay: 0.6s;
}
/* Interactive particles background */
.particles-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url('https://www.transparenttextures.com/patterns/cubes.png');
opacity: 0.1;
}
/* Smooth page section transitions */
html {
scroll-behavior: smooth;
}
/* Custom cursor styles */
body {
cursor: url('https://example.com/custom-cursor.png'), auto;
}
button:hover {
cursor: pointer;
}
/* Resume Section */
.resume-section {
margin: 40px auto;
padding: 20px;
text-align: center;
max-width: 800px;
box-shadow: var(--shadow-md);
}
.resume-toggle-button {
background: linear-gradient(45deg, var(--primary-da), var(--primary-cv));
color: white;
border: none;
border-radius: var(--border-radius-lg);
padding: 10px 20px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all var(--transition-med);
box-shadow: var(--shadow-sm);
width: auto; /* Adjust width to fit the text */
min-width: 150px; /* Optional: Set a minimum width for consistency */
}
.resume-toggle-button:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
filter: brightness(1.1);
}
.resume-content {
margin-top: 20px;
text-align: center;
}
.resume-download-button {
display: inline-block;
margin: 20px 0;
padding: 10px 20px;
font-size: 1rem;
font-weight: 600;
color: white;
background: linear-gradient(45deg, var(--primary-da), var(--primary-cv));
border: none;
border-radius: var(--border-radius-lg);
text-decoration: none;
transition: all var(--transition-med);
box-shadow: var(--shadow-sm);
}
.resume-download-button:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-md);
filter: brightness(1.1);
}
.resume-preview {
margin-top: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: var(--border-radius-md);
overflow: hidden;
box-shadow: var(--shadow-sm);
}
.resume-iframe {
width: 100%;
height: 500px;
border: none;
}
/* Performance optimizations */
img {
loading: lazy;
}
/* Micro-interactions */
button:hover {
transform: translateY(-3px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
button:active {
transform: scale(0.95);
transition: transform 0.1s ease;
}
/* Scroll to Top Button */
.scroll-to-top {
position: fixed;
bottom: 20px;
right: 20px;
background: linear-gradient(45deg, var(--primary-da), var(--primary-cv));
color: white;
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
font-size: 1.5rem;
font-weight: bold;
cursor: pointer;
box-shadow: var(--shadow-md);
display: none; /* Initially hidden */
align-items: center;
justify-content: center;
transition: all var(--transition-med);
z-index: 1000;
}
.scroll-to-top:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-lg);
filter: brightness(1.1);
}
"""
# Load all content from JSON files
try:
profile_data = load_json("profile")
sections_data = load_json("sections")
data_analytics_data = load_json("data_analytics")
machine_learning_data = load_json("machine_learning")
computer_vision_data = load_json("computer_vision")
except Exception as e:
print(f"Error loading content: {e}")
# Default values in case of error
profile_data = {}
sections_data = {"cards": []}
data_analytics_data = {}
machine_learning_data = {}
computer_vision_data = {}
# --- Portfolio Layout ---
with gr.Blocks(title=f"{profile_data.get('name', 'Portfolio')}", css=portfolio_css) as demo:
# Create sections
# Data Analytics Section (initially hidden)
with gr.Row(visible=False, elem_classes="section-container da-section") as da_section:
with gr.Column():
# Back button
back_from_da = gr.Button("← Back to Home", elem_classes="back-button back-button-da")
gr.HTML(generate_section_html(data_analytics_data, "da"))
# Machine Learning Section (initially hidden)
with gr.Row(visible=False, elem_classes="section-container ml-section") as ml_section:
with gr.Column():
# Back button
back_from_ml = gr.Button("← Back to Home", elem_classes="back-button back-button-ml")
gr.HTML(generate_section_html(machine_learning_data, "ml"))
# Computer Vision Section (initially hidden)
with gr.Row(visible=False, elem_classes="section-container cv-section") as cv_section:
with gr.Column():
# Back button
back_from_cv = gr.Button("← Back to Home", elem_classes="back-button back-button-cv")
gr.HTML(generate_section_html(computer_vision_data, "cv"))
with gr.Row(visible=True, elem_classes="landing-section") as landing_section:
with gr.Column():
# Profile section with picture
gr.HTML(generate_profile_html())
# Resume Section (Hugging Face-compatible implementation)
resume_state = gr.State(value=False)
with gr.Group(visible=False) as resume_container:
gr.File(value="data/resume.pdf", label="Resume", interactive=False)
resume_toggle_btn = gr.Button("View Resume")
resume_toggle_btn.click(fn=toggle_resume, inputs=[resume_state], outputs=[resume_state, resume_container, resume_toggle_btn])
# Specializations heading
gr.HTML("<h2>My Specializations</h2>")
# Cards Grid with proper structure
with gr.Row(elem_classes="cards-grid"):
with gr.Column():
# Data Analytics Card
gr.HTML('<div class="card-container da">')
da_button = gr.Button("Data Analytics", elem_classes="card-button")
gr.HTML(f'''
<div class="card-inner">
<div class="card-content">
{icons.get(sections_data.get("cards", [])[0].get("icon", ""), "")}
<span>{sections_data.get("cards", [])[0].get("title", "Data Analytics")}</span>
</div>
<div class="card-description">
{sections_data.get("cards", [])[0].get("description", "")}
<div class="click-to-view da-click">Click to view</div>
</div>
</div>
''')
with gr.Column():
# Machine Learning Card
gr.HTML('<div class="card-container ml">')
ml_button = gr.Button("Machine Learning", elem_classes="card-button")
gr.HTML(f'''
<div class="card-inner">
<div class="card-content">
{icons.get(sections_data.get("cards", [])[1].get("icon", ""), "")}
<span>{sections_data.get("cards", [])[1].get("title", "Machine Learning")}</span>
</div>
<div class="card-description">
{sections_data.get("cards", [])[1].get("description", "")}
<div class="click-to-view ml-click">Click to view</div>
</div>
</div>
''')
with gr.Column():
# Computer Vision Card
gr.HTML('<div class="card-container cv">')
cv_button = gr.Button("Computer Vision", elem_classes="card-button")
gr.HTML(f'''
<div class="card-inner">
<div class="card-content">
{icons.get(sections_data.get("cards", [])[2].get("icon", ""), "")}
<span>{sections_data.get("cards", [])[2].get("title", "Computer Vision")}</span>
</div>
<div class="card-description">
{sections_data.get("cards", [])[2].get("description", "")}
<div class="click-to-view cv-click">Click to view</div>
</div>
</div>
''')
gr.HTML(f'''
<div class="experience-timeline">
<h2>My Journey</h2>
<div class="timeline-container">
<div class="timeline-track"></div>
<div class="timeline-node">
<div class="timeline-dot"></div>
<div class="timeline-year">2018-2021</div>
<div class="timeline-content">
<div class="timeline-title">Bachelor's in Commerce (89%)</div>
<div class="timeline-details">
<p class="timeline-location">SRM University Chennai</p>
<p>Graduated with honors focusing on Business and Finance.</p>
</div>
</div>
</div>
<div class="timeline-node">
<div class="timeline-dot"></div>
<div class="timeline-year">2021-2023</div>
<div class="timeline-content">
<div class="timeline-title">Junior Software Engineer at Cognizant</div>
<div class="timeline-details">
<p class="timeline-location">Chennai India</p>
<p>Developed and maintained enterprise-grade Java applications for the Insurance domain</p>
</div>
</div>
</div>
<div class="timeline-node">
<div class="timeline-dot"></div>
<div class="timeline-year">2023-2024</div>
<div class="timeline-content">
<div class="timeline-title">Post-Graduation in AI/ML (97%)</div>
<div class="timeline-details">
<p class="timeline-location">George Brown college,Canada</p>
<p>Advanced studies in AI/ML and data science.</p>
</div>
</div>
</div>
<div class="timeline-node">
<div class="timeline-dot"></div>
<div class="timeline-year">2025</div>
<div class="timeline-content">
<div class="timeline-title">Seeking ML Engineer/Data Scientist Role</div>
<div class="timeline-details">
<p>Ready for opportunities in ML and Data Science in Canada.</p>
</div>
</div>
</div>
</div>
</div>
''')
gr.HTML("""
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get all timeline nodes
const timelineNodes = document.querySelectorAll('.timeline-node');
// Add click event listener to each node
timelineNodes.forEach(node => {
node.addEventListener('click', function() {
// Check if this node is already active
const isActive = this.classList.contains('active');
// Remove active class from all nodes
timelineNodes.forEach(n => n.classList.remove('active'));
// If node wasn't active before, make it active
if (!isActive) {
this.classList.add('active');
}
});
});
// Make the first node active by default
if (timelineNodes.length > 0) {
timelineNodes[0].classList.add('active');
}
});
</script>
""")
# Contact section
gr.HTML(generate_contact_html())
# Add a Gradio File component to serve the resume file
gr.File(value="data/resume.pdf", label="Resume", interactive=False, visible=False)
# Set up click events for navigation
da_button.click(show_data_analytics, inputs=None, outputs=[landing_section, da_section, ml_section, cv_section])
ml_button.click(show_machine_learning, inputs=None, outputs=[landing_section, da_section, ml_section, cv_section])
cv_button.click(show_computer_vision, inputs=None, outputs=[landing_section, da_section, ml_section, cv_section])
back_from_da.click(go_home, inputs=None, outputs=[landing_section, da_section, ml_section, cv_section])
back_from_ml.click(go_home, inputs=None, outputs=[landing_section, da_section, ml_section, cv_section])
back_from_cv.click(go_home, inputs=None, outputs=[landing_section, da_section, ml_section, cv_section])
# Scroll to Top Button
gr.HTML("""
<button id="scrollToTop" class="scroll-to-top" aria-label="Scroll to Top">
</button>
<script>
document.addEventListener('DOMContentLoaded', function () {
const scrollToTopButton = document.getElementById('scrollToTop');
// Show or hide the button based on scroll position
window.addEventListener('scroll', function () {
if (window.scrollY > 300) {
scrollToTopButton.style.display = 'flex'; // Use 'flex' for proper alignment
} else {
scrollToTopButton.style.display = 'none';
}
});
// Scroll to top when the button is clicked
scrollToTopButton.addEventListener('click', function () {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
});
</script>
""")
# Launch the app
if __name__ == "__main__":
demo.launch()