Author SHA1 Message Date
copilot-swe-agent[bot] 9a05b71aea Initial plan 2026-07-04 18:25:17 +00:00
Quinta d683d9ace6 uovo di pasqua 2026-07-04 20:22:07 +02:00
Quinta 64f3207224 Update availability text for consulting roles 2026-06-03 12:48:59 +02:00
Quinta c419d566da experience bullets made outcome-focused, hero copy sharpened, and all CSS
fixes applied.
2026-06-02 12:42:59 +02:00
Quinta 6b8c03dbf4 CSS — col-rule-c1 and col-rule-c2 are now display:none globally so they won't appear at any viewport width. 2026-06-02 01:37:42 +02:00
Quinta 1ad48261b8 Update OS and Role information in page.tsx 2026-06-02 01:20:24 +02:00
QuintaandClaude Sonnet 4.6 56835ec562 redesign: port index.html stone/beige design into Next.js app
Replace Nord dark-mode theme with the stone/beige grid-based design from
index.html. Rewrites globals.css with the full CSS system, updates layout.tsx
fonts to Syne + DM Sans + DM Mono, and ports the entire HTML to page.tsx with
React accordion state. Adds Desktop section with Ryzen 7 9800X3D / RTX 5080
specs and corrects UNRAID server hardware (Core Ultra 5 225, 48GB DDR5,
UNRAID 7.3.1).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 01:14:07 +02:00
Quinta 6d89555256 changed favicon 2025-12-17 23:34:22 +01:00
Quinta 35f1a9b3d2 Update email address and improve personal summary 2025-12-14 19:53:32 +01:00
Quinta 691f1bafcb changed colors 2025-12-08 02:27:30 +01:00
Quinta 1905af853f revamp all 2025-12-08 02:08:23 +01:00
Quinta f4af7ac32c fixes for build 2025-10-15 15:27:24 +02:00
Quinta a9db70a3da revamp 2025-10-15 15:20:24 +02:00
Quinta 5c064b6201 Rename Quintavalle Pietro CV eng-6.pdf to Pietro_Quintavalle_CV.pdf 2025-08-11 17:28:17 +02:00
Quinta b79b813acc Add files via upload 2025-08-11 17:26:52 +02:00
Quinta 0e86e7e075 Delete public/Pietro_Quintavalle_CV.pdf 2025-08-11 17:18:15 +02:00
Quinta 40796c2e88 fixed CV stuff 2025-08-07 15:15:37 +02:00
Quinta 5429a17ed6 fiixes 2025-08-07 15:08:09 +02:00
Quinta 324cc73153 revamp 2025-08-07 13:37:08 +02:00
Quinta b38fe4ab86 Added images 2025-06-26 17:26:59 +02:00
Quinta e7647fb80b refactor: enhance Nord theme styles and update Tailwind configuration 2025-06-26 17:14:20 +02:00
Quinta 5d93919fb2 refactor: update showProjectGallery state initialization and onClose handler for clarity 2025-06-26 17:08:03 +02:00
Quinta 9e4a6a8c95 refactor: add TypeScript types to scrollToSection and ProjectGallery components 2025-06-26 17:05:33 +02:00
Quinta 45db885cf3 refactor: update ESLint rules, remove unused Portfolio component 2025-06-26 17:02:24 +02:00
Quinta d8c6bd6586 refactor: update CSS variables and styles for Nord theme, add custom scrollbar and smooth scrolling 2025-06-26 16:56:04 +02:00
Quinta 73677afdbe refactor: clean up formatting and improve code readability in Portfolio component 2025-06-26 16:50:57 +02:00
Quinta 554bf9ba93 pkg lock 2025-06-26 16:49:10 +02:00
Quinta 88be8d7aca Merge pull request #3 from Quinta0/refactor
static export
2025-06-26 16:26:02 +02:00
Quinta 01271f56b5 Merge pull request #2 from Quinta0/refactor
fixes
2025-06-26 16:21:04 +02:00
Quinta 0da3a9fe76 Merge pull request #1 from Quinta0/refactor
update
2025-06-26 15:49:03 +02:00
Quinta 09630a949c Update page.tsx 2025-06-26 15:30:28 +02:00
28 changed files with 10281 additions and 1333 deletions
+5 -2
View File
@@ -1,8 +1,11 @@
{
"extends": ["next/core-web-vitals", "next/typescript"],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"react-hooks/exhaustive-deps": "warn"
"react-hooks/exhaustive-deps": "warn",
"react/no-unescaped-entities": "off",
"@next/next/no-img-element": "warn",
"jsx-a11y/alt-text": "warn"
}
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+445 -73
View File
@@ -2,91 +2,463 @@
@tailwind components;
@tailwind utilities;
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--background: #ffffff;
--foreground: #171717;
--stone:#E8E5DF;
--stone2:#DDD9D2;
--stone3:#D4D0C9;
--navy:#1C2033;
--navy2:#252A42;
--mid:#626880;
--muted:#959BAF;
--line:rgba(28,32,51,0.11);
--line2:rgba(28,32,51,0.18);
--line3:rgba(28,32,51,0.26);
--g1:240px;
--g2:calc(100% - 480px);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
html{scroll-behavior:smooth}
body{
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
background:var(--stone);
color:var(--navy);
font-family:'DM Sans',system-ui,sans-serif;
font-size:15px;
line-height:1.6;
-webkit-font-smoothing:antialiased;
overflow-x:hidden;
}
@layer utilities {
.text-balance {
text-wrap: balance;
.col-rule-l,.col-rule-r{
position:fixed;top:0;bottom:0;width:1px;
background:var(--line2);z-index:200;pointer-events:none;
}
.col-rule-l{left:var(--g1)}
.col-rule-r{right:var(--g1)}
.col-rule-c1{
position:fixed;top:0;bottom:0;width:1px;
background:var(--line);z-index:200;pointer-events:none;
left:calc(var(--g1) + var(--g2)/3);display:none;
}
.col-rule-c2{
position:fixed;top:0;bottom:0;width:1px;
background:var(--line);z-index:200;pointer-events:none;
left:calc(var(--g1) + var(--g2)*2/3);display:none;
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
nav{
position:sticky;top:0;z-index:300;
background:var(--stone);
border-bottom:1px solid var(--line3);
display:flex;align-items:stretch;
height:52px;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
.nav-logo-wrap{
width:var(--g1);flex-shrink:0;
display:flex;align-items:center;
padding:0 28px;
border-right:1px solid var(--line3);
gap:9px;
text-decoration:none;color:var(--navy);
}
.nav-logosvg{width:22px;height:22px}
.nav-name{
font-family:'Syne',sans-serif;
font-size:14px;font-weight:700;
letter-spacing:-.01em;
}
.nav-name sup{
font-size:8px;font-weight:500;
opacity:.5;vertical-align:super;
letter-spacing:.06em;font-family:'DM Sans',sans-serif;
}
.nav-links{display:flex;align-items:stretch;flex:1}
.nav-links a{
display:flex;align-items:center;
padding:0 22px;
font-size:13px;color:var(--mid);
text-decoration:none;
border-right:1px solid var(--line);
transition:color .15s;
font-weight:400;
letter-spacing:.01em;
}
.nav-links a:hover{color:var(--navy)}
.nav-socials{
display:flex;align-items:stretch;
width:var(--g1);
border-left:1px solid var(--line3);
}
.nav-socials a{
display:flex;align-items:center;justify-content:center;
padding:0 22px;
color:var(--mid);text-decoration:none;
border-left:1px solid var(--line);
transition:color .15s;
}
.nav-socials a:hover{color:var(--navy)}
.nav-socials svg{width:16px;height:16px;fill:currentColor}
.strip{
background:#D0CCC5;
border-bottom:1px solid var(--line3);
display:flex;align-items:center;justify-content:center;gap:12px;
padding:9px 24px;font-size:12.5px;
color:var(--mid);
}
.strip-pill{
background:var(--navy);color:var(--stone);
border-radius:20px;padding:2px 10px;
font-size:10.5px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;
}
.strip a{color:var(--navy);text-decoration:none;font-weight:500;display:flex;align-items:center;gap:5px}
.strip-arr{opacity:.45;font-size:14px}
.row{
display:grid;
grid-template-columns:var(--g1) 1fr var(--g1);
border-bottom:1px solid var(--line3);
}
.row-inner{padding:0}
.row-gutter-l{border-right:1px solid var(--line3)}
.row-gutter-r{border-left:1px solid var(--line3)}
.hero-row{
display:grid;
grid-template-columns:var(--g1) 1fr 300px var(--g1);
border-bottom:1px solid var(--line3);
min-height:340px;
}
.hero-gutter{border-right:1px solid var(--line3)}
.hero-gutter-r{border-left:1px solid var(--line3)}
.hero-main{
padding:60px 52px 52px;
border-right:1px solid var(--line3);
}
.status-row{
display:inline-flex;align-items:center;gap:8px;
font-size:12px;color:var(--mid);font-weight:500;
letter-spacing:.03em;margin-bottom:22px;
}
.green-dot{width:7px;height:7px;border-radius:50%;background:#4CAF7D;position:relative;flex-shrink:0}
.green-dot::after{content:'';position:absolute;inset:-3px;border-radius:50%;background:rgba(76,175,125,.2);animation:gpulse 2.2s ease-in-out infinite}
@keyframes gpulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.7);opacity:0}}
.hero-name{
font-family:'Syne',sans-serif;
font-size:clamp(36px,4vw,56px);
font-weight:700;
line-height:1.03;
letter-spacing:-.035em;
color:var(--navy);
margin-bottom:6px;
}
.hero-role{
font-family:'DM Sans',sans-serif;
font-size:clamp(14px,1.5vw,16px);
color:var(--mid);
font-weight:300;
font-style:italic;
letter-spacing:.01em;
margin-bottom:22px;
}
.hero-lead{
font-size:14.5px;color:var(--mid);line-height:1.68;
max-width:500px;margin-bottom:36px;font-weight:300;
}
.hero-thesis{
font-size:13.5px;color:var(--navy);line-height:1.55;
max-width:480px;margin-bottom:16px;font-weight:500;
font-style:italic;
}
.now-line{
display:inline-flex;align-items:flex-start;gap:7px;
font-size:12px;color:var(--mid);line-height:1.5;
max-width:480px;margin-bottom:20px;
padding:8px 12px;background:var(--stone2);
border:1px solid var(--line3);border-radius:4px;
}
.now-line strong{color:var(--navy);font-weight:600}
.now-dot{color:#4CAF7D;font-size:8px;flex-shrink:0;margin-top:4px}
.status-badge{
display:inline-flex;align-items:center;gap:7px;
margin-bottom:22px;text-decoration:none;
font-size:12px;color:var(--mid);font-family:'DM Mono',monospace;
}
.status-badge img{height:20px;display:block;border-radius:2px}
.status-badge span{border-bottom:1px solid var(--line3)}
.status-badge:hover span{color:var(--navy);border-color:var(--line2)}
.btns{display:flex;gap:9px;flex-wrap:wrap}
.btn{
display:inline-flex;align-items:center;gap:7px;
border-radius:3px;padding:8px 16px;
font-size:13px;font-weight:500;font-family:'DM Sans',sans-serif;
text-decoration:none;cursor:pointer;border:none;
transition:all .14s;letter-spacing:.01em;
}
.btn-d{background:var(--navy);color:var(--stone)}
.btn-d:hover{background:var(--navy2)}
.btn-o{background:transparent;color:var(--navy);border:1px solid var(--line3)}
.btn-o:hover{background:var(--stone2)}
.hero-stats{padding:32px 28px;display:flex;flex-direction:column}
.stat{padding:15px 0;border-bottom:1px solid var(--line);display:flex;gap:11px;align-items:flex-start}
.stat:first-child{border-top:1px solid var(--line)}
.stat-ico{font-size:15px;opacity:.35;flex-shrink:0;margin-top:2px}
.stat-val{font-size:13.5px;font-weight:600;color:var(--navy);letter-spacing:-.01em}
.stat-lbl{font-size:11px;color:var(--muted);margin-top:1px}
.sec{
display:grid;
grid-template-columns:var(--g1) 1fr var(--g1);
border-bottom:1px solid var(--line3);
}
.sec-g{border-right:1px solid var(--line3)}
.sec-gr{border-left:1px solid var(--line3)}
.sec-body{padding:48px 52px}
.slbl{
font-size:10.5px;font-weight:600;letter-spacing:.12em;
text-transform:uppercase;color:var(--muted);
margin-bottom:30px;font-family:'DM Mono',monospace;
}
.sec-h{
font-family:'Syne',sans-serif;font-weight:700;
font-size:clamp(22px,2.4vw,32px);
letter-spacing:-.025em;color:var(--navy);
margin-bottom:12px;line-height:1.1;
}
@layer base {
* {
@apply border-border;
.about-split{
display:grid;
grid-template-columns:var(--g1) 1fr 1fr var(--g1);
border-bottom:1px solid var(--line3);
}
body {
@apply bg-background text-foreground;
.about-g{border-right:1px solid var(--line3)}
.about-gr{border-left:1px solid var(--line3)}
.about-left{padding:44px 48px;border-right:1px solid var(--line3)}
.about-right{padding:44px 48px}
.terminal{background:var(--navy);border-radius:6px;overflow:hidden}
.t-bar{background:rgba(255,255,255,.05);padding:10px 15px;display:flex;align-items:center;gap:7px;border-bottom:1px solid rgba(255,255,255,.06)}
.td{width:10px;height:10px;border-radius:50%}
.td.r{background:#FF5F57}.td.y{background:#FEBC2E}.td.g{background:#28C840}
.t-title{flex:1;text-align:center;font-size:10.5px;color:rgba(255,255,255,.2);font-family:'DM Mono',monospace}
.t-body{
padding:18px 20px;
font-family:'DM Mono',monospace;
font-size:13px;line-height:1.85;
color:rgba(232,229,223,.75);
}
.t-p{color:#5FCE8A}.t-c{color:rgba(232,229,223,.35)}
.t-k{color:#82B4D0}.t-v{color:rgba(232,229,223,.8)}
.cur{display:inline-block;width:7px;height:12px;background:rgba(232,229,223,.5);vertical-align:middle;animation:bl 1s step-end infinite}
@keyframes bl{0%,100%{opacity:1}50%{opacity:0}}
.t-stage{position:relative;min-height:400px}
.t-pane{position:absolute;inset:0;opacity:1;transition:opacity .55s ease}
.t-pane.t-hidden{opacity:0;pointer-events:none}
.t-cmd-row{display:flex;align-items:center;gap:6px;margin-top:2px}
.t-input{background:transparent;border:none;outline:none;color:rgba(232,229,223,.85);font:inherit;flex:1;caret-color:#5FCE8A}
.t-hint{color:rgba(232,229,223,.28);font-size:11px;margin-top:10px}
.t-pane.t-egg-pane{
display:flex;flex-direction:column;align-items:center;justify-content:center;
text-align:center;gap:12px;padding:18px 20px;
opacity:0;pointer-events:none;
}
.t-pane.t-egg-pane.t-egg-show{opacity:1;pointer-events:auto}
.t-ascii-scroll{max-width:100%;overflow-x:auto}
.t-ascii{white-space:pre;margin:0;line-height:1.05;font-family:'DM Mono',monospace}
.t-ascii-moria{
font-size:clamp(5px,2.1vw,8.5px);
color:#7ee7ff;
text-shadow:0 0 4px #7ee7ff,0 0 14px rgba(126,231,255,.55),0 0 28px rgba(126,231,255,.3);
animation:t-pulse-cyan 2.2s ease-in-out infinite;
}
.t-ascii-balrog{
font-size:clamp(3px,1vw,4.8px);
color:#ff7a3d;
text-shadow:0 0 4px #ff7a3d,0 0 14px rgba(255,122,61,.6),0 0 30px rgba(255,80,20,.35);
animation:t-pulse-fire 1.4s ease-in-out infinite;
}
@keyframes t-pulse-cyan{0%,100%{filter:brightness(1)}50%{filter:brightness(1.35)}}
@keyframes t-pulse-fire{0%,100%{filter:brightness(1) hue-rotate(0deg)}50%{filter:brightness(1.4) hue-rotate(-6deg)}}
.t-egg-cap{font-size:11px;letter-spacing:.02em;font-family:'DM Mono',monospace;color:rgba(232,229,223,.45)}
.t-egg-cap-moria{color:#a9e9f7}
.t-egg-cap-balrog{color:#ffb08a}
.about-right p{font-size:14px;color:var(--mid);line-height:1.72;font-weight:300;margin-bottom:14px}
.about-right strong{color:var(--navy);font-weight:500}
.sk4{
display:grid;grid-template-columns:repeat(4,1fr);
border:1px solid var(--line3);border-radius:5px;
overflow:hidden;background:var(--line);
}
.sk{
background:var(--stone);padding:22px 20px;
border-right:1px solid var(--line);border-bottom:1px solid var(--line);
transition:background .14s;overflow:hidden;
}
.sk:hover{background:var(--stone2)}
.sk-ico{font-size:17px;margin-bottom:10px;opacity:.38}
.sk-t{font-size:11.5px;font-weight:600;color:var(--navy);letter-spacing:.01em;margin-bottom:10px;font-family:'DM Mono',monospace}
.tags{display:flex;flex-wrap:wrap;gap:3px}
.tag{
background:rgba(28,32,51,.06);border:1px solid var(--line);
border-radius:2px;padding:2px 6px;
font-size:10.5px;color:var(--mid);
font-family:'DM Mono',monospace;
}
.hl3{
display:grid;grid-template-columns:repeat(3,1fr);
border:1px solid var(--line3);border-radius:5px;
overflow:hidden;background:var(--line);
margin-bottom:24px;
}
.hl{
background:var(--stone);padding:24px 20px;
border-right:1px solid var(--line);border-bottom:1px solid var(--line);
transition:background .14s;overflow:hidden;
}
.hl:hover{background:var(--stone2)}
.hl-hd{display:flex;align-items:center;gap:9px;margin-bottom:9px}
.hl-ico{font-size:18px;opacity:.4}
.hl-t{font-size:14px;font-weight:600;color:var(--navy);letter-spacing:-.01em}
.hl-d{font-size:12.5px;color:var(--mid);line-height:1.62;margin-bottom:11px;font-weight:300}
.specs{display:flex;flex-wrap:wrap;gap:3px}
.spec{background:rgba(28,32,51,.05);border:1px solid var(--line);border-radius:2px;padding:2px 6px;font-size:10.5px;color:var(--mid);font-family:'DM Mono',monospace}
.hw-table{width:100%;border-collapse:collapse;margin-bottom:24px;font-size:12.5px}
.hw-table th{text-align:left;font-weight:600;font-family:'DM Mono',monospace;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);padding:8px 12px;border-bottom:1px solid var(--line3);background:var(--stone2)}
.hw-table td{padding:9px 12px;border-bottom:1px solid var(--line);color:var(--mid);vertical-align:top}
.hw-table td:first-child{color:var(--navy);font-weight:500;width:140px;min-width:100px;font-family:'DM Mono',monospace;font-size:11.5px}
.hw-table tr:last-child td{border-bottom:none}
.hw-table tr:hover td{background:var(--stone2)}
.note{
padding:16px 18px;
background:var(--stone2);border:1px solid var(--line3);border-radius:5px;
display:flex;gap:12px;align-items:flex-start;
}
.note-ico{font-size:16px;opacity:.38;flex-shrink:0;margin-top:2px}
.note-t{font-size:13px;font-weight:500;color:var(--navy);margin-bottom:2px}
.note-d{font-size:12.5px;color:var(--mid);line-height:1.6;font-weight:300}
.pr3{
display:grid;grid-template-columns:repeat(3,1fr);
border:1px solid var(--line3);border-radius:5px;
overflow:hidden;background:var(--line);
}
.pr{
background:var(--stone);padding:24px 20px;
border-right:1px solid var(--line);border-bottom:1px solid var(--line);
display:flex;flex-direction:column;
transition:background .14s;
}
.pr:hover{background:var(--stone2)}
.pr-type{font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:7px;font-family:'DM Mono',monospace}
.pr-t{font-size:14.5px;font-weight:600;color:var(--navy);letter-spacing:-.01em;margin-bottom:7px;line-height:1.3}
.pr-d{font-size:12.5px;color:var(--mid);line-height:1.62;flex:1;margin-bottom:14px;font-weight:300}
.pr-tags{display:flex;flex-wrap:wrap;gap:3px}
.pr-tag{background:rgba(28,32,51,.05);border:1px solid var(--line);border-radius:2px;padding:2px 6px;font-size:10.5px;color:var(--muted);font-family:'DM Mono',monospace}
.badge{display:inline-flex;align-items:center;gap:4px;border-radius:2px;padding:2px 7px;font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;margin-bottom:8px;font-family:'DM Mono',monospace}
.b-live{background:rgba(76,175,125,.1);color:#2A7A4F}
.b-dev{background:rgba(210,140,0,.1);color:#8A5E00}
.b-oss{background:rgba(28,32,51,.07);color:var(--mid)}
.gh-list{border:1px solid var(--line3);border-radius:5px;overflow:hidden}
.gh-item{
display:flex;align-items:center;justify-content:space-between;
padding:14px 18px;background:var(--stone);
border-bottom:1px solid var(--line);
transition:background .14s;gap:16px;
}
.gh-item:last-child{border-bottom:none}
.gh-item:hover{background:var(--stone2)}
.gh-n{font-size:13.5px;font-weight:500;color:var(--navy);margin-bottom:2px;font-family:'DM Mono',monospace}
.gh-d{font-size:12px;color:var(--mid);font-weight:300}
.gh-r{display:flex;align-items:center;gap:8px;flex-shrink:0}
.gh-lang{font-size:10.5px;font-family:'DM Mono',monospace;color:var(--muted);padding:2px 7px;border:1px solid var(--line);border-radius:2px}
.gh-stars{font-size:10.5px;font-family:'DM Mono',monospace;color:#B08A2E;padding:2px 7px;border:1px solid var(--line);border-radius:2px}
.gh-lnk{color:var(--mid);text-decoration:none;font-size:12px;font-family:'DM Mono',monospace;transition:color .14s}
.gh-lnk:hover{color:var(--navy)}
.acc{border-top:1px solid var(--line3)}
.acc-item{border-bottom:1px solid var(--line3)}
.acc-btn{
width:100%;background:none;border:none;
padding:20px 0;display:flex;align-items:flex-start;
justify-content:space-between;cursor:pointer;
text-align:left;font-family:inherit;gap:20px;
transition:background .15s;
}
.acc-btn:hover .acc-t{color:var(--navy2)}
.acc-l{flex:1}
.acc-t{font-size:16.5px;font-weight:500;color:var(--navy);letter-spacing:-.015em;line-height:1.25;margin-bottom:3px;transition:color .14s;font-family:'Syne',sans-serif}
.acc-s{font-size:12px;color:var(--muted);font-family:'DM Mono',monospace}
.acc-r2{display:flex;align-items:center;gap:12px;flex-shrink:0}
.acc-per{font-size:11.5px;color:var(--muted);font-family:'DM Mono',monospace}
.acc-chev{width:17px;height:17px;color:var(--muted);transition:transform .2s}
.acc-item.open .acc-chev{transform:rotate(180deg)}
.acc-body{display:none;padding:0 0 22px;color:var(--mid);font-size:13.5px;line-height:1.7;font-weight:300}
.acc-item.open .acc-body{display:block}
.acc-body ul{list-style:none;margin-top:10px;display:flex;flex-direction:column;gap:6px}
.acc-body li{display:flex;gap:11px;align-items:flex-start}
.acc-body li::before{content:'--';color:var(--muted);flex-shrink:0;font-family:'DM Mono',monospace;font-size:10px;margin-top:4px}
.econ-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:12px}
.econ-tag{background:rgba(28,32,51,.06);border:1px solid var(--line3);border-radius:2px;padding:3px 8px;font-size:11.5px;color:var(--navy);font-weight:500}
.langs{
display:grid;grid-template-columns:repeat(5,1fr);
border:1px solid var(--line3);border-radius:5px;overflow:hidden;
background:var(--line);margin-top:28px;
}
.langc{background:var(--stone);padding:16px 12px;text-align:center;border-right:1px solid var(--line)}
.langc:last-child{border-right:none}
.lang-n{font-size:13px;font-weight:600;color:var(--navy);margin-bottom:3px;font-family:'Syne',sans-serif}
.lang-l{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.07em;font-family:'DM Mono',monospace}
.certs{display:flex;gap:9px;flex-wrap:wrap;margin-top:24px}
.cert{display:flex;align-items:center;gap:8px;border:1px solid var(--line3);border-radius:4px;padding:10px 14px;font-size:12.5px;color:var(--navy)}
.contact{
display:grid;
grid-template-columns:var(--g1) 1fr var(--g1);
background:var(--navy);border-top:1px solid var(--line3);
}
.contact-g{border-right:1px solid rgba(255,255,255,.07)}
.contact-gr{border-left:1px solid rgba(255,255,255,.07)}
.contact-body{padding:64px 52px}
.contact-body h2{
font-family:'Syne',sans-serif;font-weight:700;
font-size:clamp(28px,3.2vw,46px);letter-spacing:-.03em;
margin-bottom:12px;line-height:1.06;color:var(--stone);
}
.contact-body p{font-size:14px;color:rgba(232,229,223,.45);margin-bottom:32px;line-height:1.65;max-width:440px;font-weight:300}
.contact-ctas{display:flex;gap:9px;flex-wrap:wrap}
.btn-li{background:var(--stone);color:var(--navy);border-radius:3px;padding:8px 16px;font-size:13px;font-weight:500;font-family:inherit;text-decoration:none;transition:all .14s;word-break:break-all}
.btn-li:hover{background:#F0EDE7}
.btn-gh{background:transparent;color:rgba(232,229,223,.55);border:1px solid rgba(255,255,255,.14);border-radius:3px;padding:8px 16px;font-size:13px;font-weight:500;font-family:inherit;text-decoration:none;transition:all .14s}
.btn-gh:hover{border-color:rgba(255,255,255,.32);color:var(--stone)}
footer{
background:var(--navy);border-top:1px solid rgba(255,255,255,.06);
display:grid;grid-template-columns:var(--g1) 1fr var(--g1);
}
.foot{grid-column:2/3;display:flex;align-items:center;justify-content:space-between;padding:18px 52px}
footer p,footer a{font-size:12px;color:rgba(232,229,223,.24);text-decoration:none;font-family:'DM Mono',monospace}
@media(max-width:1100px){
:root{--g1:20px}
.col-rule-l,.col-rule-r,.col-rule-c1,.col-rule-c2{display:none}
.nav-socials{width:auto}
.hero-row{grid-template-columns:20px 1fr 260px 20px}
.sk4,.hl3,.pr3{grid-template-columns:repeat(2,1fr)}
.langs{grid-template-columns:repeat(3,1fr)}
}
@media(max-width:700px){
nav .nav-links{display:none}
.hero-row{grid-template-columns:16px 1fr 16px}
.hero-stats{display:none}
.about-split{grid-template-columns:16px 1fr 16px}
.about-left{display:none}
.sk4,.hl3,.pr3{grid-template-columns:1fr}
.langs{grid-template-columns:repeat(2,1fr)}
.sec-body,.about-right,.contact-body,.hero-main{padding:32px 20px}
}
+12 -17
View File
@@ -2,31 +2,26 @@ import type { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
title: 'Pietro Quintavalle - Economics Student & Developer',
description: 'Economics student at USI with extensive data analytics and web development expertise. Google-certified in Data Analytics and IT Support.',
keywords: ['Pietro Quintavalle', 'Economics', 'Data Analytics', 'Web Development', 'Switzerland', 'USI'],
title: 'Pietro Quintavalle',
description: 'Systems Engineer · Developer · Homelabber. Economics student at USI Lugano with 5+ years building production-grade infrastructure.',
keywords: ['Pietro Quintavalle', 'Systems Engineer', 'Homelab', 'SysAdmin', 'Economics', 'USI', 'Switzerland'],
authors: [{ name: 'Pietro Quintavalle' }],
openGraph: {
title: 'Pietro Quintavalle - Economics Student & Developer',
description: 'Economics student at USI with extensive data analytics and web development expertise.',
url: 'https://your-domain.com',
siteName: 'Pietro Quintavalle Portfolio',
title: 'Pietro Quintavalle',
description: 'Systems Engineer · Developer · Homelabber',
siteName: 'Pietro Quintavalle',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: 'Pietro Quintavalle - Economics Student & Developer',
description: 'Economics student at USI with extensive data analytics and web development expertise.',
},
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet" />
</head>
<body>{children}</body>
</html>
)
+1292
View File
File diff suppressed because it is too large Load Diff
+639 -228
View File
@@ -1,235 +1,646 @@
"use client";
import Link from "next/link"
import { Button } from "@/components/ui/button"
import {JSX, SVGProps, useState} from "react"
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import Image from "next/image"
import {Github, Linkedin, MenuIcon} from "lucide-react";
export default function Home() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const skills = [
"IT Support", "Data Analysis", "Python", "R", "SQL", "Network Architecture",
"Network Security", "Cloud Computing", "Data Visualization", "Machine Learning",
"Statistical Analysis", "Database Management", "Linux", "Git", "Agile Methodologies",
"Project Management", "Business Analysis", "Critical Thinking"
];
"use client"
const certifications = [
{
name: "Google IT Support Professional Certificate",
issuer: "Google",
image: "./certificates/Coursera_RZ1V4RU6JP2D.png"
},
{
name: "Google Data Analytics Professional Certificate",
issuer: "Google",
image: "./certificates/Coursera_EN181NFH7VYT.png"
},
{
name: "IBM Python for Data Science, AI & Development",
issuer: "IBM",
image: "./certificates/Coursera_QT9WT5VCHJNU.png"
}
];
return (
<div className="bg-[#00004d] text-white font-['Segoe UI', 'FiraCode'] min-h-screen">
<header className="py-4 px-6 flex items-center justify-between border-b border-[#4d4dff]">
<div className="flex items-center gap-4">
<TerminalIcon className="w-8 h-8" aria-hidden="true" />
<h1 className="text-2xl font-semibold">Pietro Quintavalle</h1>
</div>
<nav className="hidden md:flex items-center gap-6">
{["About", "Skills", "Portfolio", "Certifications"].map((item) => (
<Link key={item} href={`#${item.toLowerCase()}`} className="hover:text-[#4d4dff] transition-colors p-2">
{item}
</Link>
))}
</nav>
<button
className="md:hidden text-white p-2"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-label={mobileMenuOpen ? "Close menu" : "Open menu"}
>
<MenuIcon className="h-6 w-6" aria-hidden="true" />
</button>
</header>
import { useState } from 'react'
{/* Mobile menu */}
{mobileMenuOpen && (
<div className="md:hidden bg-[#0000b3] py-2">
{["About", "Skills", "Certifications", "Portfolio"].map((item) => (
<Link
key={item}
href={`#${item.toLowerCase()}`}
className="block py-2 px-4 text-white hover:bg-[#4d4dff] transition-colors"
onClick={() => setMobileMenuOpen(false)}
>
{item}
</Link>
))}
</div>
)}
<main className="px-6 py-12">
<section id="about" className="mb-12">
<h2 className="text-3xl font-semibold mb-4">About Me</h2>
<div className="prose prose-invert max-w-3xl">
<p>
Hello! I&apos;m Pietro Quintavalle, a dedicated IT hobbyist with a strong foundation in IT support and
data analytics who&apos;s currently pursuing a bachelor in Economics.
My passion lies in leveraging technology to solve complex problems and derive meaningful insights from
data.
With a combination of technical skills and analytical thinking, I&apos;m equipped to tackle challenges
in both IT infrastructure and data-driven decision-making.
</p>
</div>
<div className="flex space-x-4 mt-4">
<Link href="https://github.com/Quinta0" passHref>
<a aria-label="GitHub Profile">
<Button variant="outline" size="icon" className="p-2">
<Github className="h-6 w-6" aria-hidden="true"/>
</Button>
</a>
</Link>
<Link href="https://linkedin.com/in/pietro-quintavalle-996b96267" passHref>
<a aria-label="LinkedIn Profile">
<Button variant="outline" size="icon" className="p-2">
<Linkedin className="h-6 w-6" aria-hidden="true"/>
</Button>
</a>
</Link>
</div>
</section>
<section id="skills" className="mb-16">
<h2 className="text-3xl font-bold mb-4">Skills</h2>
<div className="relative overflow-hidden h-16">
<div className="absolute flex animate-marquee">
{[...skills, ...skills].map((skill, index) => (
<Badge key={index} variant="secondary" className="mx-2 text-md text-center py-1 px-3">
{skill}
</Badge>
))}
</div>
</div>
</section>
<section id="portfolio" className="mb-12">
<h2 className="text-3xl font-semibold mb-4">Portfolio</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{[
{
title: "password-generator",
description: "A unique and interactive password generator built with Next.js and React. ",
technologies: ["Nextjs", "Encryption", "Typescript"],
githubLink: "https://github.com/Quinta0/password-generator"
},
{
title: "Valgo",
description: "A website for visualizing sorting algorithms for the visual learners out there.",
technologies: ["Typescript", "React", "Algorithms"],
githubLink: "https://github.com/Quinta0/valgo"
},
{
title: "Gravity",
description: "3D Gravity Simulator is a C++ program that visualizes the gravitational interactions between celestial bodies",
technologies: ["C++", "OpenGL", "Physics", "3D", "Simulation", "Cmake", "VCPKG"],
githubLink: "https://github.com/Quinta0/gravity"
},
{
title: "Boston Housing Price Prediction",
description: "A simulation of the Boston Housing Price Prediction project that involves building a machine learning model to predict housing prices. ",
technologies: ["Python", "Machine Learning", "Data Analysis", "Data Visualization", "Jupyter Notebook", "Probability", "Statistics"],
githubLink: "https://github.com/Quinta0/BostonHousing"
},
{
title: "Forest Fire Simulation",
description: "An assignment for the Probability & Statistics course at USI that aims to model the spread of forest fires over a grid representing a forest landscape.",
technologies: ["Statistics", "Python", "Jupyter Notebook", "Simulation", "Probability"],
githubLink: "https://github.com/Quinta0/ForestFire"
},
{
title: "AequitasIQ",
description: "An intelligent financial analysis tool that creates an interactive dashboard to visualize personal income and expenses.",
technologies: ["Python", "Pandas", "LLM", "Ollama", "Plotly", "NumPy", "LangChain"],
githubLink: "https://github.com/Quinta0/AequitasIQ-.git"
}
].map((project, index) => (
<div key={index} className="bg-[#0000b3] rounded-md p-4">
<h3 className="text-xl font-semibold mb-2">{project.title}</h3>
<p className="mb-4">{project.description}</p>
<div className="mb-4">
{project.technologies.map((tech, techIndex) => (
<span key={techIndex}
className="inline-block bg-[#4d4dff] text-white rounded-full px-3 py-1 text-sm font-semibold mr-2 mb-2">
{tech}
</span>
))}
</div>
<div className="flex justify-end">
<Link
href={project.githubLink}
className="inline-flex items-center rounded-md bg-[#4d4dff] px-4 py-2 text-sm font-medium text-white shadow-sm transition-colors hover:bg-[#3b3bd9] focus:outline-none focus:ring-2 focus:ring-[#4d4dff] focus:ring-offset-2"
aria-label={`View ${project.title} on GitHub`}
>
View on GitHub
</Link>
</div>
</div>
))}
</div>
</section>
<section id="certifications" className="mb-16">
<h2 className="text-3xl font-semibold mb-4">Certifications</h2>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{certifications.map((cert, index) => (
<Card key={index} className="overflow-hidden bg-[#0000b3] text-white">
<Image src={cert.image} alt={cert.name} width={400} height={300}
className="w-full object-contain"/>
<CardHeader>
<CardTitle>{cert.name}</CardTitle>
<CardDescription className="text-gray-300">{cert.issuer}</CardDescription>
</CardHeader>
</Card>
))}
</div>
</section>
</main>
<footer className="bg-[#0000b3] py-6 px-6 text-center text-sm">
<p>&copy; 2024 Pietro Quintavalle. All rights reserved.</p>
</footer>
<style jsx>{`
@keyframes marquee {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}
const DURIN_ART = ` ______________ _ _,-----------._ ___
| | (_,.- _,-'_,-----------._\`-._ _)_)
| THE _ _ _ | | ,'_,-' ___________ \`-._\`.
| | / \\|_)| \\ | \`' ,',' _,-'___________\`-._ \`.\`.
| |__\\_/| \\|_/ | ,',' ,'_,-' . \`-._\`. \`.\`.
| | /,' ,',' >|< \`.\`. \`.\\
| OF THE _ _ | // ,',' >< ,^. >< \`.\`. \\\\
| |_)||\\|/_(_ | // /,' >< / | \\ >< \`.\\ \\\\
| | \\|| |\\_|_) | // // >< \\/\\^/\\/ >< \\\\ \\\\
|______________| ;; ;; \`---' :: ::
|| || (____ || ||
DOORS OF DURIN _||__||_ ,'----. _||__||_
(o.____.o)____ \`---' ____(o.____.o)
| | /,--.) (,--.\\ | |
| |(( -\`___ ___\` ))| |
| | \\\\,'', \`. .' .\`\`.// | |
| | // (___,'. .'.___) \\\\ | |
/| | ;;)) ____) . . (____ ((\\\\ | |\\
\\|.__ | ||/ .'.--.\\/ \`/,--.\`. \\;: | __,|;
|\`-,\`;.| :/ /,' \`)-' \`-(' \`.\\ \\: |.;',-'|
| \`.. ' / \\__.' \`.__/ \\ \` ,.' |
| |,\\ /, ,\\ /,| |
| ||: : ) . ( : :|| |
/| |:; |/ . ./|\\, , \\| :;| |\\
\\|.__ |/ : ,/- <--:--> ,\\. ; \\| __,|;
|\`-.\`\`: \`'/-. '\\|/\` ,-\\\`; ;'',-'|
| \`.. ,' \`' ' \` \`. ,.' |
| || : : || |
| || | | || |
| || | | || |
| |' | _ | \`| |
| | | '|)) | | |
;____: \`._ \`' _,' ;____:
{______} \\___________________/ {______}
SSt |______|_______________________________|______|`
.animate-marquee {
animation: marquee 30s linear infinite;
}
`}</style>
</div>
)
}
const BALROG_ART = `############################+##++++++########+++++-----. ------------ . - --.--- --.--.------...---------------..---
###############################+++++++####+++++----- --- - ---- -- -. ..- - - -------------------------------
##########################+++++++++++++#####+++---. . -- - - - - .--.-----------------------------
########################+++#+++++++++######++++-. ..--++++++------..----- - ------------.- ---------
###########################+##+++++++++++##++- -++++++++--+--------.- .. ...-.. - ----------------.---------
############################+++++++++++++#++-+ - -++++++++++++------+++---++-------------. -.----------.--------
#############################++++++++++++#++++- --- ++++++++++++++++--+++++++------------------------- -------------.---
#############################++++++++++++++++--- .+##+++++++++++++++++++++++++---------------------------.----------------
############++#+###+#+++#####++++++++++++++-- --- -- -+###+++++++++++++++++++++++++----+---------------------------------------
###########++++++++++++#++++++++++++++--..---- . .-- .. -+#####++++++++++++++++++++++++-+--++++-------------------------------------
############+++++++++++++++++++++++++++++++++.+ ..--.- +#########+++++++++++++++++++++++++++----+------------------------.-----------
############++++++++++++++++++++++-+++++++++++++.. + .++#####++#++++#++++++++++++++++++++++++++-------+--------------------- ---------
########+#+++++++++++++++++++++---++++-+++++++++.. -+#########-+ .+++++++++++++++++++++++-++++-+++++----+-------------------+--------
+######+#+++++++++++++++++++++-+++++++-++++++++++##+-.+++++++#+ +++++++++++++++++++++++-----+++++++++++-----+--------+-------------
++##+++++++++++++++++++++++++++++++++++++-+++.++++-+ .+++++-++++++++++++++++++++++----+---------------+++++++-----+----------------
++++++++++++++++++++++++++##+++++++++++++++. ++# ++ +-.++++++++++++++++#++++---+------------------------+-+++---+++-+---+-----.----
+++++++++++++++++++++++###++++++++++++++++++-+++- +++ ++++++++++++++#++++-++----+-+----------------------+---++++++++----+--++++----
+++++++++++++++++++++###++++++++++++++++++++++++ +#+#+#+#+++#######+++#----------------------------------+-++----++-+++--+++++++----.
+++++++++++++++++++++++#++++++++++++-++++++++++##++++#++#+++#+++#+#+++++++++--+----------+------------------+-++--++-+--++++++-++-.---
++++++++++++++++++++++++++++++++++++++++++#+++++-++++##+#++##++####+++++++++++-+-----------+------------------+---++++++++++++++-++---
+++++++++++++++++++++++++++++++++++++++--++++++#++++###+#++#+#++++++++++++++++++++---+---------------------------+++++++++++++++++++--
+++++++++++++++++++++++++++++-------++--+++++#++++#+#######+#++++#+#-++-+++++++++++------------------------------+--++++++++++++++++--
+++++++++++++++++++++++++++-------+-----+#+++#+++# #.# #+####+###++--+++++++++++++--------------.-------------+-++--++++++++++++++--
+++++++++++++++++++++++-----------------+++#+--# . - - .+####+##++-----++++++++--+----+-+--------.--------------+-----+++++++++++++--
+++++++++++++++++++++-------++----------#++++# - #. ####+-+-.+--+++++++---+++++++----------------------------.--++++++++++++--
+++++++++++++--+++++-+-------+----------+++##. . + +#+- -++---+ ++++----+++++++++---------------------------..+++++++++++++--
+++++++++++++-++++++---------------------#++ ---. +#- +++-. -++----++-+-+-+++++--------------------------..-+++++++++++++--
++++++++++++++-----+--------------++++++++#+ +...-- + . - ----+-+++--++-+++-----------------------.---++++++++++++++--
++++++++----++--+++----------+++++++++++-++#++. . . ----+----++---+--++-------------------------+ +++++++++++++---
+++++++++---+--------------++++++++++++++++...-. . -----------+---++++------------------------++++++++++++++++---
+++++++---+-++-+++-+-----+++++++++++++++++------ .. ---------+-----+++++-+-----------------++++++++++++++++++----
+++++++-+++-+----++------+++++++++++++++++++++--- ....----+--+------++++-++------------.+.+++++++++++##+++++++----
++++++++++++++++------+++++++++++++++++++++++++--. . ... ++---+--+----++++---++-------.+-.+++++++++++++#######+------
++++---++++------+---++++++++++++++++++++++++++---. ...-.--+--+---++--+------+-+--.-.++.+#+++#+++++###++++++#---------
-++++.--+++++---+----+++++++++++++++++++++++++++-+-... ......---+++-++----++-----+--.---++####+++++###--++####++-----.----
++++-----+----------++++++++++++++++++++++++++++++--.... ..------++++--------------.+- ############+++##+###+---------------
+++-+++------------++++++++++++++++++++++++++++++----..... ..----+. -+---------- ---..+###+####+###-#####++-+--.---------------
+++++++++--.------+++++++++++++++++++++++++++++. -----.... . . ..-++-+--+----------- .#++++#++##+--++++---+-----------.-----------
-----+-+++--------++++++++++++++++++++++++++++++---.--.... . .. ---++-.+------------ .+-++##--#+--++--.-----------------------------
-------+++--------++++#++++++++++++++++++++++-+-------..-. .---+--+++---------. +#-#+#+--+------.---.---.----- ----------------..
-------++++++-+------.+++++++++++++++++-++++++++-- ----..... ..----+--+.++-------- +--#++-------------------------....-.---------..--
------++++++++++++---+--##++++++++++++++++++++---------......-.--+++-.-++---.---. -##++----------------------- ---.....---------......
-++---++++++#+++++##++...+#++++++++ +++++++++---------...-.---.++++...--.-+-----..+#+-----------.---------------.... ......-.........
-------+++-+++#+# ++#+....+##+#++++#+++++++++----++++---...----++++-..+.--++---.+#+-------------------------..... . ....... . ...
++----+++++++-+---###+-+..-##+##+##+++++++++-.--------.--------+-+--------++---.#-----------..----------...... .. . ..
++++-+++++++++++++++##++-..##++++##+++++++++-+----..----+---+--++---------++-------------------------...-. . . ....
++++++++++++++++++++++##-..+++++++ ++++++ +- - --------------+-.---------------- ----------.----------.... . .
-+++++++-+++++++++++++#+--++.++-----------------------. .--- --------------------------------------...--..... ...... .
-++++--++++++++++++++++-++ -++.-------.------------ ------.---------.....--------------------- .--- ....... ......
----------++++--------+++.----------..--------.------------ ...-.-.-.----------------------..---.--. .. ........
-----.-...-------------------------------.------ -----..----...-.........-----------------.. .-.... ....... ... .
........ .--------------------------------------- --... ---......----.. . - ----------.--...-... ......... ..
... ..---------------.--.-------------...-.---------.. ....-...... --- --- ---.-----...... .
...... .-----.-.----------.---------------..--------.--...... . ..----------------.-.. ..
. ...--------------------------...----.--------.-..---.-. .---------------------. . `
function TerminalIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="4 17 10 11 4 5"/>
<line x1="12" x2="20" y1="19" y2="19"/>
const ChevronDown = () => (
<svg className="acc-chev" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9"/>
</svg>
)
export default function Home() {
const [openAcc, setOpenAcc] = useState<Record<string, boolean>>({
'exp-0': true,
'edu-0': true,
})
const toggle = (id: string) => {
setOpenAcc(prev => ({ ...prev, [id]: !prev[id] }))
}
const [cmd, setCmd] = useState('')
const [eggView, setEggView] = useState<'main' | 'moria' | 'balrog'>('main')
const [eggBusy, setEggBusy] = useState(false)
const triggerEgg = (view: 'moria' | 'balrog') => {
if (eggBusy) return
setEggBusy(true)
setEggView(view)
setTimeout(() => {
setEggView('main')
setCmd('')
setEggBusy(false)
}, 4200)
}
const handleTermKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key !== 'Enter' || eggBusy) return
const value = cmd.trim().toLowerCase()
if (value === 'mellon') {
triggerEgg('moria')
} else if (value === 'you shall not pass') {
triggerEgg('balrog')
} else {
setCmd('')
}
}
return (
<>
<div className="col-rule-l"></div>
<div className="col-rule-r"></div>
<div className="col-rule-c1"></div>
<div className="col-rule-c2"></div>
{/* NAV */}
<nav>
<a href="#" className="nav-logo-wrap">
<svg className="nav-logosvg" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="22" height="22" rx="4" fill="#1C2033"/>
<path d="M5 8L11 5.5L17 8V12.8C17 16.5 11 20 11 20C11 20 5 16.5 5 12.8V8Z" stroke="#E8E5DF" strokeWidth="1.3"/>
<path d="M9 11.8L10.8 13.5L14 9.5" stroke="#E8E5DF" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
<span className="nav-name">Pietro<sup>DEV</sup></span>
</a>
<div className="nav-links">
<a href="#skills">Skills</a>
<a href="#homelab">Homelab</a>
<a href="#desktop">Desktop</a>
<a href="#projects">Projects</a>
<a href="#github">GitHub</a>
<a href="#experience">Experience</a>
<a href="#contact">Contact</a>
</div>
<div className="nav-socials">
<a href="https://github.com/Quinta0" target="_blank" rel="noopener" title="GitHub">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12c0 4.42 2.87 8.17 6.84 9.49.5.09.68-.22.68-.48v-1.69C6.72 19.9 6.14 18 6.14 18c-.46-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.61.07-.61 1 .07 1.53 1.03 1.53 1.03.89 1.52 2.34 1.08 2.91.83.09-.65.35-1.08.63-1.33-2.22-.25-4.56-1.11-4.56-4.95 0-1.09.39-1.98 1.03-2.68-.1-.26-.45-1.27.1-2.64 0 0 .84-.27 2.75 1.02A9.56 9.56 0 0 1 12 7.8c.85.004 1.71.115 2.51.337 1.91-1.29 2.75-1.02 2.75-1.02.55 1.37.2 2.38.1 2.64.64.7 1.03 1.59 1.03 2.68 0 3.85-2.34 4.7-4.57 4.95.36.31.68.92.68 1.85v2.74c0 .27.18.58.69.48A10.01 10.01 0 0 0 22 12c0-5.52-4.48-10-10-10Z"/></svg>
</a>
<a href="https://www.linkedin.com/in/pietro-quintavalle-996b96267/" target="_blank" rel="noopener" title="LinkedIn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14m-.5 15.5v-5.3a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.32 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1 1.4 1.4v4.93h2.79M6.88 8.56a1.68 1.68 0 0 0 1.68-1.68c0-.93-.75-1.69-1.68-1.69a1.69 1.69 0 0 0-1.69 1.69c0 .93.76 1.68 1.69 1.68m1.39 9.94v-8.37H5.5v8.37h2.77Z"/></svg>
</a>
</div>
</nav>
{/* ANNOUNCE STRIP */}
<div className="strip">
<span className="strip-pill">Open to work</span>
<a href="#contact">Available for Junior SysAdmin &amp; IT consulting &amp; Junior Quant <span className="strip-arr">&#8594;</span></a>
</div>
{/* HERO */}
<div className="hero-row">
<div className="hero-gutter"></div>
<div className="hero-main">
<div className="status-row"><span className="green-dot"></span>Lugano &middot; Switzerland &middot; USI Economics student</div>
<div className="hero-name">Pietro<br/>Quintavalle</div>
<div className="hero-role">Systems Engineer &middot; Developer &middot; Homelabber</div>
<p className="hero-thesis">Economics models incentives under constraints; infrastructure models constraints under load I like sitting where those two disciplines overlap.</p>
<p className="hero-lead">5+ years building production-grade infrastructure from the ground up. I design resilient systems and write the software that runs on top of them then secure and monitor all of it myself.</p>
<div className="btns">
<a href="#contact" className="btn btn-d">Get in touch &#8594;</a>
<a href="#homelab" className="btn btn-o">Explore homelab</a>
<a href="#projects" className="btn btn-o">View projects</a>
</div>
</div>
<div className="hero-stats">
<div className="stat"><span className="stat-ico">&#9889;</span><div><div className="stat-val">90%+ uptime</div><div className="stat-lbl">Production servers</div></div></div>
<div className="stat"><span className="stat-ico">&#128187;</span><div><div className="stat-val">4 servers</div><div className="stat-lbl">Under management</div></div></div>
<div className="stat"><span className="stat-ico">&#128197;</span><div><div className="stat-val">5+ years</div><div className="stat-lbl">SysAdmin experience</div></div></div>
<div className="stat"><span className="stat-ico">&#127891;</span><div><div className="stat-val">BSc Economics</div><div className="stat-lbl">USI &middot; Ongoing</div></div></div>
<div className="stat"><span className="stat-ico">&#127760;</span><div><div className="stat-val">IT &middot; EN &middot; DE &middot; ES &middot; NO</div><div className="stat-lbl">5 languages</div></div></div>
</div>
<div className="hero-gutter-r"></div>
</div>
{/* ABOUT + TERMINAL */}
<div className="about-split">
<div className="about-g"></div>
<div className="about-left">
<div className="terminal">
<div className="t-bar"><span className="td r"></span><span className="td y"></span><span className="td g"></span><span className="t-title">quinta@proxmox:~</span></div>
<div className="t-stage">
<div className={`t-pane t-body${eggView !== 'main' ? ' t-hidden' : ''}`}>
<div><span className="t-p">&#10148;</span> <span className="t-c">~</span> neofetch</div>
<div>&nbsp;</div>
<div><span className="t-k">OS </span><span className="t-v">Proxmox VE 8 / UNRAID 7.3.1 / TrueNAS</span></div>
<div><span className="t-k">Virt </span><span className="t-v">LXC &middot; Docker &middot; KVM</span></div>
<div><span className="t-k">DNS </span><span className="t-v">Cloudflare &middot; AAAA &middot; AdGuard</span></div>
<div><span className="t-k">Tunnels </span><span className="t-v">Cloudflare Tunnel &middot; Newt</span></div>
<div><span className="t-k">Proxy </span><span className="t-v">Pangolin + Traefik + Auto-TLS</span></div>
<div><span className="t-k">Security </span><span className="t-v">Fail2Ban &middot; Crowdsec &middot; Authelia</span></div>
<div><span className="t-k">Storage </span><span className="t-v">UNRAID parity &middot; TrueNAS ZFS RAID 10</span></div>
<div><span className="t-k">Media </span><span className="t-v">Jellyfin &middot; Navidrome &middot; *arr</span></div>
<div><span className="t-k">Music </span><span className="t-v">Listenbrainz &middot; Symfonium</span></div>
<div><span className="t-k">Monitor </span><span className="t-v">Grafana &middot; Prometheus &middot; Kuma</span></div>
<div><span className="t-k">Code </span><span className="t-v">Python &middot; TypeScript &middot; SQL</span></div>
<div><span className="t-k">Status </span><span className="t-v" style={{color:'#5FCE8A'}}>Open for work &#9679;</span></div>
<div>&nbsp;</div>
<div className="t-cmd-row">
<span className="t-p">&#10148;</span> <span className="t-c">~</span>
<input
className="t-input"
type="text"
value={cmd}
onChange={e => setCmd(e.target.value)}
onKeyDown={handleTermKey}
autoComplete="off"
spellCheck={false}
aria-label="terminal command input"
/>
</div>
<div className="t-hint">try typing something...</div>
</div>
<div className={`t-pane t-egg-pane${eggView === 'moria' ? ' t-egg-show' : ''}`}>
<div className="t-ascii-scroll">
<pre className="t-ascii t-ascii-moria">{DURIN_ART}</pre>
</div>
<div className="t-egg-cap t-egg-cap-moria">the doors of durin, lord of moria speak, friend, and enter</div>
</div>
<div className={`t-pane t-egg-pane${eggView === 'balrog' ? ' t-egg-show' : ''}`}>
<div className="t-ascii-scroll">
<pre className="t-ascii t-ascii-balrog">{BALROG_ART}</pre>
</div>
<div className="t-egg-cap t-egg-cap-balrog">a demon of the ancient world YOU SHALL NOT PASS</div>
</div>
</div>
</div>
</div>
<div className="about-right">
<div className="slbl">About me</div>
<p>Hi friends call me <strong>Quinta</strong>. I&rsquo;m an Economics student at USI Lugano and a self-taught systems engineer who&rsquo;s been running homelab infrastructure since 2020.</p>
<p>My setup spans everything from <strong>bare-metal Proxmox clusters</strong> to <strong>DNS and domain management via Cloudflare</strong>, <strong>Pangolin/Traefik reverse proxying</strong>, and a full media+music automation pipeline.</p>
<p>On the academic side I&rsquo;m deepening my understanding of <strong>microeconomics, macroeconomics, game theory, and quantitative finance</strong> and finding more overlap with systems thinking than you&rsquo;d expect.</p>
<p style={{fontSize:'12.5px',color:'var(--muted)',fontFamily:'\'DM Mono\',monospace',marginTop:'4px'}}>Daily driver Ryzen 7 9800X3D &middot; RTX 5080 &middot; hand-built custom keyboard</p>
</div>
<div className="about-gr"></div>
</div>
{/* SKILLS */}
<div className="sec" id="skills">
<div className="sec-g"></div>
<div className="sec-body">
<div className="slbl">Technical Arsenal</div>
<div className="sk4">
<div className="sk"><div className="sk-ico">&#128423;</div><div className="sk-t">System &amp; Infra</div><div className="tags"><span className="tag">Proxmox VE</span><span className="tag">UNRAID</span><span className="tag">TrueNAS</span><span className="tag">Docker</span><span className="tag">LXC</span><span className="tag">KVM</span><span className="tag">Linux</span><span className="tag">Win Server</span><span className="tag">AD</span></div></div>
<div className="sk"><div className="sk-ico">&#128274;</div><div className="sk-t">Network &amp; Security</div><div className="tags"><span className="tag">VLAN</span><span className="tag">Cloudflare</span><span className="tag">AAAA/DNS</span><span className="tag">CF Tunnels</span><span className="tag">Tailscale</span><span className="tag">Fail2Ban</span><span className="tag">Crowdsec</span><span className="tag">UFW</span><span className="tag">Authelia</span><span className="tag">Traefik</span></div></div>
<div className="sk"><div className="sk-ico">&#9000;</div><div className="sk-t">Development</div><div className="tags"><span className="tag">Python</span><span className="tag">TypeScript</span><span className="tag">React</span><span className="tag">Next.js</span><span className="tag">Django</span><span className="tag">SQL</span><span className="tag">Bash</span><span className="tag">Ansible</span><span className="tag">HTML/CSS</span><span className="tag">Soldering</span></div></div>
<div className="sk"><div className="sk-ico">&#128202;</div><div className="sk-t">Data &amp; Monitoring</div><div className="tags"><span className="tag">Grafana</span><span className="tag">Prometheus</span><span className="tag">Uptime Kuma</span><span className="tag">ZFS</span><span className="tag">rsync</span><span className="tag">RAID</span><span className="tag">cAdvisor</span></div></div>
</div>
<div style={{marginTop:'32px'}}>
<div className="slbl">Economics &amp; Quant</div>
<div className="sk4">
<div className="sk"><div className="sk-ico">&#128200;</div><div className="sk-t">Microeconomics</div><div className="tags"><span className="tag">Consumer theory</span><span className="tag">Market structures</span><span className="tag">Production &amp; costs</span><span className="tag">Price theory</span><span className="tag">Welfare econ.</span></div></div>
<div className="sk"><div className="sk-ico">&#127758;</div><div className="sk-t">Macroeconomics</div><div className="tags"><span className="tag">GDP &amp; growth</span><span className="tag">Monetary policy</span><span className="tag">Fiscal policy</span><span className="tag">IS-LM model</span><span className="tag">Open economy</span></div></div>
<div className="sk"><div className="sk-ico">&#9861;</div><div className="sk-t">Game Theory</div><div className="tags"><span className="tag">Nash equilibrium</span><span className="tag">Dominant strategies</span><span className="tag">Extensive form</span><span className="tag">Mechanism design</span><span className="tag">Auctions</span></div></div>
<div className="sk"><div className="sk-ico">&#128185;</div><div className="sk-t">Quantitative Finance</div><div className="tags"><span className="tag">Time value of money</span><span className="tag">Probability</span><span className="tag">Statistics</span><span className="tag">Regression</span><span className="tag">Financial modelling</span></div></div>
</div>
</div>
<div style={{marginTop:'28px'}}>
<div className="slbl">Certifications</div>
<div className="certs">
<div className="cert">&#127891; Google Data Analytics Professional Certificate</div>
<div className="cert">&#127891; Google IT Support Professional Certificate</div>
<div className="cert">&#127891; Microsoft Foundations of IT Systems, Networking &amp; Data Protection</div>
</div>
</div>
</div>
<div className="sec-gr"></div>
</div>
{/* HOMELAB */}
<div className="sec" id="homelab">
<div className="sec-g"></div>
<div className="sec-body">
<div className="slbl">Homelab Infrastructure</div>
<p style={{fontSize:'14px',color:'var(--mid)',marginBottom:'32px',maxWidth:'680px',lineHeight:'1.65',fontWeight:'300'}}>Every service containerised, every entry-point proxied, every failure logged. Domain management via Cloudflare with AAAA records and tunnels. Built to enterprise patterns at personal scale.</p>
<div className="slbl" style={{marginBottom:'14px'}}>Hardware</div>
<table className="hw-table" style={{marginBottom:'32px',border:'1px solid var(--line3)',borderRadius:'5px',overflow:'hidden'}}>
<thead><tr><th>Component</th><th>UNRAID Server</th><th>Proxmox Node</th></tr></thead>
<tbody>
<tr><td>CPU</td><td>Intel Core Ultra 5 225</td><td>Intel Core i7-8700</td></tr>
<tr><td>RAM</td><td>48GB DDR5 5600MHz</td><td>32GB DDR4</td></tr>
<tr><td>Storage</td><td>4&times; HDD parity array + NVMe cache</td><td>SSD (OS) + SATA pool</td></tr>
<tr><td>Network</td><td>1Gbps LAN &middot; VLANs</td><td>1Gbps LAN &middot; VLANs</td></tr>
<tr><td>OS</td><td>UNRAID 7.3.1</td><td>Proxmox VE 8</td></tr>
<tr><td>Access</td><td>Pangolin + Traefik</td><td>Tailscale + SSH</td></tr>
<tr><td>Focus</td><td>Idle power efficiency &middot; perf/watt</td><td>Virtualisation &middot; LXC</td></tr>
</tbody>
</table>
<div className="slbl" style={{marginBottom:'14px'}}>Services &amp; Stack</div>
<div className="hl3">
<div className="hl">
<div className="hl-hd"><span className="hl-ico">&#128190;</span><span className="hl-t">UNRAID NAS</span></div>
<div className="hl-d">Parity-protected array hosting 9-user network shares, Docker Appdata, automated backups. Acts as the central file hub for all services via SMB/NFS.</div>
<div className="specs"><span className="spec">UNRAID 7.3.1</span><span className="spec">Parity array</span><span className="spec">NVMe cache</span><span className="spec">9 users</span><span className="spec">rsync</span><span className="spec">SMB/NFS</span></div>
</div>
<div className="hl">
<div className="hl-hd"><span className="hl-ico">&#127758;</span><span className="hl-t">DNS &amp; Cloudflare</span></div>
<div className="hl-d">Domain managed through Cloudflare with AAAA records for IPv6, Cloudflare Tunnels for zero-open-port external access, and AdGuard Home for internal DNS resolution and ad-blocking.</div>
<div className="specs"><span className="spec">Cloudflare</span><span className="spec">AAAA records</span><span className="spec">CF Tunnels</span><span className="spec">AdGuard Home</span><span className="spec">Split DNS</span><span className="spec">DNSSEC</span></div>
</div>
<div className="hl">
<div className="hl-hd"><span className="hl-ico">&#128256;</span><span className="hl-t">Pangolin + Traefik</span></div>
<div className="hl-d">Pangolin (Traefik-based) handles all inbound HTTPS with automatic TLS, subdomain routing, and middleware. Newt tunnel enables access without exposing raw ports. Authelia adds SSO/2FA.</div>
<div className="specs"><span className="spec">Pangolin</span><span className="spec">Traefik</span><span className="spec">Auto-TLS</span><span className="spec">Newt tunnel</span><span className="spec">Authelia</span><span className="spec">Middleware</span></div>
</div>
<div className="hl">
<div className="hl-hd"><span className="hl-ico">&#128737;</span><span className="hl-t">Fail2Ban + Security</span></div>
<div className="hl-d">Fail2Ban monitors SSH and auth logs, auto-banning brute-force IPs. Crowdsec adds community threat intelligence with real-time blocklist sync. UFW provides host-level firewall rules on each node.</div>
<div className="specs"><span className="spec">Fail2Ban</span><span className="spec">Crowdsec</span><span className="spec">UFW</span><span className="spec">iptables</span><span className="spec">SSH hardening</span><span className="spec">Authelia</span></div>
</div>
<div className="hl">
<div className="hl-hd"><span className="hl-ico">&#127916;</span><span className="hl-t">Jellyfin + Media</span></div>
<div className="hl-d">Jellyfin handles hardware-transcoded streaming with per-user OAuth authentication and Authelia SSO. Sonarr/Radarr auto-manage TV and film libraries; Bazarr handles subtitles; Prowlarr indexes trackers.</div>
<div className="specs"><span className="spec">Jellyfin</span><span className="spec">HW transcode</span><span className="spec">OAuth</span><span className="spec">Sonarr</span><span className="spec">Radarr</span><span className="spec">Bazarr</span><span className="spec">Prowlarr</span></div>
</div>
<div className="hl">
<div className="hl-hd"><span className="hl-ico">&#127925;</span><span className="hl-t">Music Pipeline</span></div>
<div className="hl-d">Lidarr monitors for releases &#8594; Prowlarr indexes &#8594; Beets normalises tags &#8594; Navidrome streams via Subsonic API. Scrobbling to Listenbrainz. Mobile playback via Symfonium and Explo apps.</div>
<div className="specs"><span className="spec">Navidrome</span><span className="spec">Lidarr</span><span className="spec">Prowlarr</span><span className="spec">Beets</span><span className="spec">Listenbrainz</span><span className="spec">Symfonium</span><span className="spec">Explo</span></div>
</div>
</div>
<div className="note">
<span className="note-ico">&#128279;</span>
<div>
<div className="note-t">Zero open ports via Tailscale + Cloudflare Tunnels</div>
<div className="note-d">All services reachable remotely through a combination of Tailscale mesh VPN (for trusted devices) and Cloudflare Tunnels (for public-facing services). No port-forwarding. MagicDNS handles internal name resolution. Pangolin&rsquo;s Newt agent tunnels external traffic into the private LAN.</div>
</div>
</div>
</div>
<div className="sec-gr"></div>
</div>
{/* DESKTOP */}
<div className="sec" id="desktop">
<div className="sec-g"></div>
<div className="sec-body">
<div className="slbl">Personal Workstation</div>
<p style={{fontSize:'14px',color:'var(--mid)',marginBottom:'32px',maxWidth:'680px',lineHeight:'1.65',fontWeight:'300'}}>Daily driver and dev machine. Built around AMD&rsquo;s 3D V-Cache architecture for a blend of high single-threaded performance and serious GPU compute doubles as a gaming rig, 3D modelling workstation, and 3D printing controller.</p>
<table className="hw-table" style={{marginBottom:'32px',border:'1px solid var(--line3)',borderRadius:'5px',overflow:'hidden'}}>
<thead><tr><th>Component</th><th>Spec</th></tr></thead>
<tbody>
<tr><td>CPU</td><td>AMD Ryzen 7 9800X3D (8c/16t &middot; 3D V-Cache)</td></tr>
<tr><td>GPU</td><td>Zotac RTX 5080 Solid OC</td></tr>
<tr><td>RAM</td><td>32GB DDR5 6000MHz</td></tr>
<tr><td>Motherboard</td><td>Gigabyte X870E AORUS PRO X3D ICE</td></tr>
<tr><td>OS</td><td>CachyOS</td></tr>
<tr><td>Role</td><td>Dev &middot; Gaming &middot; 3D Modelling &middot; 3D Printing &middot; Daily Driver</td></tr>
<tr><td>Keyboard</td><td>Weikav Stars 75 &middot; Huano Sakura switches (hand-lubed, custom assembly)</td></tr>
</tbody>
</table>
<div className="hl3" style={{marginBottom:'0',gridTemplateColumns:'repeat(2,1fr)'}}>
<div className="hl">
<div className="hl-hd"><span className="hl-ico">&#9889;</span><span className="hl-t">AMD Ryzen 7 9800X3D</span></div>
<div className="hl-d">Zen 5 architecture with 96MB 3D V-Cache. Exceptional single-thread and gaming performance, with low-latency cache access ideal for simulation and data-heavy workloads.</div>
<div className="specs"><span className="spec">Zen 5</span><span className="spec">8c / 16t</span><span className="spec">96MB L3 3D V-Cache</span><span className="spec">5.0GHz boost</span></div>
</div>
<div className="hl">
<div className="hl-hd"><span className="hl-ico">&#127918;</span><span className="hl-t">Zotac RTX 5080 Solid OC</span></div>
<div className="hl-d">Blackwell architecture. 16GB GDDR7, hardware ray tracing, DLSS 4 with Multi Frame Generation. Used for gaming, 3D rendering, and GPU-accelerated dev and compute tasks.</div>
<div className="specs"><span className="spec">Blackwell</span><span className="spec">16GB GDDR7</span><span className="spec">DLSS 4</span><span className="spec">MFG</span><span className="spec">CUDA</span><span className="spec">Ray Tracing</span></div>
</div>
<div className="hl">
<div className="hl-hd"><span className="hl-ico">&#128268;</span><span className="hl-t">X870E AORUS PRO X3D ICE</span></div>
<div className="hl-d">Gigabyte X870E flagship board with PCIe 5.0, USB4, and optimised X3D power delivery. Supports DDR5 6000MHz XMP out-of-the-box. White aesthetic matches the build&rsquo;s ICE theme.</div>
<div className="specs"><span className="spec">X870E</span><span className="spec">PCIe 5.0</span><span className="spec">USB4</span><span className="spec">DDR5 6000</span><span className="spec">WiFi 7</span></div>
</div>
<div className="hl">
<div className="hl-hd"><span className="hl-ico">&#9000;</span><span className="hl-t">Weikav Stars 75</span></div>
<div className="hl-d">Fully custom 75% aluminium keyboard hand-lubed Huano Sakura tactile switches, gasket mount, per-key RGB, 1000Hz polling. Full assembly and switch tuning done from scratch.</div>
<div className="specs"><span className="spec">75% layout</span><span className="spec">Huano Sakura</span><span className="spec">Tactile</span><span className="spec">Hand-lubed</span><span className="spec">Gasket mount</span><span className="spec">Aluminium</span><span className="spec">1000Hz</span></div>
</div>
</div>
</div>
<div className="sec-gr"></div>
</div>
{/* PROJECTS */}
<div className="sec" id="projects">
<div className="sec-g"></div>
<div className="sec-body">
<div className="slbl">Personal &amp; Client Projects</div>
<div className="pr3">
<div className="pr">
<span className="badge b-live">&#9679; Live</span>
<div className="pr-type">Professional Client</div>
<div className="pr-t">Enterprise Proxmox Cluster</div>
<div className="pr-d">Full single-node virtualisation with VLAN segmentation, Pangolin/Traefik reverse proxy, Cloudflare Tunnels, and centralised Grafana/Prometheus monitoring. 90%+ uptime maintained.</div>
<div className="pr-tags"><span className="pr-tag">Proxmox</span><span className="pr-tag">Docker</span><span className="pr-tag">LXC</span><span className="pr-tag">Traefik</span><span className="pr-tag">Tailscale</span></div>
</div>
<div className="pr">
<span className="badge b-live">&#9679; Live</span>
<div className="pr-type">Professional Client</div>
<div className="pr-t">9-User RAID 10 NAS</div>
<div className="pr-d">TrueNAS on RAID 10 serving as a self-hosted cloud for 9 users. Per-user ACLs, automated rsync offsite backups, ZFS snapshots for instant point-in-time recovery.</div>
<div className="pr-tags"><span className="pr-tag">TrueNAS</span><span className="pr-tag">RAID 10</span><span className="pr-tag">ZFS</span><span className="pr-tag">rsync</span><span className="pr-tag">ACLs</span></div>
</div>
<div className="pr">
<span className="badge b-oss">&#8599; Open Source</span>
<div className="pr-type">Personal Project</div>
<div className="pr-t">Valgo Algorithm Visualizer</div>
<div className="pr-d">Step-by-step algorithm visualisation built with React and Next.js. Sorting, graph, and search algorithms with live code annotations and speed controls.</div>
<div className="pr-tags"><span className="pr-tag">React</span><span className="pr-tag">Next.js</span><span className="pr-tag">TypeScript</span><span className="pr-tag">Tailwind</span></div>
</div>
<div className="pr">
<span className="badge b-dev">&#9680; In Dev</span>
<div className="pr-type">Personal Project</div>
<div className="pr-t">Finance Tracker</div>
<div className="pr-d">50/30/20 budgeting tool with Django backend, SQLite, and Chart.js dashboards. Monthly trend analysis, category breakdowns, and exportable reports.</div>
<div className="pr-tags"><span className="pr-tag">Django</span><span className="pr-tag">Python</span><span className="pr-tag">SQLite</span><span className="pr-tag">Chart.js</span></div>
</div>
<div className="pr">
<span className="badge b-live">&#9679; Live</span>
<div className="pr-type">Personal Homelab</div>
<div className="pr-t">Self-Hosted Stack (IaC)</div>
<div className="pr-d">Entire homelab reproduced from code Ansible playbooks, Docker Compose, and Bash scripts. Idempotent provisioning; full rebuild in under 30 minutes from scratch.</div>
<div className="pr-tags"><span className="pr-tag">Ansible</span><span className="pr-tag">Compose</span><span className="pr-tag">Bash</span><span className="pr-tag">IaC</span></div>
</div>
<div className="pr">
<span className="badge b-oss">&#8599; Open Source</span>
<div className="pr-type">Personal Project</div>
<div className="pr-t">re-soulcommand</div>
<div className="pr-d">Music recommendation downloader integrating ListenBrainz and Last.fm playlists with Navidrome via slskd (Soulseek daemon). Fork rebuilt around a REST API backend stripped of bloat, Docker-native.</div>
<div className="pr-tags"><span className="pr-tag">Python</span><span className="pr-tag">Docker</span><span className="pr-tag">ListenBrainz</span><span className="pr-tag">Last.fm</span><span className="pr-tag">slskd</span><span className="pr-tag">Navidrome</span></div>
</div>
<div className="pr">
<span className="badge b-live">&#9679; Live</span>
<div className="pr-type">Personal Homelab</div>
<div className="pr-t">Ainulindale Music Stack</div>
<div className="pr-d">Fully self-hosted music discovery and streaming platform. Auto-discovers music weekly from listening habits, downloads via Soulseek P2P through a VPN gateway, streams via Navidrome. No commercial services required.</div>
<div className="pr-tags"><span className="pr-tag">Docker</span><span className="pr-tag">Navidrome</span><span className="pr-tag">slskd</span><span className="pr-tag">Gluetun</span><span className="pr-tag">ListenBrainz</span><span className="pr-tag">ProtonVPN</span></div>
</div>
<div className="pr">
<span className="badge b-live">&#9679; Live</span>
<div className="pr-type">Personal Homelab</div>
<div className="pr-t">UNRAID Home Server</div>
<div className="pr-d">Intel Core Ultra 5 225 build running UNRAID 7.3.1 as primary NAS, Docker host, and media stack. Parity-protected array with NVMe cache, 9-user SMB/NFS shares, and all homelab services containerised.</div>
<div className="pr-tags"><span className="pr-tag">UNRAID 7.3.1</span><span className="pr-tag">Docker</span><span className="pr-tag">Parity Array</span><span className="pr-tag">48GB DDR5</span><span className="pr-tag">SMB/NFS</span></div>
</div>
<div className="pr">
<span className="badge b-live">&#9679; Live</span>
<div className="pr-type">Personal Homelab</div>
<div className="pr-t">Proxmox Virtualisation Node</div>
<div className="pr-d">Intel Core i7-8700 node running Proxmox VE 8 for LXC containers and KVM VMs. VLAN-segmented network, Tailscale + SSH access, acts as the compute backbone for all hosted services.</div>
<div className="pr-tags"><span className="pr-tag">Proxmox VE 8</span><span className="pr-tag">LXC</span><span className="pr-tag">KVM</span><span className="pr-tag">Tailscale</span><span className="pr-tag">VLAN</span><span className="pr-tag">32GB DDR4</span></div>
</div>
<div className="pr">
<span className="badge b-oss">&#8599; Open Source &middot; &#9733; 26</span>
<div className="pr-type">Personal Project</div>
<div className="pr-t">ObsidianSetup</div>
<div className="pr-d">Cross-platform utility script that makes installing Obsidian and its plugins consistent across devices PowerShell and Bash installers driven by a single plugins.json manifest.</div>
<div className="pr-tags"><span className="pr-tag">PowerShell</span><span className="pr-tag">Bash</span><span className="pr-tag">Obsidian</span><span className="pr-tag">Automation</span></div>
</div>
<div className="pr">
<span className="badge b-oss">&#8599; Open Source</span>
<div className="pr-type">Academic Project</div>
<div className="pr-t">Boston Housing Price Prediction</div>
<div className="pr-d">Probability &amp; Statistics coursework at USI built and evaluated regression models on the classic Boston housing dataset to predict median home values from neighbourhood features.</div>
<div className="pr-tags"><span className="pr-tag">Python</span><span className="pr-tag">scikit-learn</span><span className="pr-tag">Regression</span><span className="pr-tag">Jupyter</span><span className="pr-tag">Statistics</span></div>
</div>
</div>
<p style={{marginTop:'16px',fontSize:'12px',color:'var(--muted)',fontFamily:'\'DM Mono\',monospace'}}>
More projects at <a href="https://github.com/Quinta0" target="_blank" rel="noopener" style={{color:'var(--mid)',textDecoration:'none',borderBottom:'1px solid var(--line3)'}}>github.com/Quinta0 &#8599;</a>
</p>
</div>
<div className="sec-gr"></div>
</div>
{/* GITHUB */}
<div className="sec" id="github">
<div className="sec-g"></div>
<div className="sec-body">
<div className="slbl">GitHub Contributions &amp; Issues</div>
<div className="gh-list">
<div className="gh-item"><div style={{flex:1}}><div className="gh-n">Quinta0/re-soulcommand</div><div className="gh-d">Music recommendation downloader ListenBrainz &amp; Last.fm playlists into Navidrome via slskd. Author &amp; maintainer.</div></div><div className="gh-r"><span className="gh-lang">Python</span><a href="https://github.com/Quinta0/re-soulcommand" target="_blank" rel="noopener" className="gh-lnk">&#8599; repo</a></div></div>
<div className="gh-item"><div style={{flex:1}}><div className="gh-n">Quinta0/Ainulindale</div><div className="gh-d">Self-hosted music discovery &amp; streaming stack auto-discovery, Soulseek P2P download via VPN, Navidrome streaming. Author &amp; maintainer.</div></div><div className="gh-r"><span className="gh-lang">Shell</span><a href="https://github.com/Quinta0/Ainulindale" target="_blank" rel="noopener" className="gh-lnk">&#8599; repo</a></div></div>
<div className="gh-item"><div style={{flex:1}}><div className="gh-n">Quinta0/homelab</div><div className="gh-d">IaC scripts, Ansible playbooks, Docker Compose stacks for full homelab provisioning.</div></div><div className="gh-r"><span className="gh-lang">Shell</span><a href="https://github.com/Quinta0" target="_blank" rel="noopener" className="gh-lnk">&#8599; repo</a></div></div>
<div className="gh-item"><div style={{flex:1}}><div className="gh-n">Quinta0/ObsidianSetup</div><div className="gh-d">Cross-platform Obsidian + plugin installer, driven by a shared plugins.json manifest. Author &amp; maintainer.</div></div><div className="gh-r"><span className="gh-stars">&#9733; 26</span><span className="gh-lang">PowerShell</span><a href="https://github.com/Quinta0/ObsidianSetup" target="_blank" rel="noopener" className="gh-lnk">&#8599; repo</a></div></div>
<div className="gh-item"><div style={{flex:1}}><div className="gh-n">Quinta0/BostonHousing</div><div className="gh-d">Probability &amp; Statistics coursework at USI regression models predicting Boston-area housing prices.</div></div><div className="gh-r"><span className="gh-lang">Jupyter</span><a href="https://github.com/Quinta0/BostonHousing" target="_blank" rel="noopener" className="gh-lnk">&#8599; repo</a></div></div>
</div>
</div>
<div className="sec-gr"></div>
</div>
{/* EXPERIENCE + EDUCATION */}
<div className="sec" id="experience">
<div className="sec-g"></div>
<div className="sec-body">
<div className="slbl">Experience</div>
<div className="acc">
<div className={`acc-item${openAcc['exp-0'] ? ' open' : ''}`}>
<button className="acc-btn" onClick={() => toggle('exp-0')}>
<div className="acc-l"><div className="acc-t">Systems Engineering Consultant</div><div className="acc-s">Freelance &middot; Ticino, Switzerland</div></div>
<div className="acc-r2"><span className="acc-per">Jan 2020 Present</span><ChevronDown/></div>
</button>
<div className="acc-body">
<p>End-to-end IT infrastructure for private and business clients in Ticino.</p>
<ul>
<li>Delivered and maintained 4 production servers at 90%+ uptime using containerisation, IaC, and automated monitoring all without managed hosting.</li>
<li>Eliminated public exposure across all client servers by routing every external entry-point through Cloudflare Tunnels and Pangolin/Traefik zero open ports, DNSSEC enforced, split-DNS via AdGuard Home.</li>
<li>Pangolin + Traefik reverse proxy with Authelia SSO, automatic TLS, and Newt tunnelling.</li>
<li>Fail2Ban + Crowdsec intrusion detection; UFW + iptables firewall rules across all nodes.</li>
<li>Reduced storage recovery time to under 30 minutes with ZFS snapshots on the TrueNAS RAID 10 array and automated rsync offsite backups across all storage nodes.</li>
<li>Music pipeline: Lidarr &#8594; Prowlarr &#8594; Beets &#8594; Navidrome + Listenbrainz scrobbling + Symfonium/Explo mobile.</li>
<li>Windows Server: Active Directory, DNS, DHCP, Group Policy, Disaster Recovery planning.</li>
</ul>
</div>
</div>
</div>
<div style={{marginTop:'44px'}}><div className="slbl">Education</div></div>
<div className="acc">
<div className={`acc-item${openAcc['edu-0'] ? ' open' : ''}`}>
<button className="acc-btn" onClick={() => toggle('edu-0')}>
<div className="acc-l"><div className="acc-t">BSc in Economics</div><div className="acc-s">USI Universit&agrave; della Svizzera italiana &middot; Lugano</div></div>
<div className="acc-r2"><span className="acc-per">Sep 2024 Present</span><ChevronDown/></div>
</button>
<div className="acc-body">
<p>Ongoing bachelor&rsquo;s focusing on rigorous theory and quantitative methods.</p>
<ul>
<li><strong style={{color:'var(--navy)'}}>Microeconomics</strong> consumer and producer theory, market structures (perfect competition, oligopoly, monopoly), welfare economics, externalities.</li>
<li><strong style={{color:'var(--navy)'}}>Macroeconomics</strong> GDP and growth models, IS-LM, monetary and fiscal policy, open economy models, inflation and unemployment dynamics.</li>
<li><strong style={{color:'var(--navy)'}}>Game Theory</strong> Nash equilibrium, dominant strategies, extensive and normal form games, mechanism design, auction theory, repeated games.</li>
<li><strong style={{color:'var(--navy)'}}>Quantitative Finance &amp; Statistics</strong> probability theory, statistical inference, regression analysis, time series, financial modelling, portfolio theory basics.</li>
<li>Accounting, Statistics, and Quantitative Methods for economic analysis.</li>
</ul>
<div className="econ-tags">
<span className="econ-tag">Game Theory</span><span className="econ-tag">Microeconomics</span><span className="econ-tag">Macroeconomics</span><span className="econ-tag">Quantitative Finance</span><span className="econ-tag">Statistics</span><span className="econ-tag">Financial Modelling</span><span className="econ-tag">IS-LM</span><span className="econ-tag">Nash Equilibrium</span>
</div>
</div>
</div>
<div className={`acc-item${openAcc['edu-1'] ? ' open' : ''}`}>
<button className="acc-btn" onClick={() => toggle('edu-1')}>
<div className="acc-l"><div className="acc-t">BSc in Informatics <span style={{fontSize:'11.5px',color:'var(--muted)',fontWeight:400,fontFamily:'\'DM Mono\',monospace'}}> 2 yrs completed</span></div><div className="acc-s">USI Universit&agrave; della Svizzera italiana &middot; Lugano</div></div>
<div className="acc-r2"><span className="acc-per">Sep 2022 Jun 2024</span><ChevronDown/></div>
</button>
<div className="acc-body">
<p>Two years of rigorous computer science foundations before transitioning to Economics.</p>
<ul>
<li><strong style={{color:'var(--navy)'}}>Software Engineering</strong> agile methodologies, requirements engineering, UML modelling, design patterns, and testing practices.</li>
<li><strong style={{color:'var(--navy)'}}>Data Structures &amp; Algorithms</strong> complexity analysis (Big-O), arrays, linked lists, trees, graphs, and sorting and searching algorithms.</li>
<li><strong style={{color:'var(--navy)'}}>Object-Oriented Programming</strong> Java and C++ with inheritance, polymorphism, encapsulation, and SOLID principles.</li>
<li><strong style={{color:'var(--navy)'}}>Web Development</strong> HTML, CSS, and JavaScript fundamentals with introductory full-stack concepts.</li>
<li>Discrete mathematics, logic, and formal reasoning for computer science.</li>
</ul>
<div className="econ-tags">
<span className="econ-tag">Java</span><span className="econ-tag">C++</span><span className="econ-tag">Algorithms</span><span className="econ-tag">Data Structures</span><span className="econ-tag">OOP</span><span className="econ-tag">Agile</span><span className="econ-tag">Software Engineering</span><span className="econ-tag">Web Dev</span>
</div>
</div>
</div>
<div className={`acc-item${openAcc['edu-2'] ? ' open' : ''}`}>
<button className="acc-btn" onClick={() => toggle('edu-2')}>
<div className="acc-l"><div className="acc-t">Maturit&agrave; Cantonale &amp; AFC Economics</div><div className="acc-s">Scuola cantonale di commercio &middot; Bellinzona</div></div>
<div className="acc-r2"><span className="acc-per">Sep 2018 Jun 2022</span><ChevronDown/></div>
</button>
<div className="acc-body">
<p>Swiss dual-track qualification combining the academic Maturit&agrave; with a commercial AFC diploma. Graduated above average in both tracks.</p>
<ul>
<li><strong style={{color:'var(--navy)'}}>Economics &amp; Business</strong> microeconomic principles, business organisation, entrepreneurship, and market dynamics.</li>
<li><strong style={{color:'var(--navy)'}}>Accounting &amp; Finance</strong> double-entry bookkeeping, financial statements, cost accounting, and Swiss business law.</li>
<li><strong style={{color:'var(--navy)'}}>Mathematics &amp; Statistics</strong> algebra, calculus foundations, probability, and descriptive statistics.</li>
<li><strong style={{color:'var(--navy)'}}>Languages</strong> Italian (native instruction), German, English, and French; professional writing and communication.</li>
<li>Maturit&agrave; Cantonale: <strong>4.5 / 6</strong> &middot; AFC Economics: <strong>5 / 6</strong>. Above average in both tracks.</li>
</ul>
<div className="econ-tags">
<span className="econ-tag">Maturit&agrave; 4.5/6</span><span className="econ-tag">AFC 5/6</span><span className="econ-tag">Accounting</span><span className="econ-tag">Business Law</span><span className="econ-tag">Mathematics</span><span className="econ-tag">Economics</span><span className="econ-tag">Multilingual</span>
</div>
</div>
</div>
</div>
<div style={{marginTop:'36px'}}><div className="slbl">Languages</div></div>
<div className="langs">
<div className="langc"><div className="lang-n">Italian</div><div className="lang-l">Native &middot; C2</div></div>
<div className="langc"><div className="lang-n">English</div><div className="lang-l">Fluent &middot; C2</div></div>
<div className="langc"><div className="lang-n">German</div><div className="lang-l">Conversational &middot; B1&ndash;B2</div></div>
<div className="langc"><div className="lang-n">Spanish</div><div className="lang-l">Basic &middot; A1</div></div>
<div className="langc"><div className="lang-n">Norwegian</div><div className="lang-l">Basic &middot; A1</div></div>
</div>
</div>
<div className="sec-gr"></div>
</div>
{/* CONTACT */}
<div className="contact" id="contact">
<div className="contact-g"></div>
<div className="contact-body">
<h2>Ready to<br/>collaborate?</h2>
<p>Whether you need server infrastructure, network architecture, a self-hosted media stack, or a Junior SysAdmin let&rsquo;s build something reliable together.</p>
<div className="contact-ctas">
<a href="mailto:0pietroquintavalle0@gmail.com" className="btn-li">&#9993; 0pietroquintavalle0@gmail.com</a>
<a href="https://github.com/Quinta0" target="_blank" rel="noopener" className="btn-gh">GitHub &#8599;</a>
<a href="https://www.linkedin.com/in/pietro-quintavalle-996b96267/" target="_blank" rel="noopener" className="btn-gh">LinkedIn &#8599;</a>
</div>
</div>
<div className="contact-gr"></div>
</div>
{/* FOOTER */}
<footer>
<div className="foot">
<p>&#169; 2026 Pietro Quintavalle &middot; Lugano, Switzerland</p>
<a href="#">Back to top &#8593;</a>
</div>
</footer>
</>
)
}
+3 -2
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
@@ -16,5 +16,6 @@
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
},
"iconLibrary": "lucide"
}
-721
View File
@@ -1,721 +0,0 @@
'use client';
import React, { useState, useEffect } from 'react';
import { Github, Linkedin, Mail, ExternalLink, ChevronDown, Code, Database, BarChart3, Globe, Award, GraduationCap, Image, ExternalLink as LinkIcon } from 'lucide-react';
// Types
interface NordColors {
bg: string;
bgLight: string;
bgLighter: string;
bgLightest: string;
text: string;
textMuted: string;
accent: string;
blue: string;
darkBlue: string;
cyan: string;
green: string;
yellow: string;
orange: string;
red: string;
purple: string;
}
interface Project {
title: string;
period: string;
description: string;
tech: string[];
icon: React.ReactNode;
status: string;
images: string[];
features: string[];
github?: string;
demo?: string;
}
interface Education {
institution: string;
degree: string;
period: string;
status: string;
}
interface ProjectGalleryProps {
project: Project;
onClose: () => void;
}
const Portfolio: React.FC = () => {
const [activeSection, setActiveSection] = useState<string>('home');
const [scrolled, setScrolled] = useState<boolean>(false);
const [showProjectGallery, setShowProjectGallery] = useState<Project | false>(false);
// Nord Color Palette
const nordColors: NordColors = {
bg: '#2E3440',
bgLight: '#3B4252',
bgLighter: '#434C5E',
bgLightest: '#4C566A',
text: '#D8DEE9',
textMuted: 'rgba(216, 222, 233, 0.6)',
accent: '#88C0D0',
blue: '#81A1C1',
darkBlue: '#5E81AC',
cyan: '#8FBCBB',
green: '#A3BE8C',
yellow: '#EBCB8B',
orange: '#D08770',
red: '#BF616A',
purple: '#B48EAD'
};
useEffect(() => {
const handleScroll = (): void => {
setScrolled(window.scrollY > 50);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const skills: Record<string, string[]> = {
'Data & Analytics': ['Python', 'R', 'SQL', 'Statistical Analysis', 'Data Visualization', 'Business Intelligence'],
'Web Development': ['JavaScript', 'TypeScript', 'React', 'Next.js', 'HTML5', 'CSS3', 'Tailwind CSS'],
'Backend & Tools': ['Flask', 'Express', 'MongoDB', 'SQLite', 'Git', 'Linux'],
'Other': ['Java', 'LaTeX', 'Project Planning', 'Financial Analysis']
};
const projects: Project[] = [
{
title: 'Valgo - Algorithm Visualizer',
period: 'Jun 2024 - Present',
description: 'Interactive web application designed to help users understand and visualize various sorting algorithms. Built with Next.js and React to make learning algorithms more engaging and intuitive.',
tech: ['Next.js', 'React', 'JavaScript', 'Algorithm Visualization'],
icon: <Code className="w-6 h-6" />,
status: 'Live',
images: [
'https://via.placeholder.com/600x400/81A1C1/2E3440?text=Algorithm+Visualizer',
'https://via.placeholder.com/600x400/88C0D0/2E3440?text=Sorting+Demo',
'https://via.placeholder.com/600x400/5E81AC/2E3440?text=Interactive+UI'
],
features: ['Real-time algorithm visualization', 'Multiple sorting algorithms', 'Step-by-step breakdown', 'Performance comparison'],
github: 'https://github.com/Quinta0/valgo',
demo: 'https://valgo-demo.vercel.app'
},
{
title: 'Boston Housing Price Prediction',
period: 'May 2024 - Present',
description: 'Machine learning model to predict housing prices in the Boston area based on various features such as crime rate, average number of rooms per dwelling, and accessibility to radial highways.',
tech: ['Python', 'Machine Learning', 'Data Analysis', 'Statistical Modeling'],
icon: <BarChart3 className="w-6 h-6" />,
status: 'In Progress',
images: [
'https://via.placeholder.com/600x400/A3BE8C/2E3440?text=Data+Visualization',
'https://via.placeholder.com/600x400/EBCB8B/2E3440?text=ML+Model',
'https://via.placeholder.com/600x400/D08770/2E3440?text=Results+Analysis'
],
features: ['Predictive modeling', 'Feature engineering', 'Data visualization', 'Statistical analysis'],
github: 'https://github.com/Quinta0/boston-housing'
},
{
title: 'Forest Fire Simulation',
period: 'Mar 2024 - Present',
description: 'Probability & Statistics course project modeling the spread of forest fires over a grid. Accounts for vegetation types, terrain elevation, wind patterns, humidity levels, and spontaneous ignition.',
tech: ['Python', 'Statistical Modeling', 'Simulation', 'Data Science'],
icon: <Database className="w-6 h-6" />,
status: 'Academic Project',
images: [
'https://via.placeholder.com/600x400/BF616A/2E3440?text=Fire+Simulation',
'https://via.placeholder.com/600x400/B48EAD/2E3440?text=Statistical+Model',
'https://via.placeholder.com/600x400/8FBCBB/2E3440?text=Data+Analysis'
],
features: ['Monte Carlo simulation', 'Environmental factors modeling', 'Statistical analysis', 'Visualization tools'],
github: 'https://github.com/Quinta0/forest-fire-sim'
}
];
const education: Education[] = [
{
institution: 'USI Università della Svizzera italiana',
degree: "Bachelor's degree Economics",
period: 'Sep 2024 - Jun 2027',
status: 'Current'
},
{
institution: 'USI Università della Svizzera italiana',
degree: 'Bachelor of Science - BS Informatics',
period: 'Jan 2022 - Jan 2024',
status: 'Completed'
},
{
institution: 'Scuola cantonale di commercio',
degree: 'Maturità Cantonale e AFC Economics',
period: 'Sep 2017 - Jun 2021',
status: 'GPA: 4.5/5'
}
];
const certificates: string[] = [
'Google Data Analytics Professional Certificate',
'Google IT Support Professional Certificate',
'IT Security: Defense against the digital dark arts',
'Python for Data Science, AI & Development'
];
const scrollToSection = (sectionId: string): void => {
document.getElementById(sectionId)?.scrollIntoView({ behavior: 'smooth' });
setActiveSection(sectionId);
};
const ProjectGallery: React.FC<ProjectGalleryProps> = ({ project, onClose }) => (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4"
style={{ backgroundColor: 'rgba(46, 52, 64, 0.95)' }}
onClick={onClose}
>
<div
className="max-w-4xl w-full max-h-[90vh] overflow-y-auto rounded-lg"
style={{ backgroundColor: nordColors.bgLight }}
onClick={(e: React.MouseEvent) => e.stopPropagation()}
>
<div className="p-6">
<div className="flex justify-between items-start mb-6">
<div>
<h3 className="text-2xl font-bold mb-2" style={{ color: nordColors.text }}>
{project.title}
</h3>
<div className="flex items-center gap-4">
<span className="text-sm" style={{ color: nordColors.blue }}>
{project.period}
</span>
<span
className="px-3 py-1 rounded-full text-xs"
style={{
backgroundColor: nordColors.darkBlue,
color: nordColors.text
}}
>
{project.status}
</span>
</div>
</div>
<button
onClick={onClose}
className="text-2xl hover:opacity-70"
style={{ color: nordColors.textMuted }}
>
×
</button>
</div>
{/* Project Images */}
<div className="grid md:grid-cols-2 gap-4 mb-6">
{project.images.map((image: string, idx: number) => (
<div key={idx} className="aspect-video rounded-lg overflow-hidden">
<img
src={image}
alt={`${project.title} screenshot ${idx + 1}`}
className="w-full h-full object-cover"
/>
</div>
))}
</div>
{/* Description */}
<div className="mb-6">
<h4 className="text-lg font-semibold mb-3" style={{ color: nordColors.blue }}>
About This Project
</h4>
<p className="leading-relaxed" style={{ color: nordColors.textMuted }}>
{project.description}
</p>
</div>
{/* Features */}
<div className="mb-6">
<h4 className="text-lg font-semibold mb-3" style={{ color: nordColors.blue }}>
Key Features
</h4>
<div className="grid md:grid-cols-2 gap-2">
{project.features.map((feature: string, idx: number) => (
<div key={idx} className="flex items-center">
<div
className="w-2 h-2 rounded-full mr-3"
style={{ backgroundColor: nordColors.accent }}
></div>
<span style={{ color: nordColors.text }}>{feature}</span>
</div>
))}
</div>
</div>
{/* Tech Stack */}
<div className="mb-6">
<h4 className="text-lg font-semibold mb-3" style={{ color: nordColors.blue }}>
Technology Stack
</h4>
<div className="flex flex-wrap gap-2">
{project.tech.map((tech: string) => (
<span
key={tech}
className="px-3 py-1 rounded text-sm"
style={{
backgroundColor: nordColors.bg,
color: nordColors.accent
}}
>
{tech}
</span>
))}
</div>
</div>
{/* Links */}
<div className="flex gap-4">
{project.github && (
<a
href={project.github}
className="flex items-center gap-2 px-4 py-2 rounded hover:opacity-80 transition-opacity"
style={{ backgroundColor: nordColors.bgLighter, color: nordColors.text }}
target="_blank"
rel="noopener noreferrer"
>
<Github className="w-4 h-4" />
View Code
</a>
)}
{project.demo && (
<a
href={project.demo}
className="flex items-center gap-2 px-4 py-2 rounded hover:opacity-80 transition-opacity"
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
target="_blank"
rel="noopener noreferrer"
>
<LinkIcon className="w-4 h-4" />
Live Demo
</a>
)}
</div>
</div>
</div>
</div>
);
return (
<div className="min-h-screen font-sans" style={{ backgroundColor: nordColors.bg, color: nordColors.text }}>
{/* Navigation */}
<nav
className="fixed top-0 w-full z-40 transition-all duration-300"
style={{
backgroundColor: scrolled ? 'rgba(46, 52, 64, 0.95)' : 'transparent',
borderBottom: scrolled ? `1px solid ${nordColors.bgLight}` : 'none',
backdropFilter: scrolled ? 'blur(10px)' : 'none'
}}
>
<div className="max-w-6xl mx-auto px-6 py-4">
<div className="flex justify-between items-center">
<div className="text-xl font-bold" style={{ color: nordColors.accent }}>
Pietro Quintavalle
</div>
<div className="hidden md:flex space-x-8">
{['Home', 'About', 'Projects', 'Education', 'Contact'].map((item: string) => (
<button
key={item}
onClick={() => scrollToSection(item.toLowerCase())}
className="transition-colors hover:opacity-80"
style={{
color: activeSection === item.toLowerCase() ? nordColors.accent : nordColors.text
}}
>
{item}
</button>
))}
</div>
</div>
</div>
</nav>
{/* Hero Section */}
<section id="home" className="min-h-screen flex items-center justify-center relative">
<div className="text-center max-w-4xl mx-auto px-6">
<div className="mb-8">
<h1 className="text-5xl md:text-7xl font-bold mb-4">
<span style={{ color: nordColors.text }}>Pietro</span>
<span className="ml-4" style={{ color: nordColors.accent }}>Quintavalle</span>
</h1>
<div className="text-xl md:text-2xl mb-6" style={{ color: nordColors.blue }}>
Economics Student • Data Analytics • Web Development
</div>
<p className="text-lg max-w-2xl mx-auto leading-relaxed" style={{ color: nordColors.textMuted }}>
Passionate about the intersection of economics and technology, exploring how data and code can solve real-world problems.
</p>
</div>
<div className="flex justify-center space-x-6 mb-12">
<a
href="https://github.com/Quinta0"
className="p-3 rounded-full transition-colors hover:opacity-80"
style={{ backgroundColor: nordColors.bgLight }}
target="_blank"
rel="noopener noreferrer"
>
<Github className="w-6 h-6" style={{ color: nordColors.text }} />
</a>
<a
href="https://www.linkedin.com/in/pietro-quintavalle-996b96267/"
className="p-3 rounded-full transition-colors hover:opacity-80"
style={{ backgroundColor: nordColors.bgLight }}
target="_blank"
rel="noopener noreferrer"
>
<Linkedin className="w-6 h-6" style={{ color: nordColors.text }} />
</a>
<a
href="mailto:0pietroquintavalle0@gmail.com"
className="p-3 rounded-full transition-colors hover:opacity-80"
style={{ backgroundColor: nordColors.bgLight }}
>
<Mail className="w-6 h-6" style={{ color: nordColors.text }} />
</a>
<a
href="https://quinta0.github.io/whoami/"
className="p-3 rounded-full transition-colors hover:opacity-80"
style={{ backgroundColor: nordColors.bgLight }}
target="_blank"
rel="noopener noreferrer"
>
<Globe className="w-6 h-6" style={{ color: nordColors.text }} />
</a>
</div>
</div>
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 animate-bounce">
<ChevronDown className="w-6 h-6" style={{ color: nordColors.blue }} />
</div>
</section>
{/* About Section */}
<section id="about" className="py-20" style={{ backgroundColor: 'rgba(59, 66, 82, 0.3)' }}>
<div className="max-w-6xl mx-auto px-6">
<h2 className="text-4xl font-bold text-center mb-16" style={{ color: nordColors.accent }}>
About Me
</h2>
<div className="grid md:grid-cols-2 gap-12 items-center">
<div>
<p className="text-lg leading-relaxed mb-6" style={{ color: nordColors.text }}>
I'm an Economics student at USI with extensive experience in data analytics and web development.
My journey combines analytical thinking with technical skills to create meaningful solutions.
</p>
<p className="text-lg leading-relaxed mb-6" style={{ color: nordColors.text }}>
Google-certified in Data Analytics and IT Support, I bring proven expertise in Python, R, SQL,
and modern web technologies like Next.js and React.
</p>
<div className="flex flex-wrap gap-3">
<span
className="px-3 py-1 rounded-full text-sm"
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
>
Italian (Native)
</span>
<span
className="px-3 py-1 rounded-full text-sm"
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
>
English (C2)
</span>
<span
className="px-3 py-1 rounded-full text-sm"
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
>
Switzerland 🇨🇭
</span>
</div>
</div>
<div className="space-y-6">
{Object.entries(skills).map(([category, skillList]: [string, string[]]) => (
<div
key={category}
className="p-6 rounded-lg border"
style={{
backgroundColor: nordColors.bg,
borderColor: nordColors.bgLight
}}
>
<h3 className="text-xl font-semibold mb-3" style={{ color: nordColors.blue }}>
{category}
</h3>
<div className="flex flex-wrap gap-2">
{skillList.map((skill: string) => (
<span
key={skill}
className="px-3 py-1 rounded text-sm"
style={{
backgroundColor: nordColors.bgLighter,
color: nordColors.text
}}
>
{skill}
</span>
))}
</div>
</div>
))}
</div>
</div>
</div>
</section>
{/* Projects Section */}
<section id="projects" className="py-20">
<div className="max-w-6xl mx-auto px-6">
<h2 className="text-4xl font-bold text-center mb-16" style={{ color: nordColors.accent }}>
Featured Projects
</h2>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{projects.map((project: Project, index: number) => (
<div
key={index}
className="p-6 rounded-lg border transition-colors hover:opacity-90 cursor-pointer"
style={{
backgroundColor: nordColors.bgLight,
borderColor: nordColors.bgLighter
}}
onClick={() => setShowProjectGallery(project)}
>
<div className="flex items-center mb-4">
<div
className="p-2 rounded-lg mr-3"
style={{ backgroundColor: nordColors.darkBlue }}
>
{project.icon}
</div>
<div>
<h3 className="text-xl font-semibold" style={{ color: nordColors.text }}>
{project.title}
</h3>
<p className="text-sm" style={{ color: nordColors.blue }}>
{project.period}
</p>
</div>
</div>
<p className="leading-relaxed mb-4" style={{ color: nordColors.textMuted }}>
{project.description}
</p>
<div className="flex flex-wrap gap-2 mb-4">
{project.tech.map((tech: string) => (
<span
key={tech}
className="px-2 py-1 rounded text-xs"
style={{
backgroundColor: nordColors.bg,
color: nordColors.accent
}}
>
{tech}
</span>
))}
</div>
<div className="flex items-center justify-between">
<span
className="text-sm flex items-center gap-2"
style={{ color: nordColors.blue }}
>
<Image className="w-4 h-4" />
View Gallery
</span>
<span
className="px-2 py-1 rounded text-xs"
style={{
backgroundColor: nordColors.darkBlue,
color: nordColors.text
}}
>
{project.status}
</span>
</div>
</div>
))}
</div>
</div>
</section>
{/* Education & Certificates Section */}
<section id="education" className="py-20" style={{ backgroundColor: 'rgba(59, 66, 82, 0.3)' }}>
<div className="max-w-6xl mx-auto px-6">
<h2 className="text-4xl font-bold text-center mb-16" style={{ color: nordColors.accent }}>
Education & Certifications
</h2>
<div className="grid md:grid-cols-2 gap-12">
{/* Education */}
<div>
<h3 className="text-2xl font-semibold mb-8 flex items-center" style={{ color: nordColors.blue }}>
<GraduationCap className="w-6 h-6 mr-2" />
Education
</h3>
<div className="space-y-6">
{education.map((edu: Education, index: number) => (
<div
key={index}
className="p-6 rounded-lg border"
style={{
backgroundColor: nordColors.bg,
borderColor: nordColors.bgLight
}}
>
<h4 className="text-lg font-semibold mb-2" style={{ color: nordColors.text }}>
{edu.degree}
</h4>
<p className="mb-2" style={{ color: nordColors.blue }}>
{edu.institution}
</p>
<div className="flex justify-between items-center">
<span className="text-sm" style={{ color: nordColors.textMuted }}>
{edu.period}
</span>
<span
className="px-2 py-1 rounded text-xs"
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
>
{edu.status}
</span>
</div>
</div>
))}
</div>
</div>
{/* Certificates */}
<div>
<h3 className="text-2xl font-semibold mb-8 flex items-center" style={{ color: nordColors.blue }}>
<Award className="w-6 h-6 mr-2" />
Certifications
</h3>
<div className="space-y-4">
{certificates.map((cert: string, index: number) => (
<div
key={index}
className="p-4 rounded-lg border flex items-center"
style={{
backgroundColor: nordColors.bg,
borderColor: nordColors.bgLight
}}
>
<div
className="w-2 h-2 rounded-full mr-4"
style={{ backgroundColor: nordColors.accent }}
></div>
<span style={{ color: nordColors.text }}>{cert}</span>
</div>
))}
</div>
<div
className="mt-8 p-6 rounded-lg border"
style={{
backgroundColor: nordColors.bg,
borderColor: nordColors.bgLight
}}
>
<h4 className="text-lg font-semibold mb-3" style={{ color: nordColors.blue }}>
Interests
</h4>
<div className="flex flex-wrap gap-2">
{['Computer Hardware', 'Web Development', 'Finance', 'Economics', 'Literature'].map((interest: string) => (
<span
key={interest}
className="px-3 py-1 rounded text-sm"
style={{
backgroundColor: nordColors.bgLighter,
color: nordColors.text
}}
>
{interest}
</span>
))}
</div>
</div>
</div>
</div>
</div>
</section>
{/* Contact Section */}
<section id="contact" className="py-20">
<div className="max-w-4xl mx-auto px-6 text-center">
<h2 className="text-4xl font-bold mb-8" style={{ color: nordColors.accent }}>
Let's Connect
</h2>
<p className="text-lg mb-12 max-w-2xl mx-auto" style={{ color: nordColors.textMuted }}>
I'm always interested in discussing new opportunities, collaborations, or just having a chat about technology and economics.
</p>
<div className="grid md:grid-cols-3 gap-8 mb-12">
<a
href="mailto:0pietroquintavalle0@gmail.com"
className="p-6 rounded-lg transition-colors hover:opacity-80 group"
style={{ backgroundColor: nordColors.bgLight }}
>
<Mail className="w-8 h-8 mx-auto mb-4" style={{ color: nordColors.blue }} />
<h3 className="text-lg font-semibold mb-2" style={{ color: nordColors.text }}>Email</h3>
<p className="text-sm" style={{ color: nordColors.textMuted }}>Drop me a line</p>
</a>
<a
href="https://www.linkedin.com/in/pietro-quintavalle-996b96267/"
className="p-6 rounded-lg transition-colors hover:opacity-80 group"
style={{ backgroundColor: nordColors.bgLight }}
target="_blank"
rel="noopener noreferrer"
>
<Linkedin className="w-8 h-8 mx-auto mb-4" style={{ color: nordColors.blue }} />
<h3 className="text-lg font-semibold mb-2" style={{ color: nordColors.text }}>LinkedIn</h3>
<p className="text-sm" style={{ color: nordColors.textMuted }}>Let's connect professionally</p>
</a>
<a
href="https://github.com/Quinta0"
className="p-6 rounded-lg transition-colors hover:opacity-80 group"
style={{ backgroundColor: nordColors.bgLight }}
target="_blank"
rel="noopener noreferrer"
>
<Github className="w-8 h-8 mx-auto mb-4" style={{ color: nordColors.blue }} />
<h3 className="text-lg font-semibold mb-2" style={{ color: nordColors.text }}>GitHub</h3>
<p className="text-sm" style={{ color: nordColors.textMuted }}>Check out my code</p>
</a>
</div>
</div>
</section>
{/* Footer */}
<footer className="py-8 border-t" style={{ borderColor: nordColors.bgLight }}>
<div className="max-w-6xl mx-auto px-6 text-center">
<p style={{ color: nordColors.textMuted }}>
Built with ❄️ using Next.js & Nord theme • Pietro Quintavalle © 2025
</p>
</div>
</footer>
{/* Project Gallery Modal */}
{showProjectGallery && (
<ProjectGallery
project={showProjectGallery}
onClose={() => setShowProjectGallery(false)}
/>
)}
</div>
);
};
export default Portfolio;
-36
View File
@@ -1,36 +0,0 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }
-56
View File
@@ -1,56 +0,0 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
-79
View File
@@ -1,79 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
-24
View File
@@ -1,24 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
-23
View File
@@ -1,23 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"
export { Textarea }
-31
View File
@@ -1,31 +0,0 @@
export const NORD_COLORS: NordColors = {
bg: '#2E3440',
bgLight: '#3B4252',
bgLighter: '#434C5E',
bgLightest: '#4C566A',
text: '#D8DEE9',
textMuted: 'rgba(216, 222, 233, 0.6)',
accent: '#88C0D0',
blue: '#81A1C1',
darkBlue: '#5E81AC',
cyan: '#8FBCBB',
green: '#A3BE8C',
yellow: '#EBCB8B',
orange: '#D08770',
red: '#BF616A',
purple: '#B48EAD'
};
export const SKILLS: Record<string, string[]> = {
'Data & Analytics': ['Python', 'R', 'SQL', 'Statistical Analysis', 'Data Visualization', 'Business Intelligence'],
'Web Development': ['JavaScript', 'TypeScript', 'React', 'Next.js', 'HTML5', 'CSS3', 'Tailwind CSS'],
'Backend & Tools': ['Flask', 'Express', 'MongoDB', 'SQLite', 'Git', 'Linux'],
'Other': ['Java', 'LaTeX', 'Project Planning', 'Financial Analysis']
};
export const CERTIFICATES: string[] = [
'Google Data Analytics Professional Certificate',
'Google IT Support Professional Certificate',
'IT Security: Defense against the digital dark arts',
'Python for Data Science, AI & Development'
];
+7662
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -11,13 +11,14 @@
},
"dependencies": {
"@radix-ui/react-slot": "^1.1.0",
"class-variance-authority": "^0.7.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.441.0",
"next": "14.2.13",
"radix-ui": "^1.4.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwind-merge": "^2.5.2",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

+121
View File
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 140 KiB

+57 -17
View File
@@ -11,30 +11,70 @@ const config: Config = {
extend: {
colors: {
nord: {
0: '#2E3440',
1: '#3B4252',
2: '#434C5E',
3: '#4C566A',
4: '#D8DEE9',
5: '#E5E9F0',
6: '#ECEFF4',
7: '#8FBCBB',
8: '#88C0D0',
9: '#81A1C1',
10: '#5E81AC',
11: '#BF616A',
12: '#D08770',
13: '#EBCB8B',
14: '#A3BE8C',
15: '#B48EAD',
'0': '#2E3440',
'1': '#3B4252',
'2': '#434C5E',
'3': '#4C566A',
'4': '#D8DEE9',
'5': '#E5E9F0',
'6': '#ECEFF4',
'7': '#8FBCBB',
'8': '#88C0D0',
'9': '#81A1C1',
'10': '#5E81AC',
'11': '#BF616A',
'12': '#D08770',
'13': '#EBCB8B',
'14': '#A3BE8C',
'15': '#B48EAD'
},
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
}
},
}
},
plugins: [require("tailwindcss-animate")],
};
+27 -7
View File
@@ -1,6 +1,10 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "es6"],
"lib": [
"dom",
"dom.iterable",
"es6"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -19,12 +23,28 @@
],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"],
"@/types/*": ["./src/types/*"]
"@/*": [
"./*"
],
"@/components/*": [
"./components/*"
],
"@/lib/*": [
"./lib/*"
],
"@/types/*": [
"./types/*"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"out/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}