Project page touch up

This commit is contained in:
2026-08-07 20:06:34 +03:00
parent 7d378cb2d5
commit ae10c8f049
2 changed files with 108 additions and 28 deletions

View File

@@ -2,28 +2,41 @@ import styles from '../styles/components/Projects.module.scss';
const PROJECTS = [ const PROJECTS = [
{ {
name: 'Klapi - Unified Control Panel', name: 'Klapi - Unified content management panel',
desc: 'Web interface to view and manage data held by Livonsaaren Tietokonepaja. Easy to customize serving various customer solutions.', desc: 'Web interface to view and manage data held by Livonsaaren Tietokonepaja. Easy to customize serving various customer solutions.',
tags: ['.NET', 'React', 'SQLite'], tags: ['.NET', 'React', 'SQLite'],
href: 'https://klapi.tietokonepaja.fi/', href: 'https://klapi.tietokonepaja.fi/',
star: false, sourceHref: 'https://github.com',
}, },
{ {
name: 'Hattara - Online office', name: 'Hattara - Shared online office',
desc: 'Why to code your own when there is working open source solution to host on your own server? Hattara is a self-hosted cloud office solution for small teams and individuals.', desc: 'Why to code your own when there is working open source solution to host on your own server? Hattara is a self-hosted cloud office solution for small teams and individuals.',
tags: ['Apache2', 'Ubuntu Server', 'NextCloud'], tags: ['Apache2', 'Ubuntu Server', 'NextCloud'],
href: 'https://hattara.tietokonepaja.fi/', href: 'https://hattara.tietokonepaja.fi/',
star: false, sourceHref: 'https://github.com',
}, },
{ {
name: 'Runosaari festival website - Pro bono', name: 'Runosaari festival website - Pro bono project',
desc: "Supporting local culture initiatives have always been close to my heart. I'm proudly helping Runosaari festival with their website and online presence already for the sixth year!", desc: "Supporting local culture initiatives have always been close to my heart. I'm proudly helping Runosaari festival with their website and online presence already for the sixth year!",
tags: ['React', 'Vite', 'Apache2', 'Ubuntu Server'], tags: ['React', 'Vite', 'Apache2', 'Ubuntu Server'],
href: 'https://www.runosaari.fi/', href: 'https://www.runosaari.fi/',
star: false, sourceHref: 'https://github.com',
}, },
]; ];
const getTagColor = (tag) => {
const colorMap = {
'.NET': 'tagDotnet',
React: 'tagReact',
SQLite: 'tagSqlite',
Apache2: 'tagApache',
'Ubuntu Server': 'tagUbuntu',
NextCloud: 'tagNextcloud',
Vite: 'tagVite',
};
return colorMap[tag] || 'tagDefault';
};
export default function Projects() { export default function Projects() {
return ( return (
<section id="projects" className={styles.projects}> <section id="projects" className={styles.projects}>
@@ -36,19 +49,25 @@ export default function Projects() {
<div className={styles.grid}> <div className={styles.grid}>
{PROJECTS.map((p) => ( {PROJECTS.map((p) => (
<article key={p.name} className={`${styles.card} ${p.star ? styles.featured : ''}`}> <article key={p.name} className={styles.card}>
{p.star && <span className={styles.featuredBadge}>Featured</span>}
<div className={styles.cardTop}> <div className={styles.cardTop}>
<span className={styles.folderIcon}>⬡</span> <span className={styles.folderIcon}>⬡</span>
<div className={styles.cardLinks}>
<a href={p.href} className={styles.externalLink} aria-label={`Open ${p.name}`}> <a href={p.href} className={styles.externalLink} aria-label={`Open ${p.name}`}>
↗ ↗
</a> </a>
<a href={p.sourceHref} className={styles.sourceLink} aria-label={`View source code for ${p.name}`}>
📦
</a>
</div>
</div> </div>
<h3 className={styles.cardName}>{p.name}</h3> <h3 className={styles.cardName}>{p.name}</h3>
<p className={styles.cardDesc}>{p.desc}</p> <p className={styles.cardDesc}>{p.desc}</p>
<ul className={styles.tags}> <ul className={styles.tags}>
{p.tags.map((t) => ( {p.tags.map((t) => (
<li key={t}>{t}</li> <li key={t} className={styles.tagItem}>
<span className={`${styles.tagPill} ${styles[getTagColor(t)]}`}>{t}</span>
</li>
))} ))}
</ul> </ul>
</article> </article>

View File

@@ -66,6 +66,29 @@
} }
} }
.cardTop {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $space-2;
}
.cardLinks {
display: flex;
gap: $space-2;
}
.sourceLink {
font-size: $text-xl;
color: $color-text-muted;
transition: color $transition-fast, transform $transition-fast;
&:hover {
color: $color-accent;
transform: translate(2px, -2px);
}
}
.featuredBadge { .featuredBadge {
position: absolute; position: absolute;
top: $space-4; top: $space-4;
@@ -79,13 +102,6 @@
padding: 2px $space-3; padding: 2px $space-3;
} }
.cardTop {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $space-2;
}
.folderIcon { .folderIcon {
font-size: $text-2xl; font-size: $text-2xl;
color: $color-accent; color: $color-accent;
@@ -121,13 +137,58 @@
flex-wrap: wrap; flex-wrap: wrap;
gap: $space-2; gap: $space-2;
margin-top: $space-2; margin-top: $space-2;
padding: 0;
}
li { .tagItem {
list-style: none;
}
.tagPill {
font-family: $font-mono; font-family: $font-mono;
font-size: $text-xs; font-size: $text-xs;
color: $color-text-dim;
background: rgb(var(--color-primary) / 0.15);
border-radius: $radius-sm; border-radius: $radius-sm;
padding: 2px $space-3; padding: 2px $space-3;
display: inline-block;
} }
// Tag color variants
.tagReact {
background: rgb(97, 218, 251);
color: rgb(0, 0, 0);
}
.tagDotnet {
background: rgb(102, 51, 153);
color: rgb(255, 255, 255);
}
.tagSqlite {
background: rgb(70, 130, 180);
color: rgb(255, 255, 255);
}
.tagApache {
background: rgb(204, 102, 51);
color: rgb(255, 255, 255);
}
.tagUbuntu {
background: rgb(220, 100, 40);
color: rgb(255, 255, 255);
}
.tagNextcloud {
background: rgb(0, 150, 200);
color: rgb(255, 255, 255);
}
.tagVite {
background: rgb(150, 50, 200);
color: rgb(255, 255, 255);
}
.tagDefault {
background: rgb(var(--color-primary) / 0.15);
color: $color-text-dim;
} }