Stable Website
This commit is contained in:
96
src/app.css
Normal file
96
src/app.css
Normal file
@@ -0,0 +1,96 @@
|
||||
:root {
|
||||
--color-primary: #CE412B;
|
||||
--color-secondary: #E89165;
|
||||
--color-bg: #242424;
|
||||
--color-surface: #2E2E2E;
|
||||
--color-text: #E0E0E0;
|
||||
--color-text-muted: #A0A0A0;
|
||||
--radius: 12px;
|
||||
--transition: 0.3s ease;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'IBM Plex Sans', system-ui, sans-serif;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
font-family: 'Manrope', system-ui, sans-serif;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
font-family: 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--color-secondary);
|
||||
color: var(--color-bg);
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
|
||||
.animate-fade-in-up {
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.4s ease forwards;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-secondary);
|
||||
text-decoration: none;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 80px 0;
|
||||
}
|
||||
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 {};
|
||||
15
src/app.html
Normal file
15
src/app.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="text-scale" content="scale" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400&family=IBM+Plex+Sans:wght@400;500&family=Manrope:wght@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 |
109
src/lib/components/CopyField.svelte
Normal file
109
src/lib/components/CopyField.svelte
Normal file
@@ -0,0 +1,109 @@
|
||||
<script lang="ts">
|
||||
import { copyToClipboard } from '$lib/utils/copyToClipboard.js';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
let { label, value }: Props = $props();
|
||||
|
||||
let copied = $state(false);
|
||||
|
||||
async function handleCopy() {
|
||||
const success = await copyToClipboard(value);
|
||||
if (success) {
|
||||
copied = true;
|
||||
setTimeout(() => copied = false, 2000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="copy-field" class:copied>
|
||||
<span class="label">{label}</span>
|
||||
<div class="value-container">
|
||||
<code class="value">{value}</code>
|
||||
<button class="copy-btn" onclick={handleCopy} aria-label="Kopieren">
|
||||
{#if copied}
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.copy-field {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius);
|
||||
padding: 20px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.copy-field:hover {
|
||||
border-color: var(--color-secondary);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 30px rgba(232, 145, 101, 0.1);
|
||||
}
|
||||
|
||||
.copy-field.copied {
|
||||
border-color: #4ade80;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.value-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 1rem;
|
||||
color: var(--color-text);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-secondary);
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: rgba(232, 145, 101, 0.1);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.copy-btn svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.copy-field.copied .copy-btn {
|
||||
color: #4ade80;
|
||||
}
|
||||
</style>
|
||||
54
src/lib/components/NavLink.svelte
Normal file
54
src/lib/components/NavLink.svelte
Normal file
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
href: string;
|
||||
children: () => any;
|
||||
external?: boolean;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
let { href, children, external = false, active = false }: Props = $props();
|
||||
</script>
|
||||
|
||||
<a
|
||||
{href}
|
||||
class="nav-link"
|
||||
class:active
|
||||
target={external ? '_blank' : undefined}
|
||||
rel={external ? 'noopener noreferrer' : undefined}
|
||||
>
|
||||
{@render children()}
|
||||
</a>
|
||||
|
||||
<style>
|
||||
.nav-link {
|
||||
position: relative;
|
||||
color: var(--color-text);
|
||||
font-weight: 500;
|
||||
padding: 8px 0;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-link::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, var(--color-primary), var(--color-secondary));
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.nav-link:hover::after,
|
||||
.nav-link.active::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
</style>
|
||||
225
src/lib/components/ProjectCard.svelte
Normal file
225
src/lib/components/ProjectCard.svelte
Normal file
@@ -0,0 +1,225 @@
|
||||
<script lang="ts">
|
||||
import type { Project } from '$lib/types/project.js';
|
||||
|
||||
interface Props {
|
||||
project: Project;
|
||||
}
|
||||
|
||||
let { project }: Props = $props();
|
||||
|
||||
function getImageUrl(image: string): string {
|
||||
// Check if it's an external URL
|
||||
if (image.startsWith('http://') || image.startsWith('https://')) {
|
||||
return image;
|
||||
}
|
||||
// Local image - prepend /images/
|
||||
return `/images/${image}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<article class="project-card">
|
||||
{#if project.image}
|
||||
<div class="card-image">
|
||||
<img src={getImageUrl(project.image)} alt={project.title} loading="lazy" />
|
||||
<div class="image-overlay"></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="card-content">
|
||||
<div class="card-header">
|
||||
<h3>{project.title}</h3>
|
||||
{#if project.status}
|
||||
<span class="status">{project.status}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<p class="description">{project.description}</p>
|
||||
|
||||
{#if project.tech}
|
||||
<div class="tech-stack">
|
||||
{#each project.tech as tech}
|
||||
<span class="tech-tag">{tech}</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="card-actions">
|
||||
{#if project.code}
|
||||
<a href={project.code} target="_blank" rel="noopener noreferrer" class="action-link">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="16 18 22 12 16 6"></polyline>
|
||||
<polyline points="8 6 2 12 8 18"></polyline>
|
||||
</svg>
|
||||
Code
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{#if project.liveUrl}
|
||||
<a href={project.liveUrl} target="_blank" rel="noopener noreferrer" class="action-link primary">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
</svg>
|
||||
Live Demo
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{#if project.downloadUrl}
|
||||
<a href={project.downloadUrl} target="_blank" rel="noopener noreferrer" class="action-link">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
Download
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<style>
|
||||
.project-card {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
}
|
||||
|
||||
.project-card:hover {
|
||||
border-color: var(--color-secondary);
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3), 0 0 30px rgba(232, 145, 101, 0.1);
|
||||
}
|
||||
|
||||
.card-image {
|
||||
position: relative;
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
.project-card:hover .card-image img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.image-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(to top, var(--color-surface) 0%, transparent 50%);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
font-size: 1.25rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 0.75rem;
|
||||
padding: 4px 10px;
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
|
||||
color: white;
|
||||
border-radius: 20px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tech-stack {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tech-tag {
|
||||
font-size: 0.8rem;
|
||||
padding: 4px 10px;
|
||||
background: rgba(232, 145, 101, 0.15);
|
||||
color: var(--color-secondary);
|
||||
border-radius: 6px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.action-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 8px;
|
||||
color: var(--color-secondary);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.action-link:hover {
|
||||
background: var(--color-secondary);
|
||||
color: var(--color-bg);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.action-link.primary {
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
|
||||
border-color: transparent;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-link.primary:hover {
|
||||
filter: brightness(1.1);
|
||||
box-shadow: 0 8px 20px rgba(206, 65, 43, 0.4);
|
||||
}
|
||||
|
||||
.action-link svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
144
src/lib/components/SocialItem.svelte
Normal file
144
src/lib/components/SocialItem.svelte
Normal file
@@ -0,0 +1,144 @@
|
||||
<script lang="ts">
|
||||
import type { Social } from '$lib/types/social.js';
|
||||
|
||||
interface Props {
|
||||
social: Social;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
let { social, delay = 0 }: Props = $props();
|
||||
</script>
|
||||
|
||||
<a
|
||||
href={social.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="social-card"
|
||||
style="--social-color: {social.color || '#E89165'}; animation-delay: {delay}ms"
|
||||
>
|
||||
<div class="social-icon">
|
||||
{#if social.id === 'youtube'}
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>
|
||||
</svg>
|
||||
{:else if social.id === 'instagram'}
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
|
||||
</svg>
|
||||
{:else if social.id === 'tiktok'}
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.33-.01-8.71.01-9.07z"/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="2" y1="12" x2="22" y2="12"></line>
|
||||
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="social-info">
|
||||
<span class="social-name">{social.name}</span>
|
||||
<span class="social-status" class:inactive={social.status === 'inactive'}>
|
||||
{social.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="arrow">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="7" y1="17" x2="17" y2="17"></line>
|
||||
<polyline points="17 7 17 17 7 17"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
.social-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.social-card:hover {
|
||||
border-color: var(--social-color);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.2), 0 0 20px color-mix(in srgb, var(--social-color) 30%, transparent);
|
||||
}
|
||||
|
||||
.social-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: var(--social-color);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.social-icon svg {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.social-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.social-name {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.social-status {
|
||||
font-size: 0.8rem;
|
||||
color: #4ade80;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.social-status.inactive {
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
opacity: 0;
|
||||
transform: translateX(-10px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.social-card:hover .arrow {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.arrow svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--social-color);
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
81
src/lib/components/TechItem.svelte
Normal file
81
src/lib/components/TechItem.svelte
Normal file
@@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
name: string;
|
||||
category: string;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
let { name, category, delay = 0 }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="tech-item" style="animation-delay: {delay}ms">
|
||||
<div class="tech-icon">
|
||||
<span class="tech-name">{name[0]}</span>
|
||||
</div>
|
||||
<div class="tech-info">
|
||||
<span class="tech-name-full">{name}</span>
|
||||
<span class="tech-category">{category}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tech-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tech-item:hover {
|
||||
border-color: var(--color-secondary);
|
||||
transform: translateY(-4px) scale(1.02);
|
||||
box-shadow: 0 15px 40px rgba(232, 145, 101, 0.15);
|
||||
}
|
||||
|
||||
.tech-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 1.25rem;
|
||||
color: white;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tech-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tech-name-full {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.tech-category {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
35
src/lib/data/projects.json
Normal file
35
src/lib/data/projects.json
Normal file
@@ -0,0 +1,35 @@
|
||||
[
|
||||
{
|
||||
"id": "maltemedia",
|
||||
"title": "Maltemedia",
|
||||
"description": "A modern, fast, and secure application to stay updated with the latest news from different worlds.",
|
||||
"image": "https://gitea.malxte.de/Bytemalte/marstemedia/raw/branch/main/public/logo.png",
|
||||
"tech": ["Rust", "Nostr", "Tauri"],
|
||||
"code": "https://gitea.malxte.de/Bytemalte/marstemedia",
|
||||
"liveUrl": null,
|
||||
"downloadUrl": "https://gitea.malxte.de/Bytemalte/marstemedia/releases/tag/v1",
|
||||
"status": "v1"
|
||||
},
|
||||
{
|
||||
"id": "easy-nostr",
|
||||
"title": "Easy-Nostr",
|
||||
"description": "A Rust Crate that makes Nostr-SDK easy to use with preprogrammed functions to call.",
|
||||
"image": null,
|
||||
"tech": ["Rust", "Crate", "Nostr"],
|
||||
"code": "https://gitea.malxte.de/Bytemalte/easy-nostr",
|
||||
"liveUrl": null,
|
||||
"downloadUrl": null,
|
||||
"status": "v1"
|
||||
},
|
||||
{
|
||||
"id": "the-maltemedia-puls",
|
||||
"title": "The Maltemedia Puls",
|
||||
"description": "An animation to present Maltemedia.",
|
||||
"image": null,
|
||||
"tech": ["Svelte 5", "Web", "Animation"],
|
||||
"code": "https://gitea.malxte.de/Bytemalte/the-maltemedia-puls",
|
||||
"liveUrl": null,
|
||||
"downloadUrl": null,
|
||||
"status": "v1.0"
|
||||
}
|
||||
]
|
||||
30
src/lib/data/socials.json
Normal file
30
src/lib/data/socials.json
Normal file
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"id": "youtube",
|
||||
"name": "YouTube",
|
||||
"url": "https://youtube.com/@radixura",
|
||||
"status": "active",
|
||||
"color": "#FF0000"
|
||||
},
|
||||
{
|
||||
"id": "nostr",
|
||||
"name": "Nostr",
|
||||
"url": "https://njump.me/_@radixura.com",
|
||||
"status": "active",
|
||||
"color": "#E4405F"
|
||||
},
|
||||
{
|
||||
"id": "instagram",
|
||||
"name": "Instagram",
|
||||
"url": "https://instagram.com/radixura",
|
||||
"status": "inactive",
|
||||
"color": "#E4405F"
|
||||
},
|
||||
{
|
||||
"id": "tiktok",
|
||||
"name": "TikTok",
|
||||
"url": "https://tiktok.com/@radixura",
|
||||
"status": "inactive",
|
||||
"color": "#000000"
|
||||
}
|
||||
]
|
||||
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.
|
||||
11
src/lib/types/project.ts
Normal file
11
src/lib/types/project.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface Project {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string | null;
|
||||
tech?: string[] | null;
|
||||
code?: string | null;
|
||||
liveUrl?: string | null;
|
||||
downloadUrl?: string | null;
|
||||
status?: string | null;
|
||||
}
|
||||
7
src/lib/types/social.ts
Normal file
7
src/lib/types/social.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface Social {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
status: 'active' | 'inactive';
|
||||
color?: string;
|
||||
}
|
||||
9
src/lib/utils/copyToClipboard.ts
Normal file
9
src/lib/utils/copyToClipboard.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export async function copyToClipboard(text: string): Promise<boolean> {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('Failed to copy:', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
261
src/routes/+layout.svelte
Normal file
261
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,261 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/state";
|
||||
|
||||
let { children } = $props();
|
||||
let mobileMenuOpen = $state(false);
|
||||
|
||||
const navItems = [
|
||||
{ href: "/#identity", label: "Identity" },
|
||||
{ href: "/#socials", label: "Socials" },
|
||||
{ href: "/#stack", label: "Tech Stack" },
|
||||
{ href: "/projects", label: "Projects" },
|
||||
];
|
||||
|
||||
function isActive(href: string): boolean {
|
||||
if (href.startsWith("/#")) {
|
||||
return (
|
||||
page.url.pathname === "/" && page.url.hash === href.substring(1)
|
||||
);
|
||||
}
|
||||
return page.url.pathname === href;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</svelte:head>
|
||||
|
||||
<header class="header">
|
||||
<div class="container header-content">
|
||||
<a href="/" class="logo">
|
||||
<img src="/radixura_icon.svg" alt="Radixura" class="logo-icon" />
|
||||
<span class="logo-text">Radixura</span>
|
||||
</a>
|
||||
|
||||
<nav class="desktop-nav">
|
||||
{#each navItems as item}
|
||||
<a
|
||||
href={item.href}
|
||||
class="nav-link"
|
||||
class:active={isActive(item.href)}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
<button
|
||||
class="mobile-menu-btn"
|
||||
onclick={() => (mobileMenuOpen = !mobileMenuOpen)}
|
||||
aria-label="Menu"
|
||||
>
|
||||
<span class="hamburger" class:open={mobileMenuOpen}></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if mobileMenuOpen}
|
||||
<nav class="mobile-nav">
|
||||
{#each navItems as item}
|
||||
<a href={item.href} onclick={() => (mobileMenuOpen = false)}
|
||||
>{item.label}</a
|
||||
>
|
||||
{/each}
|
||||
</nav>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<div class="header-spacer"></div>
|
||||
|
||||
{@render children()}
|
||||
|
||||
<style>
|
||||
:global(:root) {
|
||||
--color-primary: #ce412b;
|
||||
--color-secondary: #e89165;
|
||||
--color-bg: #242424;
|
||||
--color-surface: #2e2e2e;
|
||||
--color-text: #e0e0e0;
|
||||
--color-text-muted: #a0a0a0;
|
||||
--radius: 12px;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(36, 36, 36, 0.9);
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid rgba(232, 145, 101, 0.1);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-family: "Manrope", sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.desktop-nav {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
position: relative;
|
||||
color: var(--color-text);
|
||||
font-weight: 500;
|
||||
padding: 8px 0;
|
||||
transition: color 0.3s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-link::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--color-primary),
|
||||
var(--color-secondary)
|
||||
);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.nav-link:hover::after,
|
||||
.nav-link.active::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.mobile-menu-btn {
|
||||
display: none;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background: var(--color-text);
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.hamburger::before,
|
||||
.hamburger::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
background: var(--color-text);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.hamburger::before {
|
||||
top: -8px;
|
||||
}
|
||||
.hamburger::after {
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.hamburger.open {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.hamburger.open::before {
|
||||
transform: rotate(45deg);
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.hamburger.open::after {
|
||||
transform: rotate(-45deg);
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
padding: 20px 24px;
|
||||
background: var(--color-surface);
|
||||
border-top: 1px solid rgba(232, 145, 101, 0.2);
|
||||
animation: slideDown 0.3s ease;
|
||||
}
|
||||
|
||||
.mobile-nav a {
|
||||
padding: 16px 0;
|
||||
color: var(--color-text);
|
||||
font-size: 1.1rem;
|
||||
border-bottom: 1px solid rgba(232, 145, 101, 0.1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.mobile-nav a:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.header-spacer {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.desktop-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-menu-btn {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1
src/routes/+layout.ts
Normal file
1
src/routes/+layout.ts
Normal file
@@ -0,0 +1 @@
|
||||
import '../app.css';
|
||||
526
src/routes/+page.svelte
Normal file
526
src/routes/+page.svelte
Normal file
@@ -0,0 +1,526 @@
|
||||
<script lang="ts">
|
||||
import CopyField from "$lib/components/CopyField.svelte";
|
||||
import TechItem from "$lib/components/TechItem.svelte";
|
||||
import SocialItem from "$lib/components/SocialItem.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import socials from "$lib/data/socials.json";
|
||||
import type { Social } from "$lib/types/social.js";
|
||||
|
||||
let mounted = $state(false);
|
||||
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
});
|
||||
|
||||
const typedSocials: Social[] = socials;
|
||||
|
||||
const techStack = [
|
||||
{ name: "Iced", category: "GUI Development" },
|
||||
{ name: "Yew", category: "WebAssembly Apps" },
|
||||
{ name: "Leptos", category: "Fullstack Rust" },
|
||||
{ name: "Svelte 5", category: "Animated & Static Sites" },
|
||||
{ name: "Burn", category: "Neural Networks & AI" },
|
||||
];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Radixura - Rust Developer</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Radixura - Rust Developer specializing in Iced, Yew, Leptos, Svelte 5, and Burn neural networks."
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<main class="main">
|
||||
<!-- Hero Section -->
|
||||
<section id="hero" class="hero">
|
||||
<div class="container">
|
||||
<div class="hero-content" class:mounted>
|
||||
<div class="hero-badge">
|
||||
<span class="badge-text">Rust Developer</span>
|
||||
</div>
|
||||
|
||||
<h1 class="hero-title">
|
||||
Building the future<br />
|
||||
<span class="gradient-text">with Rust</span>
|
||||
</h1>
|
||||
|
||||
<p class="hero-subtitle">
|
||||
Crafting high-performance applications, from desktop GUIs to
|
||||
neural networks
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="hero-visual" class:mounted>
|
||||
<img
|
||||
src="/radixura_bannar.svg"
|
||||
alt="Radixura Banner"
|
||||
class="banner-image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="scroll-indicator">
|
||||
<div class="scroll-arrow"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Identity Section -->
|
||||
<section id="identity" class="identity-section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<span class="section-label">Connect</span>
|
||||
<h2>Digital Identity</h2>
|
||||
</div>
|
||||
|
||||
<div class="identity-grid">
|
||||
<div class="identity-column">
|
||||
<h3>Nostr</h3>
|
||||
<p>NIP-05 verified address and public key</p>
|
||||
<div class="copy-fields">
|
||||
<div class="copy-field-wrapper">
|
||||
<CopyField
|
||||
label="NIP-05 Address"
|
||||
value="_@radixura.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="copy-field-wrapper">
|
||||
<CopyField
|
||||
label="Public Key (npub)"
|
||||
value="npub1guff8dkdz7k47zh0mx26y0mmx5l6np57jjkvmhnyrak0n50r5hxqjdnsra"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="identity-column">
|
||||
<h3>E-Mail</h3>
|
||||
<p>E-Mail Communication (optional better is Nostr)</p>
|
||||
<div class="copy-fields">
|
||||
<div class="copy-field-wrapper">
|
||||
<CopyField
|
||||
label="E-Mail Address"
|
||||
value="info@radixura.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Socials Section -->
|
||||
<section id="socials" class="socials-section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<span class="section-label">Follow</span>
|
||||
<h2>Social Media</h2>
|
||||
<p>Find me on various platforms</p>
|
||||
</div>
|
||||
|
||||
<div class="social-grid">
|
||||
{#each typedSocials as social, i}
|
||||
<SocialItem {social} delay={i * 100} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Tech Stack Section -->
|
||||
<section id="stack" class="stack-section">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<span class="section-label">Tools</span>
|
||||
<h2>Technology Stack</h2>
|
||||
<p>The Rust ecosystem powers everything I build</p>
|
||||
</div>
|
||||
|
||||
<div class="tech-grid">
|
||||
{#each techStack as tech, i}
|
||||
<TechItem
|
||||
name={tech.name}
|
||||
category={tech.category}
|
||||
delay={i * 100}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA Section -->
|
||||
<section id="cta" class="cta-section">
|
||||
<div class="container">
|
||||
<div class="cta-content">
|
||||
<h2>Want to see my work?</h2>
|
||||
<p>
|
||||
Explore my projects built with Rust and modern web
|
||||
technologies
|
||||
</p>
|
||||
<a href="/projects" class="cta-button">
|
||||
View Projects
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
<polyline points="12 5 19 12 12 19"></polyline>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
min-height: calc(100vh - 80px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: 60px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 800px;
|
||||
height: 800px;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(232, 145, 101, 0.08) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hero .container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 60px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease;
|
||||
}
|
||||
|
||||
.hero-content.mounted {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.hero-badge {
|
||||
display: inline-block;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.badge-text {
|
||||
padding: 8px 16px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(206, 65, 43, 0.2),
|
||||
rgba(232, 145, 101, 0.2)
|
||||
);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 20px;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: clamp(2.5rem, 6vw, 4.5rem);
|
||||
line-height: 1.1;
|
||||
margin-bottom: 24px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.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-subtitle {
|
||||
font-size: 1.25rem;
|
||||
color: var(--color-text-muted);
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.hero-visual {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: all 0.8s ease 0.3s;
|
||||
}
|
||||
|
||||
.hero-visual.mounted {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
height: auto;
|
||||
filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.4));
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-indicator {
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
animation: bounce 2s infinite;
|
||||
}
|
||||
|
||||
.scroll-arrow {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-right: 2px solid var(--color-secondary);
|
||||
border-bottom: 2px solid var(--color-secondary);
|
||||
transform: rotate(45deg);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
20%,
|
||||
50%,
|
||||
80%,
|
||||
100% {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
40% {
|
||||
transform: translateX(-50%) translateY(-10px);
|
||||
}
|
||||
60% {
|
||||
transform: translateX(-50%) translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Section Styles */
|
||||
.identity-section,
|
||||
.stack-section,
|
||||
.cta-section {
|
||||
padding: 100px 0;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
text-align: center;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
display: inline-block;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: clamp(2rem, 4vw, 3rem);
|
||||
margin-bottom: 16px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.section-header p {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Identity Section */
|
||||
.identity-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 40px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.identity-column {
|
||||
background: var(--color-surface);
|
||||
padding: 30px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid rgba(232, 145, 101, 0.1);
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
.identity-column:hover {
|
||||
border-color: rgba(232, 145, 101, 0.3);
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.identity-column h3 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.identity-column p {
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.copy-fields {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Socials Section */
|
||||
.socials-section {
|
||||
padding: 100px 0;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
transparent 0%,
|
||||
rgba(46, 46, 46, 0.2) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
.social-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Tech Stack Section */
|
||||
.stack-section {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
transparent 0%,
|
||||
rgba(46, 46, 46, 0.3) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
.tech-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* CTA Section */
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cta-content {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.cta-content h2 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 16px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.cta-content p {
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 32px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.cta-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 32px;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-primary),
|
||||
var(--color-secondary)
|
||||
);
|
||||
color: white;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 10px 30px rgba(206, 65, 43, 0.3);
|
||||
}
|
||||
|
||||
.cta-button:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 15px 40px rgba(206, 65, 43, 0.4);
|
||||
}
|
||||
|
||||
.cta-button svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.cta-button:hover svg {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 900px) {
|
||||
.hero .container {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hero-visual {
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.tech-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1
src/routes/+page.ts
Normal file
1
src/routes/+page.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const prerender = true;
|
||||
216
src/routes/projects/+page.svelte
Normal file
216
src/routes/projects/+page.svelte
Normal file
@@ -0,0 +1,216 @@
|
||||
<script lang="ts">
|
||||
import ProjectCard from '$lib/components/ProjectCard.svelte';
|
||||
import projects from '$lib/data/projects.json';
|
||||
import type { Project } from '$lib/types/project.js';
|
||||
|
||||
const typedProjects: Project[] = projects;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Projects | Radixura</title>
|
||||
<meta name="description" content="Browse all projects by Radixura - Rust Developer. Desktop apps, web applications, and neural networks." />
|
||||
</svelte:head>
|
||||
|
||||
<main class="projects-page">
|
||||
<section class="hero-section">
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<span class="page-label">Portfolio</span>
|
||||
<h1>My Projects</h1>
|
||||
<p>A collection of applications built with Rust and modern web technologies</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="projects-grid-section">
|
||||
<div class="container">
|
||||
<div class="projects-grid">
|
||||
{#each typedProjects as project, i}
|
||||
<div class="project-wrapper" style="animation-delay: {i * 100}ms">
|
||||
<ProjectCard {project} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="contact-cta">
|
||||
<div class="container">
|
||||
<div class="cta-box">
|
||||
<h2>Have a project in mind?</h2>
|
||||
<p>Let's build something amazing together with Rust</p>
|
||||
<div class="cta-links">
|
||||
<a href="/" class="cta-link secondary">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
|
||||
<polyline points="9 22 9 12 15 12 15 22"></polyline>
|
||||
</svg>
|
||||
Back to Home
|
||||
</a>
|
||||
<a href="mailto:info@radixura.com" class="cta-link secondary">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
Contact with E-Mail
|
||||
</a>
|
||||
<a href="https://njump.me/npub1guff8dkdz7k47zh0mx26y0mmx5l6np57jjkvmhnyrak0n50r5hxqjdnsra" target="_blank" rel="noopener noreferrer" class="cta-link primary">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
Contact on Nostr
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.projects-page {
|
||||
min-height: calc(100vh - 80px);
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
padding: 80px 0 60px;
|
||||
text-align: center;
|
||||
background: linear-gradient(180deg, transparent 0%, rgba(46, 46, 46, 0.2) 100%);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-label {
|
||||
display: inline-block;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: clamp(2.5rem, 5vw, 4rem);
|
||||
margin-bottom: 16px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.projects-grid-section {
|
||||
padding: 40px 0 80px;
|
||||
}
|
||||
|
||||
.projects-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.project-wrapper {
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.contact-cta {
|
||||
padding: 60px 0 100px;
|
||||
}
|
||||
|
||||
.cta-box {
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius);
|
||||
padding: 60px;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(232, 145, 101, 0.1);
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
.cta-box:hover {
|
||||
border-color: rgba(232, 145, 101, 0.3);
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.cta-box h2 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 12px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.cta-box p {
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 32px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.cta-links {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cta-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 14px 28px;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.cta-link svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.cta-link.primary {
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
|
||||
color: white;
|
||||
box-shadow: 0 8px 25px rgba(206, 65, 43, 0.3);
|
||||
}
|
||||
|
||||
.cta-link.primary:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 12px 35px rgba(206, 65, 43, 0.4);
|
||||
}
|
||||
|
||||
.cta-link.secondary {
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-secondary);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.cta-link.secondary:hover {
|
||||
background: rgba(232, 145, 101, 0.1);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.projects-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cta-box {
|
||||
padding: 40px 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1
src/routes/projects/+page.ts
Normal file
1
src/routes/projects/+page.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const prerender = true;
|
||||
Reference in New Issue
Block a user