Files
portfolio/4-vitals-monitor.html
T
2026-02-09 11:08:38 +00:00

1485 lines
50 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Andy Charlwood — MPharm | CV</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Inter+Tight:wght@400;500;600&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #FFFFFF;
--text: #334155;
--heading: #0F172A;
--teal: #00897B;
--teal-light: rgba(0, 137, 123, 0.08);
--teal-medium: rgba(0, 137, 123, 0.15);
--coral: #FF6B6B;
--coral-light: rgba(255, 107, 107, 0.08);
--muted: #94A3B8;
--border: #E2E8F0;
--card-bg: #FFFFFF;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
--shadow-lg: 0 8px 24px rgba(0,0,0,0.1);
--radius: 16px;
--font-primary: 'Plus Jakarta Sans', system-ui, sans-serif;
--font-secondary: 'Inter Tight', system-ui, sans-serif;
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--font-primary);
font-size: 15px;
line-height: 1.7;
margin: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* =========================================
BOOT SCREEN
========================================= */
#boot-screen {
position: fixed;
inset: 0;
background: #000;
z-index: 1000;
display: flex;
flex-direction: column;
justify-content: center;
padding: 40px;
font-family: 'Fira Code', monospace;
font-size: 14px;
overflow: hidden;
}
#boot-lines {
display: flex;
flex-direction: column;
gap: 4px;
max-width: 640px;
}
.boot-line {
opacity: 0;
transform: translateY(8px);
transition: opacity 400ms ease-out, transform 400ms ease-out;
white-space: nowrap;
line-height: 1.6;
}
.boot-line.visible {
opacity: 1;
transform: translateY(0);
}
.boot-line.fading {
opacity: 0;
transition: opacity 800ms ease;
}
.c-green { color: #00ff41; }
.c-green-bold { color: #00ff41; font-weight: 700; }
.c-cyan { color: #00e5ff; }
.c-dim { color: #3a6b45; }
.c-grey { color: #666; }
/* Blinking cursor */
#boot-cursor {
display: inline-block;
width: 8px;
height: 16px;
background: #00ff41;
animation: blink 1s step-end infinite;
vertical-align: middle;
margin-left: 4px;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
/* =========================================
ECG OVERLAY
========================================= */
#ecg-overlay {
position: fixed;
inset: 0;
z-index: 1001;
pointer-events: none;
}
#ecg-overlay svg {
width: 100%;
height: 100%;
display: block;
}
.ecg-line {
fill: none;
stroke: #00ff41;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
filter: drop-shadow(0 0 4px rgba(0, 255, 65, 0.6));
}
.ecg-heartbeat {
fill: none;
stroke: #00ff41;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
filter: drop-shadow(0 0 6px rgba(0, 255, 65, 0.7));
}
.ecg-branch {
fill: none;
stroke: #00897B;
stroke-width: 1.5;
stroke-linecap: round;
stroke-linejoin: round;
filter: drop-shadow(0 0 3px rgba(0, 137, 123, 0.5));
}
/* =========================================
FINAL CV CONTENT (hidden behind boot)
========================================= */
#cv-content {
opacity: 0;
}
#cv-content.revealed {
opacity: 1;
transition: opacity 600ms ease;
}
/* =========================================
FLOATING PILL NAV
========================================= */
.pill-nav {
position: fixed;
top: 16px;
left: 50%;
transform: translateX(-50%);
z-index: 100;
max-width: 600px;
width: auto;
background: var(--card-bg);
border-radius: 999px;
padding: 8px 24px;
box-shadow: var(--shadow-md);
display: flex;
align-items: center;
gap: 4px;
border: 1px solid var(--border);
}
.pill-nav a {
position: relative;
font-family: var(--font-secondary);
font-size: 13px;
font-weight: 500;
color: var(--muted);
text-decoration: none;
padding: 6px 14px;
border-radius: 999px;
transition: color 0.3s ease, background 0.3s ease;
}
.pill-nav a:hover {
color: var(--teal);
background: var(--teal-light);
}
.pill-nav a.active {
color: var(--teal);
font-weight: 600;
}
.pill-nav a.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--teal);
}
/* =========================================
MAIN CONTAINER & SECTIONS
========================================= */
.cv-main {
max-width: 1000px;
margin: 0 auto;
padding: 0 32px;
}
.cv-main section {
padding: 80px 0;
}
.section-heading {
font-family: var(--font-primary);
font-size: 24px;
font-weight: 700;
color: var(--heading);
margin-bottom: 32px;
text-align: center;
}
/* =========================================
HERO SECTION
========================================= */
.hero {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
.hero h1 {
font-family: var(--font-primary);
font-weight: 700;
font-size: clamp(36px, 5vw, 52px);
color: var(--heading);
line-height: 1.2;
margin: 0;
}
.hero-title {
font-size: 16px;
color: var(--muted);
margin: 8px 0;
}
.hero-location {
display: inline-block;
padding: 4px 16px;
border: 1px solid var(--teal);
border-radius: 999px;
font-size: 12px;
color: var(--teal);
font-weight: 500;
margin-top: 4px;
}
.hero-summary {
font-size: 15px;
line-height: 1.8;
max-width: 560px;
color: var(--text);
margin: 24px auto 0;
text-align: center;
}
/* Vital sign metric cards */
.vitals-row {
display: flex;
gap: 16px;
margin-top: 40px;
justify-content: center;
flex-wrap: wrap;
}
.vital-card {
background: var(--card-bg);
border-radius: var(--radius);
padding: 20px 24px;
box-shadow: var(--shadow-sm);
border-top: 3px solid var(--teal);
min-width: 160px;
text-align: center;
transition: box-shadow 0.3s ease, transform 0.3s ease;
}
.vital-card:hover {
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
.vital-value {
font-family: var(--font-primary);
font-size: 28px;
font-weight: 700;
color: var(--heading);
line-height: 1.2;
}
.vital-value.small {
font-size: 16px;
}
.vital-value.medium {
font-size: 18px;
}
.vital-label {
font-family: var(--font-secondary);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
margin-top: 4px;
}
/* =========================================
SKILLS SECTION
========================================= */
.skills-category {
margin-bottom: 40px;
}
.skills-category:last-child {
margin-bottom: 0;
}
.skills-cat-label {
font-family: var(--font-secondary);
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
margin-bottom: 20px;
padding-left: 4px;
}
.skills-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 24px;
}
.skill-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
border-radius: var(--radius);
transition: background 0.3s ease;
}
.skill-item:hover {
background: var(--teal-light);
}
.skill-item[data-color="coral"]:hover {
background: var(--coral-light);
}
.skill-gauge {
display: block;
}
.skill-progress {
stroke-dasharray: 213.628;
stroke-dashoffset: 213.628;
transition: stroke-dashoffset 1.2s ease-out;
}
.skill-name {
font-family: var(--font-primary);
font-size: 12px;
font-weight: 600;
color: var(--heading);
margin-top: 8px;
text-align: center;
line-height: 1.3;
}
.skill-cat {
font-family: var(--font-secondary);
font-size: 10px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.04em;
margin-top: 2px;
}
/* =========================================
EXPERIENCE SECTION
========================================= */
.experience-header {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
margin-bottom: 32px;
}
.experience-header h2 {
font-family: var(--font-primary);
font-size: 24px;
font-weight: 700;
color: var(--heading);
margin: 0;
}
.ecg-decoration {
display: block;
flex-shrink: 0;
}
.timeline {
position: relative;
}
.timeline::before {
content: '';
position: absolute;
left: 20%;
top: 0;
bottom: 0;
width: 2px;
background: var(--teal);
opacity: 0.2;
}
.timeline-entry {
position: relative;
padding-left: calc(20% + 32px);
margin-bottom: 32px;
}
.timeline-entry:last-child {
margin-bottom: 0;
}
.timeline-dot {
position: absolute;
left: 20%;
top: 8px;
transform: translateX(-50%);
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid var(--teal);
background: var(--bg);
z-index: 1;
}
.timeline-dot.current {
background: var(--teal);
}
.timeline-card {
background: var(--card-bg);
border-radius: var(--radius);
padding: 24px;
box-shadow: var(--shadow-sm);
border-left: 3px solid transparent;
transition: box-shadow 0.3s ease, transform 0.3s ease, border-left-color 0.3s ease;
}
.timeline-card:hover {
box-shadow: var(--shadow-md);
transform: scale(1.01);
border-left-color: rgba(0, 137, 123, 0.3);
}
.timeline-role {
font-family: var(--font-primary);
font-size: 17px;
font-weight: 600;
color: var(--heading);
margin: 0;
line-height: 1.3;
}
.timeline-org {
font-family: var(--font-primary);
font-size: 14px;
color: var(--teal);
margin: 2px 0;
}
.timeline-date {
display: inline-block;
padding: 2px 10px;
background: var(--teal-light);
border-radius: 999px;
font-family: var(--font-secondary);
font-size: 12px;
color: var(--teal);
font-weight: 500;
margin: 6px 0 12px;
}
.timeline-bullets {
list-style: none;
padding: 0;
margin: 0;
}
.timeline-bullets li {
font-size: 14px;
line-height: 1.7;
margin: 4px 0;
padding-left: 15px;
position: relative;
color: var(--text);
}
.timeline-bullets li::before {
content: '';
display: inline-block;
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--teal);
position: absolute;
left: 0;
top: 10px;
}
/* =========================================
RESPONSIVE: 768px
========================================= */
@media (max-width: 768px) {
.pill-nav {
max-width: 100%;
width: calc(100% - 32px);
overflow-x: auto;
padding: 6px 12px;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.pill-nav::-webkit-scrollbar {
display: none;
}
.cv-main {
padding: 0 20px;
}
.vitals-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
.skills-grid {
grid-template-columns: repeat(3, 1fr);
}
.timeline::before {
display: none;
}
.timeline-dot {
display: none;
}
.timeline-entry {
padding-left: 0;
}
}
/* =========================================
RESPONSIVE: 480px
========================================= */
@media (max-width: 480px) {
.pill-nav a {
font-size: 11px;
padding: 4px 8px;
}
.cv-main {
padding: 0 16px;
}
.cv-main section {
padding: 48px 0;
}
.hero h1 {
font-size: 28px;
}
.vitals-row {
display: grid;
grid-template-columns: 1fr;
}
.skills-grid {
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.skill-gauge {
width: 64px;
height: 64px;
}
.skill-item {
padding: 12px;
}
.timeline-card {
padding: 16px;
}
.experience-header {
gap: 10px;
}
.ecg-decoration {
width: 120px;
}
}
</style>
</head>
<body>
<!-- Boot Screen Overlay -->
<div id="boot-screen">
<div id="boot-lines"></div>
</div>
<!-- ECG Overlay (created dynamically but container lives here) -->
<div id="ecg-overlay"></div>
<!-- Final CV Content (hidden behind boot screen until transition) -->
<div id="cv-content">
<!-- Floating Pill Navigation -->
<nav class="pill-nav" id="pill-nav">
<a href="#about" data-section="about">About</a>
<a href="#skills" data-section="skills">Skills</a>
<a href="#experience" data-section="experience">Experience</a>
<a href="#education" data-section="education">Education</a>
<a href="#projects" data-section="projects">Projects</a>
<a href="#contact" data-section="contact">Contact</a>
</nav>
<!-- Main CV Content -->
<main class="cv-main">
<section id="about" class="hero">
<h1>Andy Charlwood</h1>
<p class="hero-title">Deputy Head of Population Health &amp; Data Analysis</p>
<span class="hero-location">Norwich, UK</span>
<p class="hero-summary">GPhC Registered Pharmacist specialising in medicines optimisation, population health analytics, and NHS efficiency programmes. Bridging clinical pharmacy with data science to drive meaningful improvements in patient outcomes.</p>
<div class="vitals-row">
<div class="vital-card">
<div class="vital-value">10+</div>
<div class="vital-label">Years Experience</div>
</div>
<div class="vital-card">
<div class="vital-value small">Python/SQL/BI</div>
<div class="vital-label">Analytics Stack</div>
</div>
<div class="vital-card">
<div class="vital-value medium">Pop. Health</div>
<div class="vital-label">Focus Area</div>
</div>
<div class="vital-card">
<div class="vital-value medium">NHS N&amp;W</div>
<div class="vital-label">System</div>
</div>
</div>
</section>
<section id="skills">
<h2 class="section-heading">Skills &amp; Expertise</h2>
<div class="skills-category">
<h3 class="skills-cat-label">Technical</h3>
<div class="skills-grid">
<div class="skill-item" data-level="90" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">90%</text>
</svg>
<span class="skill-name">Python</span>
<span class="skill-cat">Technical</span>
</div>
<div class="skill-item" data-level="88" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">88%</text>
</svg>
<span class="skill-name">SQL</span>
<span class="skill-cat">Technical</span>
</div>
<div class="skill-item" data-level="92" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">92%</text>
</svg>
<span class="skill-name">Power BI</span>
<span class="skill-cat">Technical</span>
</div>
<div class="skill-item" data-level="70" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">70%</text>
</svg>
<span class="skill-name">JS / TS</span>
<span class="skill-cat">Technical</span>
</div>
<div class="skill-item" data-level="95" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">95%</text>
</svg>
<span class="skill-name">Data Analysis</span>
<span class="skill-cat">Technical</span>
</div>
<div class="skill-item" data-level="88" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">88%</text>
</svg>
<span class="skill-name">Dashboard Dev</span>
<span class="skill-cat">Technical</span>
</div>
<div class="skill-item" data-level="82" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">82%</text>
</svg>
<span class="skill-name">Algorithm Design</span>
<span class="skill-cat">Technical</span>
</div>
<div class="skill-item" data-level="80" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">80%</text>
</svg>
<span class="skill-name">Data Pipelines</span>
<span class="skill-cat">Technical</span>
</div>
</div>
</div>
<div class="skills-category">
<h3 class="skills-cat-label">Clinical</h3>
<div class="skills-grid">
<div class="skill-item" data-level="95" data-color="coral">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--coral)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">95%</text>
</svg>
<span class="skill-name">Medicines Optimisation</span>
<span class="skill-cat">Clinical</span>
</div>
<div class="skill-item" data-level="90" data-color="coral">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--coral)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">90%</text>
</svg>
<span class="skill-name">Pop. Health Analytics</span>
<span class="skill-cat">Clinical</span>
</div>
<div class="skill-item" data-level="85" data-color="coral">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--coral)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">85%</text>
</svg>
<span class="skill-name">NICE TA</span>
<span class="skill-cat">Clinical</span>
</div>
<div class="skill-item" data-level="80" data-color="coral">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--coral)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">80%</text>
</svg>
<span class="skill-name">Health Economics</span>
<span class="skill-cat">Clinical</span>
</div>
<div class="skill-item" data-level="82" data-color="coral">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--coral)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">82%</text>
</svg>
<span class="skill-name">Clinical Pathways</span>
<span class="skill-cat">Clinical</span>
</div>
<div class="skill-item" data-level="88" data-color="coral">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--coral)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">88%</text>
</svg>
<span class="skill-name">CD Assurance</span>
<span class="skill-cat">Clinical</span>
</div>
</div>
</div>
<div class="skills-category">
<h3 class="skills-cat-label">Strategic</h3>
<div class="skills-grid">
<div class="skill-item" data-level="90" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">90%</text>
</svg>
<span class="skill-name">Budget Mgmt</span>
<span class="skill-cat">Strategic</span>
</div>
<div class="skill-item" data-level="88" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">88%</text>
</svg>
<span class="skill-name">Stakeholder Engagement</span>
<span class="skill-cat">Strategic</span>
</div>
<div class="skill-item" data-level="85" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">85%</text>
</svg>
<span class="skill-name">Pharma Negotiation</span>
<span class="skill-cat">Strategic</span>
</div>
<div class="skill-item" data-level="82" data-color="teal">
<svg class="skill-gauge" width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="none" stroke="var(--border)" stroke-width="5"/>
<circle class="skill-progress" cx="40" cy="40" r="34" fill="none" stroke="var(--teal)" stroke-width="5" stroke-linecap="round" transform="rotate(-90, 40, 40)"/>
<text x="40" y="40" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="600" fill="var(--heading)" font-family="var(--font-secondary)">82%</text>
</svg>
<span class="skill-name">Team Development</span>
<span class="skill-cat">Strategic</span>
</div>
</div>
</div>
</section>
<section id="experience">
<div class="experience-header">
<h2>Experience</h2>
<svg class="ecg-decoration" width="200" height="30" viewBox="0 0 200 30" fill="none">
<path d="M 0 15 L 40 15 L 50 15 C 53 15 55 12 58 12 C 61 12 63 15 66 15 L 76 15 L 80 20 L 86 2 L 92 22 L 96 15 L 106 15 C 109 15 111 11 114 11 C 117 11 120 15 123 15 L 200 15" stroke="var(--teal)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.3"/>
</svg>
</div>
<div class="timeline">
<div class="timeline-entry">
<div class="timeline-dot current"></div>
<div class="timeline-card">
<h3 class="timeline-role">Interim Head of Population Health &amp; Data Analysis</h3>
<p class="timeline-org">NHS Norfolk &amp; Waveney ICB</p>
<span class="timeline-date">May 2025 &mdash; Nov 2025</span>
<ul class="timeline-bullets">
<li>Led team through organisational transition, maintaining delivery of &pound;14.6M efficiency programme</li>
<li>Directed strategic priorities for population health analytics across Norfolk &amp; Waveney (population ~1M)</li>
<li>Managed stakeholder relationships with system leaders, provider trusts, and primary care networks</li>
</ul>
</div>
</div>
<div class="timeline-entry">
<div class="timeline-dot current"></div>
<div class="timeline-card">
<h3 class="timeline-role">Deputy Head of Population Health &amp; Data Analysis</h3>
<p class="timeline-org">NHS Norfolk &amp; Waveney ICB</p>
<span class="timeline-date">Jul 2024 &mdash; Present</span>
<ul class="timeline-bullets">
<li>Deputised for Head of department across all operational and strategic functions</li>
<li>Oversaw &pound;220M medicines budget and led programme of cost improvement initiatives</li>
<li>Developed Python-based switching algorithm processing 14,000 patients, delivering &pound;2.6M savings</li>
<li>Built Blueteq automation system reducing processing time by 70%, saving 200+ hours annually</li>
<li>Created PharMetrics dashboard platform for real-time medicines expenditure tracking</li>
</ul>
</div>
</div>
<div class="timeline-entry">
<div class="timeline-dot"></div>
<div class="timeline-card">
<h3 class="timeline-role">High-Cost Drugs &amp; Interface Pharmacist</h3>
<p class="timeline-org">NHS Norfolk &amp; Waveney ICB</p>
<span class="timeline-date">May 2022 &mdash; Jul 2024</span>
<ul class="timeline-bullets">
<li>Managed high-cost drugs budget across acute and community settings</li>
<li>Led NICE Technology Appraisal implementation and horizon scanning</li>
<li>Developed health economic models for biosimilar switching programmes</li>
<li>Built data pipelines for automated reporting of medicines expenditure</li>
</ul>
</div>
</div>
<div class="timeline-entry">
<div class="timeline-dot"></div>
<div class="timeline-card">
<h3 class="timeline-role">Pharmacy Manager</h3>
<p class="timeline-org">Tesco Pharmacy</p>
<span class="timeline-date">Nov 2017 &mdash; May 2022</span>
<ul class="timeline-bullets">
<li>Managed community pharmacy delivering 3,000+ items monthly</li>
<li>Pioneered asthma screening service generating &pound;1M+ national revenue</li>
<li>Led team of 6 through COVID-19 pandemic service delivery</li>
<li>Completed Mary Seacole NHS Leadership Programme (2018)</li>
</ul>
</div>
</div>
<div class="timeline-entry">
<div class="timeline-dot"></div>
<div class="timeline-card">
<h3 class="timeline-role">Duty Pharmacy Manager</h3>
<p class="timeline-org">Tesco Pharmacy</p>
<span class="timeline-date">Aug 2016 &mdash; Nov 2017</span>
<ul class="timeline-bullets">
<li>Supported pharmacy manager in daily operations and clinical services</li>
<li>Delivered Medicines Use Reviews and New Medicine Service consultations</li>
<li>Maintained controlled drug compliance and clinical governance standards</li>
</ul>
</div>
</div>
</div>
</section>
<section id="education">
<!-- Task 8: Education section -->
</section>
<section id="projects">
<!-- Task 8: Projects section -->
</section>
<section id="contact">
<!-- Task 8: Contact section -->
</section>
</main>
<footer id="cv-footer">
<!-- Task 8: Footer with ECG decoration -->
</footer>
</div>
<script>
(function() {
'use strict';
/* =========================================
BOOT SEQUENCE
========================================= */
var bootLines = [
{ html: '<span class="c-green-bold">CLINICAL TERMINAL v3.2.1</span>', delay: 0 },
{ html: '<span class="c-dim">Initialising pharmacist profile...</span>', delay: 220 },
{ html: '<span class="c-dim">---</span>', delay: 220 },
{ html: '<span class="c-cyan">SYSTEM </span><span class="c-green">NHS Norfolk &amp; Waveney ICB</span>', delay: 220 },
{ html: '<span class="c-cyan">USER </span><span class="c-green">Andy Charlwood</span>', delay: 220 },
{ html: '<span class="c-cyan">ROLE </span><span class="c-green">Deputy Head of Population Health &amp; Data Analysis</span>', delay: 220 },
{ html: '<span class="c-cyan">LOCATION </span><span class="c-green">Norwich, UK</span>', delay: 220 },
{ html: '<span class="c-dim">---</span>', delay: 220 },
{ html: '<span class="c-dim">Loading modules...</span>', delay: 220 },
{ html: '<span class="c-green-bold">[OK]</span> <span class="c-dim">pharmacist_core.sys</span>', delay: 220 },
{ html: '<span class="c-green-bold">[OK]</span> <span class="c-dim">population_health.mod</span>', delay: 220 },
{ html: '<span class="c-green-bold">[OK]</span> <span class="c-dim">data_analytics.eng</span>', delay: 220 },
{ html: '<span class="c-dim">---</span>', delay: 220 },
{ html: '<span class="c-green-bold">&gt; READY — Rendering CV...</span><span id="boot-cursor"></span>', delay: 220 }
];
var bootContainer = document.getElementById('boot-lines');
var totalBootTime = 0;
bootLines.forEach(function(line, index) {
totalBootTime += line.delay;
var el = document.createElement('div');
el.className = 'boot-line';
el.innerHTML = line.html;
bootContainer.appendChild(el);
setTimeout(function() {
el.classList.add('visible');
}, totalBootTime);
});
// After all lines are visible, wait 400ms then remove cursor and fade boot text
var bootEndTime = totalBootTime + 400;
setTimeout(function() {
// Remove cursor
var cursor = document.getElementById('boot-cursor');
if (cursor) {
cursor.remove();
}
// Fade out all boot lines
var allLines = bootContainer.querySelectorAll('.boot-line');
for (var i = 0; i < allLines.length; i++) {
allLines[i].classList.add('fading');
}
}, bootEndTime);
// After fade out (800ms), start the ECG phase
setTimeout(function() {
startECGPhase();
}, bootEndTime + 800);
/* =========================================
ECG PHASE: Flatline + First Heartbeat
========================================= */
function startECGPhase() {
var overlay = document.getElementById('ecg-overlay');
var bootScreen = document.getElementById('boot-screen');
var vw = window.innerWidth;
var vh = window.innerHeight;
var cy = Math.round(vh / 2);
// Create SVG
var svgNS = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('viewBox', '0 0 ' + vw + ' ' + vh);
svg.setAttribute('preserveAspectRatio', 'none');
overlay.appendChild(svg);
// --- FLATLINE ---
var flatlinePath = document.createElementNS(svgNS, 'path');
var flatlineD = 'M 0 ' + cy + ' L ' + vw + ' ' + cy;
flatlinePath.setAttribute('d', flatlineD);
flatlinePath.setAttribute('class', 'ecg-line');
svg.appendChild(flatlinePath);
var flatlineLen = flatlinePath.getTotalLength();
flatlinePath.style.strokeDasharray = flatlineLen;
flatlinePath.style.strokeDashoffset = flatlineLen;
// Animate flatline drawing left-to-right over 1000ms
requestAnimationFrame(function() {
flatlinePath.style.transition = 'stroke-dashoffset 1000ms linear';
flatlinePath.style.strokeDashoffset = '0';
});
// --- FIRST HEARTBEAT (R peak 40px, green) ---
setTimeout(function() {
drawHeartbeat(svg, svgNS, vw, vh, cy, 40, '#00ff41', 600, function() {
// Hold 300ms then second heartbeat
setTimeout(function() {
// --- SECOND HEARTBEAT (R peak 60px, transitioning color) ---
// Bg begins lightening slightly
bootScreen.style.transition = 'background 600ms ease';
bootScreen.style.background = '#0A0A0A';
drawHeartbeat(svg, svgNS, vw, vh, cy, 60, '#00C9A7', 600, function() {
// Hold 300ms then third heartbeat
setTimeout(function() {
// --- THIRD HEARTBEAT (R peak 100px, full teal) ---
bootScreen.style.transition = 'background 600ms ease';
bootScreen.style.background = '#141414';
drawHeartbeat(svg, svgNS, vw, vh, cy, 100, '#00897B', 600, function() {
// At the apex of the third beat — launch branching
startBranching(svg, svgNS, vw, vh, cy, overlay, bootScreen);
});
}, 300);
});
}, 300);
});
}, 1050);
}
/**
* Creates the overflow branching effect from the third heartbeat peak.
* Branch lines shoot outward and trace the outlines of UI elements.
*/
function startBranching(svg, svgNS, vw, vh, cy, overlay, bootScreen) {
var cx = Math.round(vw / 2);
var peakY = cy - 100; // Third beat R peak position
// Calculate UI element positions for branches to trace
// Pill nav: rounded rectangle at top center
var navW = Math.min(520, vw - 64);
var navH = 44;
var navX = Math.round((vw - navW) / 2);
var navY = 16;
var navR = 22; // border-radius
// Hero section: centered area
var heroW = Math.min(600, vw - 80);
var heroX = Math.round((vw - heroW) / 2);
var heroY = Math.round(vh * 0.2);
var heroH = Math.round(vh * 0.5);
// Card outlines (vital sign cards)
var cardW = 160;
var cardH = 80;
var cardGap = 16;
var totalCardsW = 4 * cardW + 3 * cardGap;
var cardsStartX = Math.round((vw - totalCardsW) / 2);
var cardsY = Math.round(vh * 0.65);
// Branch paths — each starts from the peak and traces outward
var branches = [];
// Branch 1: Shoots up to trace the pill nav bar outline
branches.push({
d: 'M ' + cx + ' ' + peakY +
' Q ' + cx + ' ' + (navY + navH + 20) + ', ' + (navX + navR) + ' ' + (navY + navH) +
' L ' + (navX + navR) + ' ' + (navY + navH) +
' Q ' + navX + ' ' + (navY + navH) + ', ' + navX + ' ' + (navY + navH - navR) +
' L ' + navX + ' ' + (navY + navR) +
' Q ' + navX + ' ' + navY + ', ' + (navX + navR) + ' ' + navY +
' L ' + (navX + navW - navR) + ' ' + navY +
' Q ' + (navX + navW) + ' ' + navY + ', ' + (navX + navW) + ' ' + (navY + navR) +
' L ' + (navX + navW) + ' ' + (navY + navH - navR) +
' Q ' + (navX + navW) + ' ' + (navY + navH) + ', ' + (navX + navW - navR) + ' ' + (navY + navH) +
' L ' + (navX + navR) + ' ' + (navY + navH),
delay: 0
});
// Branch 2: Shoots left to trace the left edge of hero container
branches.push({
d: 'M ' + cx + ' ' + peakY +
' Q ' + (heroX + 40) + ' ' + (peakY - 30) + ', ' + heroX + ' ' + heroY +
' L ' + heroX + ' ' + (heroY + heroH),
delay: 80
});
// Branch 3: Shoots right to trace the right edge of hero container
branches.push({
d: 'M ' + cx + ' ' + peakY +
' Q ' + (heroX + heroW - 40) + ' ' + (peakY - 30) + ', ' + (heroX + heroW) + ' ' + heroY +
' L ' + (heroX + heroW) + ' ' + (heroY + heroH),
delay: 80
});
// Branch 4: Down-left to first card outline (trace rectangle, no Z back to peak)
var c1x = cardsStartX;
branches.push({
d: 'M ' + cx + ' ' + peakY +
' Q ' + (cx - 80) + ' ' + (cardsY - 40) + ', ' + c1x + ' ' + cardsY +
' L ' + (c1x + cardW) + ' ' + cardsY +
' L ' + (c1x + cardW) + ' ' + (cardsY + cardH) +
' L ' + c1x + ' ' + (cardsY + cardH) +
' L ' + c1x + ' ' + cardsY,
delay: 150
});
// Branch 5: Down to second card outline
var c2x = cardsStartX + cardW + cardGap;
branches.push({
d: 'M ' + cx + ' ' + peakY +
' Q ' + (c2x + cardW / 2) + ' ' + (cardsY - 20) + ', ' + c2x + ' ' + cardsY +
' L ' + (c2x + cardW) + ' ' + cardsY +
' L ' + (c2x + cardW) + ' ' + (cardsY + cardH) +
' L ' + c2x + ' ' + (cardsY + cardH) +
' L ' + c2x + ' ' + cardsY,
delay: 200
});
// Branch 6: Down-right to third card outline
var c3x = cardsStartX + 2 * (cardW + cardGap);
branches.push({
d: 'M ' + cx + ' ' + peakY +
' Q ' + (cx + 60) + ' ' + (cardsY - 30) + ', ' + c3x + ' ' + cardsY +
' L ' + (c3x + cardW) + ' ' + cardsY +
' L ' + (c3x + cardW) + ' ' + (cardsY + cardH) +
' L ' + c3x + ' ' + (cardsY + cardH) +
' L ' + c3x + ' ' + cardsY,
delay: 250
});
// Branch 7: Far right to fourth card outline
var c4x = cardsStartX + 3 * (cardW + cardGap);
branches.push({
d: 'M ' + cx + ' ' + peakY +
' Q ' + (cx + 120) + ' ' + (cardsY - 20) + ', ' + c4x + ' ' + cardsY +
' L ' + (c4x + cardW) + ' ' + cardsY +
' L ' + (c4x + cardW) + ' ' + (cardsY + cardH) +
' L ' + c4x + ' ' + (cardsY + cardH) +
' L ' + c4x + ' ' + cardsY,
delay: 300
});
// Animate each branch with staggered timing
var branchDuration = 800;
var maxDelay = 0;
branches.forEach(function(branch) {
if (branch.delay > maxDelay) maxDelay = branch.delay;
setTimeout(function() {
var path = document.createElementNS(svgNS, 'path');
path.setAttribute('d', branch.d);
path.setAttribute('class', 'ecg-branch');
svg.appendChild(path);
var len = path.getTotalLength();
path.style.strokeDasharray = len;
path.style.strokeDashoffset = len;
requestAnimationFrame(function() {
path.style.transition = 'stroke-dashoffset ' + branchDuration + 'ms cubic-bezier(0.25, 0.46, 0.45, 0.94)';
path.style.strokeDashoffset = '0';
});
}, branch.delay);
});
// Background rapidly transitions to white during branching
setTimeout(function() {
bootScreen.style.transition = 'background 800ms ease-out';
bootScreen.style.background = '#FFFFFF';
}, 200);
// After all branches finish drawing, fade out and reveal
var totalBranchTime = maxDelay + branchDuration + 100;
setTimeout(function() {
finishECGPhase(overlay, bootScreen);
}, totalBranchTime);
}
/**
* Draws a PQRST waveform centered horizontally on the screen.
* @param {SVGElement} svg - The SVG container
* @param {string} svgNS - SVG namespace
* @param {number} vw - Viewport width
* @param {number} vh - Viewport height
* @param {number} cy - Vertical center (baseline Y)
* @param {number} rHeight - Height of the R peak above baseline
* @param {string} color - Stroke color
* @param {number} duration - Animation duration in ms
* @param {function} onComplete - Callback when animation finishes
*/
function drawHeartbeat(svg, svgNS, vw, vh, cy, rHeight, color, duration, onComplete) {
// PQRST waveform shape relative to center point
// Total width of the waveform: ~160px
var waveWidth = 160;
var startX = Math.round((vw - waveWidth) / 2);
// Build the PQRST path
// P wave: gentle upward bump, ~8px above baseline, ~30px wide
// Flat: ~10px
// Q dip: sharp dip ~10px below, ~8px wide
// R spike: sharp peak rHeight above baseline, ~12px wide
// S dip: sharp dip ~15px below, ~8px wide
// Flat: ~10px
// T wave: gentle upward bump, ~12px above, ~35px wide
// Return to baseline
var x = startX;
var d = 'M ' + x + ' ' + cy;
// Lead-in flat segment
d += ' L ' + (x + 10) + ' ' + cy;
x += 10;
// P wave (cubic bezier for smooth bump)
d += ' C ' + (x + 8) + ' ' + cy + ', ' + (x + 10) + ' ' + (cy - 8) + ', ' + (x + 15) + ' ' + (cy - 8);
d += ' C ' + (x + 20) + ' ' + (cy - 8) + ', ' + (x + 22) + ' ' + cy + ', ' + (x + 30) + ' ' + cy;
x += 30;
// Flat segment before QRS
d += ' L ' + (x + 10) + ' ' + cy;
x += 10;
// Q dip
d += ' L ' + (x + 4) + ' ' + (cy + 10);
x += 4;
// R spike (sharp peak)
d += ' L ' + (x + 6) + ' ' + (cy - rHeight);
x += 6;
// S dip
d += ' L ' + (x + 6) + ' ' + (cy + 15);
x += 6;
// Return to baseline
d += ' L ' + (x + 4) + ' ' + cy;
x += 4;
// Flat segment before T wave
d += ' L ' + (x + 10) + ' ' + cy;
x += 10;
// T wave (cubic bezier for smooth bump)
d += ' C ' + (x + 8) + ' ' + cy + ', ' + (x + 12) + ' ' + (cy - 12) + ', ' + (x + 17) + ' ' + (cy - 12);
d += ' C ' + (x + 22) + ' ' + (cy - 12) + ', ' + (x + 27) + ' ' + cy + ', ' + (x + 35) + ' ' + cy;
x += 35;
// Trail-out flat segment
d += ' L ' + (x + 10) + ' ' + cy;
var beatPath = document.createElementNS(svgNS, 'path');
beatPath.setAttribute('d', d);
beatPath.setAttribute('class', 'ecg-heartbeat');
beatPath.style.stroke = color;
// Match the glow filter to the stroke color
if (color !== '#00ff41') {
beatPath.style.filter = 'drop-shadow(0 0 6px rgba(0, 137, 123, 0.7))';
}
svg.appendChild(beatPath);
var beatLen = beatPath.getTotalLength();
beatPath.style.strokeDasharray = beatLen;
beatPath.style.strokeDashoffset = beatLen;
// Animate the heartbeat drawing
requestAnimationFrame(function() {
beatPath.style.transition = 'stroke-dashoffset ' + duration + 'ms ease-in-out';
beatPath.style.strokeDashoffset = '0';
});
// Callback after animation
if (onComplete) {
setTimeout(onComplete, duration + 50);
}
}
/**
* Final cleanup: fade out all SVG lines and reveal the CV content.
*/
function finishECGPhase(overlay, bootScreen) {
// Fade out all SVG lines
overlay.style.transition = 'opacity 500ms ease';
overlay.style.opacity = '0';
setTimeout(function() {
// Remove overlays from DOM
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
if (bootScreen.parentNode) bootScreen.parentNode.removeChild(bootScreen);
// Reveal CV content
var cvContent = document.getElementById('cv-content');
cvContent.classList.add('revealed');
// Initialize nav tracking, smooth scroll, and skill gauges after reveal
initNavTracking();
initSkillGauges();
}, 500);
}
/* =========================================
NAV: Active Section Tracking & Smooth Scroll
========================================= */
function initNavTracking() {
var navLinks = document.querySelectorAll('.pill-nav a');
var sections = document.querySelectorAll('.cv-main section');
// Smooth scroll on nav click
navLinks.forEach(function(link) {
link.addEventListener('click', function(e) {
e.preventDefault();
var targetId = link.getAttribute('data-section');
var target = document.getElementById(targetId);
if (target) {
window.scrollTo({
top: target.offsetTop - 70,
behavior: 'smooth'
});
}
});
});
// IntersectionObserver for active section tracking
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
var id = entry.target.getAttribute('id');
navLinks.forEach(function(link) {
link.classList.remove('active');
if (link.getAttribute('data-section') === id) {
link.classList.add('active');
}
});
}
});
}, {
threshold: 0.3,
rootMargin: '-20% 0px -60% 0px'
});
sections.forEach(function(section) {
observer.observe(section);
});
}
/* =========================================
SKILL GAUGES: Scroll-triggered animation
========================================= */
function initSkillGauges() {
var skillsSection = document.getElementById('skills');
if (!skillsSection) return;
var skillItems = skillsSection.querySelectorAll('.skill-item');
var circumference = 2 * Math.PI * 34; // ~213.628
var animated = false;
var gaugeObserver = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting && !animated) {
animated = true;
gaugeObserver.unobserve(entry.target);
skillItems.forEach(function(item, index) {
var level = parseInt(item.getAttribute('data-level'), 10);
var progressCircle = item.querySelector('.skill-progress');
if (!progressCircle) return;
var targetOffset = circumference * (1 - level / 100);
// Stagger each gauge by 100ms
setTimeout(function() {
progressCircle.style.strokeDashoffset = targetOffset;
}, index * 100);
});
}
});
}, {
threshold: 0.15
});
gaugeObserver.observe(skillsSection);
}
})();
</script>
</body>
</html>