Add Bytemalte projects showcase website with file-based CMS
- Svelte 5 website following BYTEMALTE design system - File-based CMS via static/projects.json (no restart needed) - Hero section, responsive project cards grid, navbar, footer - All 8 projects integrated with Code/Download links
This commit is contained in:
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["svelte.svelte-vscode"]
|
||||
}
|
||||
57
BYTEMALTE.md
Normal file
57
BYTEMALTE.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 🎨 BYTEMALTE Design System v1.1
|
||||
|
||||
Dieses Dokument definiert die visuelle Identität für alle Projekte von **ByteMalte**. Konsistenz vor Komplexität.
|
||||
|
||||
---
|
||||
|
||||
## 🌈 Farbpalette (Hex)
|
||||
|
||||
| Rolle | Hex-Code | Kontrast (auf `#0F172A`) | Einsatzbereich |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Primary** | `#8888FF` | 5.7:1 | Buttons, Links, Brand-Elemente |
|
||||
| **Secondary** | `#3DDC84` | 7.0:1 | Success-Meldungen, Akzente, Tags |
|
||||
| **Background** | `#0F172A` | — | Haupt-Hintergrund (Dark Mode bevorzugt) |
|
||||
| **Surface** | `#1E293B` | — | Karten, Sektionen, Modals |
|
||||
| **Text (High)** | `#F8FAFC` | 15.4:1 | Überschriften, Fließtext |
|
||||
| **Text (Muted)** | `#B0BDD0` | 7.2:1 | Beschreibungen, Footer, sekundärer Text |
|
||||
| **Accent/Error** | `#EF4444` | 4.5:1 | Fehler, Löschen-Buttons |
|
||||
|
||||
> Alle Farben erfüllen WCAG AA (mindestens 4.5:1 für normalen Text, 3:1 für großen Text).
|
||||
|
||||
---
|
||||
|
||||
## 📐 Layout & Abrundungen (Borders)
|
||||
|
||||
Wir nutzen ein weiches, modernes Design mit großzügigen Radien.
|
||||
|
||||
* **Buttons:** `8px` (Medium Round)
|
||||
* **Cards / Container:** `16px` (Large Round)
|
||||
* **Inputs / Formulare:** `8px`
|
||||
* **Profilbilder:** `50%` (Circle)
|
||||
|
||||
---
|
||||
|
||||
## 🖋️ Typografie
|
||||
|
||||
* **Font Family:** `Inter, system-ui, sans-serif` (Clean & Programmierer-Vibe)
|
||||
* **Headline Scale:**
|
||||
* **h1:** `2.5rem` | Bold (700)
|
||||
* **h2:** `1.8rem` | Semi-Bold (600)
|
||||
* **h3:** `1.2rem` | Medium (500)
|
||||
* **Body:** `1rem` | Regular (400) | Line-height: `1.6`
|
||||
|
||||
---
|
||||
|
||||
## ⚡ UI-Komponenten Spezifikationen
|
||||
|
||||
### Buttons
|
||||
* **Default:** Primary Background, White Text, keine Border.
|
||||
* **Hover:** Helligkeit +10%, leichter Box-Shadow (`0 4px 6px -1px rgb(0 0 0 / 0.1)`).
|
||||
* **Active:** Skalierung auf `0.98` (Click-Effekt).
|
||||
|
||||
### Karten (Cards)
|
||||
* **Background:** `Surface` (`#1E293B`)
|
||||
* **Border:** `1px solid #334155`
|
||||
* **Padding:** `24px`
|
||||
|
||||
---
|
||||
42
README.md
Normal file
42
README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# sv
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```sh
|
||||
# create a new project
|
||||
npx sv create my-app
|
||||
```
|
||||
|
||||
To recreate this project with the same configuration:
|
||||
|
||||
```sh
|
||||
# recreate this project
|
||||
pnpm dlx sv@0.12.7 create --template minimal --types ts --install pnpm bytemalte_de_svelte
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||
8
justfile
Normal file
8
justfile
Normal file
@@ -0,0 +1,8 @@
|
||||
default:
|
||||
just --list
|
||||
|
||||
run:
|
||||
pnpm run dev --open
|
||||
|
||||
build:
|
||||
pnpm run build
|
||||
23
package.json
Normal file
23
package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "bytemalte-de-svelte",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/kit": "^2.50.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"svelte": "^5.51.0",
|
||||
"svelte-check": "^4.4.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1"
|
||||
}
|
||||
}
|
||||
1026
pnpm-lock.yaml
generated
Normal file
1026
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
91
src/app.css
Normal file
91
src/app.css
Normal file
@@ -0,0 +1,91 @@
|
||||
:root {
|
||||
/* Color Palette - BYTEMALTE Design System */
|
||||
--color-primary: #8888ff;
|
||||
--color-secondary: #3ddc84;
|
||||
--color-background: #0f172a;
|
||||
--color-surface: #1e293b;
|
||||
--color-surface-hover: #263548;
|
||||
--color-border: #334155;
|
||||
--color-text-high: #f8fafc;
|
||||
--color-text-muted: #b0bdd0;
|
||||
--color-error: #ef4444;
|
||||
|
||||
/* Typography */
|
||||
--font-family: 'Inter', system-ui, sans-serif;
|
||||
--font-size-h1: 2.5rem;
|
||||
--font-size-h2: 1.8rem;
|
||||
--font-size-h3: 1.2rem;
|
||||
--font-size-body: 1rem;
|
||||
--line-height: 1.6;
|
||||
|
||||
/* Borders */
|
||||
--radius-button: 8px;
|
||||
--radius-card: 16px;
|
||||
--radius-input: 8px;
|
||||
|
||||
/* Spacing */
|
||||
--section-padding: 5rem 2rem;
|
||||
--container-width: 1200px;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-body);
|
||||
line-height: var(--line-height);
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-text-high);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #a0a0ff;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: var(--font-size-h1);
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-h2);
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--font-size-h3);
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: var(--container-width);
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
13
src/app.d.ts
vendored
Normal file
13
src/app.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
16
src/app.html
Normal file
16
src/app.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="Bytemalte - Open source projects and community" />
|
||||
<meta name="theme-color" content="#0f172a" />
|
||||
<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=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
1
src/lib/assets/favicon.svg
Normal file
1
src/lib/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
105
src/lib/components/Footer.svelte
Normal file
105
src/lib/components/Footer.svelte
Normal file
@@ -0,0 +1,105 @@
|
||||
<footer class="footer">
|
||||
<div class="footer-container">
|
||||
<div class="footer-brand">
|
||||
<span class="footer-logo">Bytemalte</span>
|
||||
<p>Open source projects and community</p>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<div class="footer-column">
|
||||
<h4>Links</h4>
|
||||
<a href="https://gitea.malxte.de/Bytemalte" target="_blank" rel="noopener noreferrer">Code Repository</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© {new Date().getFullYear()} Bytemalte. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
.footer {
|
||||
background: var(--color-surface);
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: 3rem 2rem 1.5rem;
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
.footer-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.footer-brand p {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
display: flex;
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.footer-column h4 {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-high);
|
||||
margin-bottom: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.footer-column a {
|
||||
display: block;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 0.5rem;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.footer-column a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.footer-bottom p {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.footer-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
gap: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
155
src/lib/components/Hero.svelte
Normal file
155
src/lib/components/Hero.svelte
Normal file
@@ -0,0 +1,155 @@
|
||||
<section class="hero">
|
||||
<div class="hero-content">
|
||||
<div class="hero-badge">My Projects</div>
|
||||
<h1>
|
||||
Building <span class="gradient-text">tools</span> for<br />
|
||||
developers & communities
|
||||
</h1>
|
||||
<p class="hero-description">
|
||||
I'm, Bytemalte creat open source applications, libraries, and tools with Rust and modern web
|
||||
technologies. Explore my projects below.
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<a href="#projects" class="btn btn-primary">View Projects</a>
|
||||
<a
|
||||
href="https://gitea.malxte.de/Bytemalte"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-secondary"
|
||||
>
|
||||
Browse Code
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-glow"></div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 8rem 2rem 4rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 800px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-badge {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 9999px;
|
||||
background: rgba(136, 136, 255, 0.15);
|
||||
border: 1px solid rgba(136, 136, 255, 0.3);
|
||||
color: var(--color-primary);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: clamp(2.5rem, 5vw, 4rem);
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.hero-description {
|
||||
font-size: 1.125rem;
|
||||
color: var(--color-text-muted);
|
||||
max-width: 600px;
|
||||
margin: 0 auto 2.5rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.875rem 2rem;
|
||||
border-radius: var(--radius-button);
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #9d9dff;
|
||||
box-shadow: 0 4px 6px -1px rgba(136, 136, 255, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text-high);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--color-surface-hover);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.hero-glow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(136, 136, 255, 0.08) 0%,
|
||||
rgba(61, 220, 132, 0.04) 50%,
|
||||
transparent 70%
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.hero {
|
||||
padding: 6rem 1.5rem 3rem;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
91
src/lib/components/Navbar.svelte
Normal file
91
src/lib/components/Navbar.svelte
Normal file
@@ -0,0 +1,91 @@
|
||||
<script lang="ts">
|
||||
let scrolled = $state(false);
|
||||
|
||||
function handleScroll() {
|
||||
scrolled = window.scrollY > 20;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onscroll={handleScroll} />
|
||||
|
||||
<nav class="navbar" class:scrolled>
|
||||
<div class="nav-container">
|
||||
<a href="/" class="logo">
|
||||
<span class="logo-text">Bytemalte</span>
|
||||
</a>
|
||||
<div class="nav-links">
|
||||
<a href="#projects">Projects</a>
|
||||
<a href="https://gitea.malxte.de/Bytemalte" target="_blank" rel="noopener noreferrer">Code</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
padding: 1rem 2rem;
|
||||
transition: all 0.3s ease;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.navbar.scrolled {
|
||||
background: rgba(15, 23, 42, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: var(--color-text-high);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.navbar {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
156
src/lib/components/ProjectCard.svelte
Normal file
156
src/lib/components/ProjectCard.svelte
Normal file
@@ -0,0 +1,156 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
image_url: string;
|
||||
download_url: string;
|
||||
code_url: string;
|
||||
}
|
||||
|
||||
let { title, description, image_url, download_url, code_url }: Props = $props();
|
||||
|
||||
let imageError = $state(false);
|
||||
|
||||
function handleImageError() {
|
||||
imageError = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-image">
|
||||
{#if !imageError && image_url !== '#'}
|
||||
<img src={image_url} alt={title} onerror={handleImageError} loading="lazy" />
|
||||
{:else}
|
||||
<div class="card-image-placeholder">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M3.75 21h16.5A2.25 2.25 0 0022.5 18.75V5.25A2.25 2.25 0 0020.25 3H3.75A2.25 2.25 0 001.5 5.25v13.5A2.25 2.25 0 003.75 21z" />
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>{title}</h3>
|
||||
<p>{description}</p>
|
||||
<div class="card-actions">
|
||||
{#if code_url !== '#'}
|
||||
<a href={code_url} target="_blank" rel="noopener noreferrer" class="btn btn-ghost">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M16 18l6-6-6-6M8 6l-6 6 6 6" />
|
||||
</svg>
|
||||
Code
|
||||
</a>
|
||||
{/if}
|
||||
{#if download_url !== '#'}
|
||||
<a href={download_url} target="_blank" rel="noopener noreferrer" class="btn btn-outline">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" />
|
||||
</svg>
|
||||
Download
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-card);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.3);
|
||||
border-color: rgba(136, 136, 255, 0.3);
|
||||
}
|
||||
|
||||
.card-image {
|
||||
position: relative;
|
||||
height: 180px;
|
||||
background: linear-gradient(135deg, #1a2744, #0f172a);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.card-image-placeholder {
|
||||
color: var(--color-text-muted);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-body h3 {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--color-text-high);
|
||||
}
|
||||
|
||||
.card-body p {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.6;
|
||||
flex: 1;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--radius-button);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
color: var(--color-text-muted);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-ghost:hover {
|
||||
color: var(--color-text-high);
|
||||
border-color: var(--color-primary);
|
||||
background: rgba(136, 136, 255, 0.1);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: var(--color-primary);
|
||||
border: 1px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
163
src/lib/components/ProjectsSection.svelte
Normal file
163
src/lib/components/ProjectsSection.svelte
Normal file
@@ -0,0 +1,163 @@
|
||||
<script lang="ts">
|
||||
import ProjectCard from "./ProjectCard.svelte";
|
||||
|
||||
interface Project {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
image_url: string;
|
||||
download_url: string;
|
||||
code_url: string;
|
||||
}
|
||||
|
||||
let projects = $state<Project[]>([]);
|
||||
let loading = $state(true);
|
||||
let error = $state("");
|
||||
|
||||
async function loadProjects() {
|
||||
try {
|
||||
loading = true;
|
||||
error = "";
|
||||
const response = await fetch("/projects.json");
|
||||
if (!response.ok) throw new Error("Failed to load projects");
|
||||
projects = await response.json();
|
||||
} catch (e) {
|
||||
error = "Could not load projects. Please try again later.";
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
loadProjects();
|
||||
});
|
||||
</script>
|
||||
|
||||
<section id="projects" class="projects-section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>My Projects</h2>
|
||||
<p>
|
||||
A collection of open source tools, applications, and libraries
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>Loading projects...</p>
|
||||
</div>
|
||||
{:else if error}
|
||||
<div class="error-state">
|
||||
<p>{error}</p>
|
||||
<button onclick={loadProjects} class="btn btn-primary"
|
||||
>Retry</button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="projects-grid">
|
||||
{#each projects as project (project.id)}
|
||||
<ProjectCard
|
||||
title={project.title}
|
||||
description={project.description}
|
||||
image_url={project.image_url}
|
||||
download_url={project.download_url}
|
||||
code_url={project.code_url}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.projects-section {
|
||||
padding: 5rem 2rem;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.section-header p {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.projects-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 3rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--color-border);
|
||||
border-top-color: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.error-state {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.error-state p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: var(--radius-button);
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #9d9dff;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.projects-section {
|
||||
padding: 3rem 1.5rem;
|
||||
}
|
||||
|
||||
.projects-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1
src/lib/index.ts
Normal file
1
src/lib/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
12
src/routes/+layout.svelte
Normal file
12
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href={favicon} />
|
||||
</svelte:head>
|
||||
|
||||
{@render children()}
|
||||
13
src/routes/+page.svelte
Normal file
13
src/routes/+page.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import Navbar from '$lib/components/Navbar.svelte';
|
||||
import Hero from '$lib/components/Hero.svelte';
|
||||
import ProjectsSection from '$lib/components/ProjectsSection.svelte';
|
||||
import Footer from '$lib/components/Footer.svelte';
|
||||
</script>
|
||||
|
||||
<Navbar />
|
||||
<main>
|
||||
<Hero />
|
||||
<ProjectsSection />
|
||||
</main>
|
||||
<Footer />
|
||||
66
static/projects.json
Normal file
66
static/projects.json
Normal file
@@ -0,0 +1,66 @@
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"title": "Maltemedia",
|
||||
"description": "A modern, fast, and secure application to stay updated with the latest news from different worlds.",
|
||||
"image_url": "https://gitea.malxte.de/Bytemalte/marstemedia/raw/branch/main/public/logo.png",
|
||||
"download_url": "https://gitea.malxte.de/Bytemalte/marstemedia/releases/tag/v0.4.2",
|
||||
"code_url": "https://gitea.malxte.de/Bytemalte/marstemedia"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Easy-Nostr",
|
||||
"description": "A Rust Crate that makes Nostr-SDK easy to use with preprogrammed functions to call.",
|
||||
"image_url": "",
|
||||
"download_url": "#",
|
||||
"code_url": "https://gitea.malxte.de/Bytemalte/malxte_de"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Malxte.de Personal Rust Website",
|
||||
"description": "My personal website, fully programmed using Rust and Yew.",
|
||||
"image_url": "",
|
||||
"download_url": "#",
|
||||
"code_url": "https://gitea.malxte.de/Bytemalte/malxte_de"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Bytemalte.de Rust Community Website",
|
||||
"description": "A community website programmed with Rust and Leptos.",
|
||||
"image_url": "",
|
||||
"download_url": "#",
|
||||
"code_url": "https://gitea.malxte.de/Bytemalte/bytemalte_de"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Bytemalte Logo",
|
||||
"description": "The official Bytemalte logo project.",
|
||||
"image_url": "",
|
||||
"download_url": "#",
|
||||
"code_url": "#"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Mdo",
|
||||
"description": "Mdo is a very simple ToDo App fully written in Rust using Dioxus.",
|
||||
"image_url": "#",
|
||||
"download_url": "#",
|
||||
"code_url": "https://gitea.malxte.de/Bytemalte/mdo"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"title": "Mcalc",
|
||||
"description": "Mcalc is a very simple Calculator example written fully in Rust using Dioxus.",
|
||||
"image_url": "#",
|
||||
"download_url": "#",
|
||||
"code_url": "https://gitea.malxte.de/Bytemalte/mcalc"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"title": "Mflow",
|
||||
"description": "Mflow is a more advanced ToDo, Project management App example fully written in Rust using Dioxus",
|
||||
"image_url": "#",
|
||||
"download_url": "#",
|
||||
"code_url": "https://gitea.malxte.de/Bytemalte/mcalc"
|
||||
}
|
||||
]
|
||||
3
static/robots.txt
Normal file
3
static/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# allow crawling everything by default
|
||||
User-agent: *
|
||||
Disallow:
|
||||
17
svelte.config.js
Normal file
17
svelte.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
},
|
||||
vitePlugin: {
|
||||
dynamicCompileOptions: ({ filename }) =>
|
||||
filename.includes('node_modules') ? undefined : { runes: true }
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
||||
}
|
||||
6
vite.config.ts
Normal file
6
vite.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
||||
Reference in New Issue
Block a user