Stable Website

This commit is contained in:
2026-04-04 20:52:40 +02:00
commit 1d59d08360
38 changed files with 3120 additions and 0 deletions

23
.gitignore vendored Normal file
View 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-*

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict=true

3
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["svelte.svelte-vscode"]
}

24
DESIGN.md Normal file
View File

@@ -0,0 +1,24 @@
# Design System
## 1. Farbpalette
| Priorität | Funktion | Hex-Code | Anwendung |
|-----------|----------|----------|-----------|
| Primärfarbe | Rust-Orange | `#CE412B` | Markenidentität & Key-Visuals |
| Sekundärfarbe | Burn-Akzent | `#E89165` | Interaktive Elemente & Highlights |
| Hintergrund | Soft Deep Grey | `#242424` | Dark Mode Basis |
| Oberfläche | Layer Grau | `#2E2E2E` | Für Karten oder Sektionen, um Tiefe zu erzeugen. |
| Textfarbe | Off-White | `#E0E0E0` | Maximale Lesbarkeit auf dunklem Grund |
## 2. Typografie
### Überschriften (Headlines)
- **Schriftart:** Manrope
- **Stil:** Semibold (600)
### Fließtext (Body)
- **Schriftart:** IBM Plex Sans
- **Stil:** Regular (400)
### Auszeichnungsschrift (Technical)
- **Schriftart:** Fira Code

42
README.md Normal file
View 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.14.0 create --template minimal --types ts --add sveltekit-adapter="adapter:static" --install pnpm radixura_com
```
## 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
View File

@@ -0,0 +1,8 @@
default:
just --list
run:
pnpm run dev
open:
pnpm run open

23
package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "radixura-com",
"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-static": "^3.0.10",
"@sveltejs/kit": "^2.50.2",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"svelte": "^5.54.0",
"svelte-check": "^4.4.2",
"typescript": "^5.9.3",
"vite": "^7.3.1"
}
}

1026
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,2 @@
onlyBuiltDependencies:
- esbuild

96
src/app.css Normal file
View 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
View 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
View 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>

View 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

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,7 @@
export interface Social {
id: string;
name: string;
url: string;
status: 'active' | 'inactive';
color?: string;
}

View 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
View 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
View File

@@ -0,0 +1 @@
import '../app.css';

526
src/routes/+page.svelte Normal file
View 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
View File

@@ -0,0 +1 @@
export const prerender = true;

View 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>

View File

@@ -0,0 +1 @@
export const prerender = true;

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

21
static/radigura_white.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 256" width="100%" height="100%">
<defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@600;700&amp;display=swap');
</style>
</defs>
<g transform="translate(0, 0)">
<circle cx="128" cy="128" r="120" fill="#1A1B1E"/>
<circle cx="128" cy="128" r="114" fill="#2A2B2F"/>
<path d="M 70 90 L 128 195 L 186 90" fill="none" stroke="#FF6B22" stroke-width="12" stroke-linecap="round" stroke-linejoin="round"/>
<polygon points="128,102 154,117 154,147 128,162 102,147 102,117" fill="none" stroke="#FF9454" stroke-width="12" stroke-linejoin="round"/>
</g>
<g transform="translate(280, 50)">
<text x="0" y="100" font-family="Montserrat, Arial, sans-serif" font-size="72" font-weight="700" fill="#FFFFFF" letter-spacing="-2">Radixura</text>
<text x="0" y="135" font-family="Montserrat, Arial, sans-serif" font-size="18" font-weight="600" fill="#FFFFFF" letter-spacing="8" text-transform="uppercase">TECHNOLOGY</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

21
static/radixura.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 256" width="100%" height="100%">
<defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@600;700&amp;display=swap');
</style>
</defs>
<g transform="translate(0, 0)">
<circle cx="128" cy="128" r="120" fill="#1A1B1E"/>
<circle cx="128" cy="128" r="114" fill="#2A2B2F"/>
<path d="M 70 90 L 128 195 L 186 90" fill="none" stroke="#FF6B22" stroke-width="12" stroke-linecap="round" stroke-linejoin="round"/>
<polygon points="128,102 154,117 154,147 128,162 102,147 102,117" fill="none" stroke="#FF9454" stroke-width="12" stroke-linejoin="round"/>
</g>
<g transform="translate(280, 50)">
<text x="0" y="100" font-family="Montserrat, Arial, sans-serif" font-size="72" font-weight="700" fill="#1A1B1E" letter-spacing="-2">Radixura</text>
<text x="0" y="135" font-family="Montserrat, Arial, sans-serif" font-size="18" font-weight="600" fill="#666666" letter-spacing="8" text-transform="uppercase">TECHNOLOGY</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,36 @@
<svg width="1200" height="400" viewBox="0 0 1200 400" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="hexagons" width="50" height="43.3" patternUnits="userSpaceOnUse" viewBox="0 0 50 43.3">
<path d="M25 0L50 10.8V32.5L25 43.3L0 32.5V10.8L25 0Z" fill="none" stroke="#2E2E2E" stroke-width="1"/>
</pattern>
<linearGradient id="lineGrad" x1="0%" y1="100%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#CE412B" />
<stop offset="100%" stop-color="#E89165" />
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="5" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
<rect width="100%" height="100%" fill="#242424" />
<rect width="100%" height="100%" fill="url(#hexagons)" opacity="0.4" />
<path d="M-50 350 L300 150 L250 100" fill="none" stroke="url(#lineGrad)" stroke-width="4" opacity="0.6" filter="url(#glow)" />
<path d="M100 450 L450 200 L400 150" fill="none" stroke="#CE412B" stroke-width="2" opacity="0.3" />
<text x="1100" y="220" text-anchor="end" font-family="sans-serif" font-weight="600" font-size="72" fill="#E0E0E0" letter-spacing="-1">
Radixura
</text>
<text x="1100" y="260" text-anchor="end" font-family="monospace" font-size="18" fill="#E89165" letter-spacing="8" opacity="0.9">
TECHNOLOGY
</text>
<text x="50" y="360" font-family="monospace" font-size="12" fill="#2E2E2E" opacity="0.8">
impl Radixura { pub fn build() -> Self { ... } }
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

8
static/radixura_icon.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="100%" height="100%">
<circle cx="128" cy="128" r="120" fill="#1A1B1E"/>
<circle cx="128" cy="128" r="114" fill="#2A2B2F"/>
<path d="M 70 90 L 128 195 L 186 90" fill="none" stroke="#FF6B22" stroke-width="12" stroke-linecap="round" stroke-linejoin="round"/>
<polygon points="128,102 154,117 154,147 128,162 102,147 102,117" fill="none" stroke="#FF9454" stroke-width="12" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 480 B

19
svelte.config.js Normal file
View File

@@ -0,0 +1,19 @@
import adapter from '@sveltejs/adapter-static';
import { relative, sep } from 'node:path';
/** @type {import('@sveltejs/kit').Config} */
const config = {
compilerOptions: {
// defaults to rune mode for the project, except for `node_modules`. Can be removed in svelte 6.
runes: ({ filename }) => {
const relativePath = relative(import.meta.dirname, filename);
const pathSegments = relativePath.toLowerCase().split(sep);
const isExternalLibrary = pathSegments.includes('node_modules');
return isExternalLibrary ? undefined : true;
}
},
kit: { adapter: adapter() }
};
export default config;

20
tsconfig.json Normal file
View 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
View File

@@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});