Initial commit: SvelteKit 3D printer housing documentation site
Set up SvelteKit project with TypeScript, i18n support (English/German), dark/light theme toggle, and component-based documentation structure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules/
|
||||
.svelte-kit/
|
||||
build/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.DS_Store
|
||||
2124
package-lock.json
generated
Normal file
2124
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
package.json
Normal file
22
package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "druckwerk",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"svelte": "^5.16.0",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.7",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"lucide-svelte": "^0.469.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
94
src/app.css
Normal file
94
src/app.css
Normal file
@@ -0,0 +1,94 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
/* Wire colors from the guide */
|
||||
--color-wire-brown: #8B4513;
|
||||
--color-wire-blue: #1E90FF;
|
||||
--color-wire-pe: #9ACD32;
|
||||
--color-wire-red: #DC2626;
|
||||
--color-wire-orange: #F97316;
|
||||
--color-wire-black: #1F2937;
|
||||
|
||||
/* UI colors */
|
||||
--color-primary: #1e293b;
|
||||
--color-primary-light: #334155;
|
||||
--color-accent: #f97316;
|
||||
--color-accent-hover: #ea580c;
|
||||
--color-warning: #EAB308;
|
||||
--color-danger: #DC2626;
|
||||
--color-success: #22c55e;
|
||||
|
||||
/* Dark mode backgrounds */
|
||||
--color-bg-dark: #0f172a;
|
||||
--color-bg-dark-secondary: #1e293b;
|
||||
--color-bg-dark-tertiary: #334155;
|
||||
|
||||
/* Light mode backgrounds */
|
||||
--color-bg-light: #f8fafc;
|
||||
--color-bg-light-secondary: #f1f5f9;
|
||||
--color-bg-light-tertiary: #e2e8f0;
|
||||
|
||||
/* Text colors */
|
||||
--color-text-dark: #f8fafc;
|
||||
--color-text-dark-muted: #94a3b8;
|
||||
--color-text-light: #0f172a;
|
||||
--color-text-light-muted: #64748b;
|
||||
}
|
||||
|
||||
/* Base styles */
|
||||
html {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* Dark mode by default, light mode when .light class is on html */
|
||||
html {
|
||||
background-color: var(--color-bg-dark);
|
||||
color: var(--color-text-dark);
|
||||
}
|
||||
|
||||
html.light {
|
||||
background-color: var(--color-bg-light);
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
|
||||
/* Smooth transitions for theme changes */
|
||||
html {
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-bg-dark-secondary);
|
||||
}
|
||||
|
||||
html.light ::-webkit-scrollbar-track {
|
||||
background: var(--color-bg-light-secondary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-primary-light);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Wire color utility classes */
|
||||
.wire-l { color: var(--color-wire-brown); }
|
||||
.wire-n { color: var(--color-wire-blue); }
|
||||
.wire-pe { color: var(--color-wire-pe); }
|
||||
.wire-12v { color: var(--color-wire-red); }
|
||||
.wire-5v { color: var(--color-wire-orange); }
|
||||
.wire-gnd { color: var(--color-wire-black); }
|
||||
|
||||
.bg-wire-l { background-color: var(--color-wire-brown); }
|
||||
.bg-wire-n { background-color: var(--color-wire-blue); }
|
||||
.bg-wire-pe { background-color: var(--color-wire-pe); }
|
||||
.bg-wire-12v { background-color: var(--color-wire-red); }
|
||||
.bg-wire-5v { background-color: var(--color-wire-orange); }
|
||||
.bg-wire-gnd { background-color: var(--color-wire-black); }
|
||||
13
src/app.d.ts
vendored
Normal file
13
src/app.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
13
src/app.html
Normal file
13
src/app.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="de" class="scroll-smooth">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="DruckWerk - Professionelles 3D-Drucker Gehäuse mit ESP32 Steuerung und Home Assistant Integration" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
104
src/lib/components/Checklist.svelte
Normal file
104
src/lib/components/Checklist.svelte
Normal file
@@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import { Check } from 'lucide-svelte';
|
||||
|
||||
interface ChecklistSection {
|
||||
title: string;
|
||||
items: string[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
sections: ChecklistSection[];
|
||||
storageKey?: string;
|
||||
}
|
||||
|
||||
let { sections, storageKey = 'druckwerk-checklist' }: Props = $props();
|
||||
|
||||
// Load checked items from localStorage
|
||||
function loadChecked(): Set<string> {
|
||||
if (!browser) return new Set();
|
||||
try {
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
return stored ? new Set(JSON.parse(stored)) : new Set();
|
||||
} catch {
|
||||
return new Set();
|
||||
}
|
||||
}
|
||||
|
||||
let checkedItems = $state(loadChecked());
|
||||
|
||||
function toggleItem(item: string) {
|
||||
if (checkedItems.has(item)) {
|
||||
checkedItems.delete(item);
|
||||
} else {
|
||||
checkedItems.add(item);
|
||||
}
|
||||
checkedItems = new Set(checkedItems); // Trigger reactivity
|
||||
if (browser) {
|
||||
localStorage.setItem(storageKey, JSON.stringify([...checkedItems]));
|
||||
}
|
||||
}
|
||||
|
||||
function isChecked(item: string): boolean {
|
||||
return checkedItems.has(item);
|
||||
}
|
||||
|
||||
// Calculate progress
|
||||
const totalItems = $derived(sections.reduce((acc, s) => acc + s.items.length, 0));
|
||||
const checkedCount = $derived(checkedItems.size);
|
||||
const progress = $derived(totalItems > 0 ? (checkedCount / totalItems) * 100 : 0);
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Progress bar -->
|
||||
<div class="rounded-lg p-4 bg-bg-dark-secondary [.light_&]:bg-bg-light-secondary">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-sm font-medium text-text-dark [.light_&]:text-text-light">
|
||||
{checkedCount} / {totalItems}
|
||||
</span>
|
||||
<span class="text-sm text-text-dark-muted [.light_&]:text-text-light-muted">
|
||||
{Math.round(progress)}%
|
||||
</span>
|
||||
</div>
|
||||
<div class="h-2 rounded-full bg-bg-dark-tertiary [.light_&]:bg-bg-light-tertiary overflow-hidden">
|
||||
<div
|
||||
class="h-full rounded-full bg-accent transition-all duration-300"
|
||||
style="width: {progress}%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Checklist sections -->
|
||||
{#each sections as section}
|
||||
<div class="rounded-lg border p-4
|
||||
border-bg-dark-tertiary [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-4 text-text-dark [.light_&]:text-text-light">
|
||||
{section.title}
|
||||
</h4>
|
||||
<ul class="space-y-2">
|
||||
{#each section.items as item}
|
||||
<li>
|
||||
<button
|
||||
onclick={() => toggleItem(item)}
|
||||
class="w-full flex items-center gap-3 p-2 rounded-lg text-left transition-colors
|
||||
hover:bg-bg-dark-secondary [.light_&]:hover:bg-bg-light-secondary"
|
||||
>
|
||||
<span class="flex-shrink-0 w-5 h-5 rounded border-2 flex items-center justify-center transition-colors
|
||||
{isChecked(item)
|
||||
? 'bg-success border-success text-white'
|
||||
: 'border-bg-dark-tertiary [.light_&]:border-bg-light-tertiary'
|
||||
}">
|
||||
{#if isChecked(item)}
|
||||
<Check class="w-3 h-3" />
|
||||
{/if}
|
||||
</span>
|
||||
<span class="text-sm {isChecked(item) ? 'line-through text-text-dark-muted [.light_&]:text-text-light-muted' : 'text-text-dark [.light_&]:text-text-light'}">
|
||||
{item}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
36
src/lib/components/ConfigPlaceholder.svelte
Normal file
36
src/lib/components/ConfigPlaceholder.svelte
Normal file
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { FileCode, Clock } from 'lucide-svelte';
|
||||
import { t } from '$lib/stores/language';
|
||||
|
||||
interface Props {
|
||||
filename: string;
|
||||
title: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
let { filename, title, placeholder }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="rounded-lg border-2 border-dashed p-6
|
||||
border-accent/40 bg-accent/5">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<FileCode class="w-5 h-5 text-accent" />
|
||||
<span class="font-mono text-sm text-accent">{filename}</span>
|
||||
</div>
|
||||
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">
|
||||
{title}
|
||||
</h4>
|
||||
|
||||
<div class="rounded-lg p-4 font-mono text-sm
|
||||
bg-bg-dark-secondary [.light_&]:bg-bg-light-secondary">
|
||||
<code class="text-text-dark-muted [.light_&]:text-text-light-muted">
|
||||
# {placeholder || $t.config.esphome.placeholder}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex items-center gap-2 text-sm text-text-dark-muted [.light_&]:text-text-light-muted">
|
||||
<Clock class="w-4 h-4" />
|
||||
<span>{$t.config.pending}</span>
|
||||
</div>
|
||||
</div>
|
||||
114
src/lib/components/Header.svelte
Normal file
114
src/lib/components/Header.svelte
Normal file
@@ -0,0 +1,114 @@
|
||||
<script lang="ts">
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { language, t } from '$lib/stores/language';
|
||||
import { Sun, Moon, Menu, X } from 'lucide-svelte';
|
||||
|
||||
let mobileMenuOpen = $state(false);
|
||||
|
||||
function toggleTheme() {
|
||||
theme.toggle();
|
||||
}
|
||||
|
||||
function toggleLanguage() {
|
||||
language.toggle();
|
||||
}
|
||||
|
||||
function toggleMobileMenu() {
|
||||
mobileMenuOpen = !mobileMenuOpen;
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="fixed top-0 left-0 right-0 z-50 border-b backdrop-blur-md
|
||||
bg-bg-dark/90 border-bg-dark-tertiary
|
||||
dark:bg-bg-dark/90 dark:border-bg-dark-tertiary
|
||||
[.light_&]:bg-bg-light/90 [.light_&]:border-bg-light-tertiary">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 bg-accent rounded-lg flex items-center justify-center">
|
||||
<span class="text-white font-bold text-sm">DW</span>
|
||||
</div>
|
||||
<span class="font-bold text-xl text-text-dark [.light_&]:text-text-light">
|
||||
DruckWerk
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Navigation placeholder - will be added separately -->
|
||||
<div class="hidden md:flex items-center gap-4">
|
||||
<!-- Language Toggle -->
|
||||
<button
|
||||
onclick={toggleLanguage}
|
||||
class="px-3 py-1.5 rounded-lg text-sm font-medium transition-colors
|
||||
bg-bg-dark-secondary hover:bg-bg-dark-tertiary text-text-dark
|
||||
[.light_&]:bg-bg-light-secondary [.light_&]:hover:bg-bg-light-tertiary [.light_&]:text-text-light"
|
||||
>
|
||||
{$language === 'de' ? 'EN' : 'DE'}
|
||||
</button>
|
||||
|
||||
<!-- Theme Toggle -->
|
||||
<button
|
||||
onclick={toggleTheme}
|
||||
class="p-2 rounded-lg transition-colors
|
||||
bg-bg-dark-secondary hover:bg-bg-dark-tertiary text-text-dark
|
||||
[.light_&]:bg-bg-light-secondary [.light_&]:hover:bg-bg-light-tertiary [.light_&]:text-text-light"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{#if $theme === 'dark'}
|
||||
<Sun class="w-5 h-5" />
|
||||
{:else}
|
||||
<Moon class="w-5 h-5" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu button -->
|
||||
<button
|
||||
onclick={toggleMobileMenu}
|
||||
class="md:hidden p-2 rounded-lg transition-colors
|
||||
bg-bg-dark-secondary hover:bg-bg-dark-tertiary text-text-dark
|
||||
[.light_&]:bg-bg-light-secondary [.light_&]:hover:bg-bg-light-tertiary [.light_&]:text-text-light"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
{#if mobileMenuOpen}
|
||||
<X class="w-6 h-6" />
|
||||
{:else}
|
||||
<Menu class="w-6 h-6" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu -->
|
||||
{#if mobileMenuOpen}
|
||||
<div class="md:hidden border-t
|
||||
bg-bg-dark border-bg-dark-tertiary
|
||||
[.light_&]:bg-bg-light [.light_&]:border-bg-light-tertiary">
|
||||
<div class="px-4 py-4 space-y-3">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
onclick={toggleLanguage}
|
||||
class="flex-1 px-3 py-2 rounded-lg text-sm font-medium transition-colors
|
||||
bg-bg-dark-secondary hover:bg-bg-dark-tertiary text-text-dark
|
||||
[.light_&]:bg-bg-light-secondary [.light_&]:hover:bg-bg-light-tertiary [.light_&]:text-text-light"
|
||||
>
|
||||
{$language === 'de' ? 'Switch to English' : 'Zu Deutsch wechseln'}
|
||||
</button>
|
||||
<button
|
||||
onclick={toggleTheme}
|
||||
class="px-3 py-2 rounded-lg transition-colors
|
||||
bg-bg-dark-secondary hover:bg-bg-dark-tertiary text-text-dark
|
||||
[.light_&]:bg-bg-light-secondary [.light_&]:hover:bg-bg-light-tertiary [.light_&]:text-text-light"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{#if $theme === 'dark'}
|
||||
<Sun class="w-5 h-5" />
|
||||
{:else}
|
||||
<Moon class="w-5 h-5" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
77
src/lib/components/Hero.svelte
Normal file
77
src/lib/components/Hero.svelte
Normal file
@@ -0,0 +1,77 @@
|
||||
<script lang="ts">
|
||||
import { t } from '$lib/stores/language';
|
||||
import { Cpu, Home, Layers } from 'lucide-svelte';
|
||||
</script>
|
||||
|
||||
<section class="relative py-20 md:py-32 overflow-hidden">
|
||||
<!-- Background gradient -->
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-accent/10 via-transparent to-primary/20"></div>
|
||||
|
||||
<!-- Grid pattern overlay -->
|
||||
<div class="absolute inset-0 opacity-5"
|
||||
style="background-image: url('data:image/svg+xml,%3Csvg width=%2240%22 height=%2240%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%3Cpath d=%22M0 0h40v40H0z%22 fill=%22none%22 stroke=%22%23fff%22 stroke-width=%220.5%22/%3E%3C/svg%3E');">
|
||||
</div>
|
||||
|
||||
<div class="relative max-w-4xl mx-auto text-center px-4">
|
||||
<!-- Logo -->
|
||||
<div class="inline-flex items-center justify-center w-20 h-20 mb-8 rounded-2xl bg-accent shadow-lg shadow-accent/25">
|
||||
<svg viewBox="0 0 48 48" class="w-12 h-12 text-white" fill="currentColor">
|
||||
<!-- Simplified enclosure icon -->
|
||||
<rect x="6" y="10" width="36" height="28" rx="2" fill="none" stroke="currentColor" stroke-width="2.5"/>
|
||||
<line x1="6" y1="18" x2="42" y2="18" stroke="currentColor" stroke-width="2"/>
|
||||
<circle cx="12" cy="14" r="1.5" fill="currentColor"/>
|
||||
<circle cx="17" cy="14" r="1.5" fill="currentColor"/>
|
||||
<rect x="14" y="24" width="20" height="10" rx="1" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||
<line x1="10" y1="42" x2="10" y2="38" stroke="currentColor" stroke-width="2"/>
|
||||
<line x1="38" y1="42" x2="38" y2="38" stroke="currentColor" stroke-width="2"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h1 class="text-4xl md:text-6xl font-bold mb-4 text-text-dark [.light_&]:text-text-light">
|
||||
{$t.hero.title}
|
||||
</h1>
|
||||
|
||||
<!-- Subtitle -->
|
||||
<p class="text-xl md:text-2xl mb-6 text-accent font-medium">
|
||||
{$t.hero.subtitle}
|
||||
</p>
|
||||
|
||||
<!-- Tagline badges -->
|
||||
<div class="flex flex-wrap justify-center gap-3 mb-8">
|
||||
<span class="inline-flex items-center gap-2 px-4 py-2 rounded-full text-sm
|
||||
bg-bg-dark-secondary border border-bg-dark-tertiary text-text-dark
|
||||
[.light_&]:bg-bg-light-secondary [.light_&]:border-bg-light-tertiary [.light_&]:text-text-light">
|
||||
<Cpu class="w-4 h-4 text-accent" />
|
||||
ESP32
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-2 px-4 py-2 rounded-full text-sm
|
||||
bg-bg-dark-secondary border border-bg-dark-tertiary text-text-dark
|
||||
[.light_&]:bg-bg-light-secondary [.light_&]:border-bg-light-tertiary [.light_&]:text-text-light">
|
||||
<Home class="w-4 h-4 text-accent" />
|
||||
Home Assistant
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-2 px-4 py-2 rounded-full text-sm
|
||||
bg-bg-dark-secondary border border-bg-dark-tertiary text-text-dark
|
||||
[.light_&]:bg-bg-light-secondary [.light_&]:border-bg-light-tertiary [.light_&]:text-text-light">
|
||||
<Layers class="w-4 h-4 text-accent" />
|
||||
Dual-Zone
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-lg text-text-dark-muted [.light_&]:text-text-light-muted max-w-2xl mx-auto">
|
||||
{$t.hero.description}
|
||||
</p>
|
||||
|
||||
<!-- Scroll indicator -->
|
||||
<div class="mt-12 animate-bounce">
|
||||
<a href="#overview" class="inline-flex flex-col items-center gap-2 text-text-dark-muted [.light_&]:text-text-light-muted hover:text-accent transition-colors">
|
||||
<span class="text-sm">{$t.nav.overview}</span>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
113
src/lib/components/MaterialsList.svelte
Normal file
113
src/lib/components/MaterialsList.svelte
Normal file
@@ -0,0 +1,113 @@
|
||||
<script lang="ts">
|
||||
import { ExternalLink, Printer } from 'lucide-svelte';
|
||||
import { t } from '$lib/stores/language';
|
||||
|
||||
interface Material {
|
||||
category: string;
|
||||
article: string;
|
||||
quantity: string;
|
||||
specification: string;
|
||||
link: string | null;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
materials: Material[];
|
||||
}
|
||||
|
||||
let { materials }: Props = $props();
|
||||
|
||||
// Group materials by category
|
||||
const groupedMaterials = $derived(
|
||||
materials.reduce((acc, item) => {
|
||||
if (!acc[item.category]) {
|
||||
acc[item.category] = [];
|
||||
}
|
||||
acc[item.category].push(item);
|
||||
return acc;
|
||||
}, {} as Record<string, Material[]>)
|
||||
);
|
||||
|
||||
const categoryOrder = [
|
||||
'Möbel', 'Holz', 'Tür', 'Druckteile', 'Strom', 'Verdrahtung',
|
||||
'Controller', 'Sensorik', 'Elektronik', 'Lüfter', 'Filter', 'Beleuchtung', 'Werkzeug'
|
||||
];
|
||||
|
||||
const sortedCategories = $derived(
|
||||
Object.keys(groupedMaterials).sort((a, b) => {
|
||||
const indexA = categoryOrder.indexOf(a);
|
||||
const indexB = categoryOrder.indexOf(b);
|
||||
if (indexA === -1 && indexB === -1) return a.localeCompare(b);
|
||||
if (indexA === -1) return 1;
|
||||
if (indexB === -1) return -1;
|
||||
return indexA - indexB;
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="space-y-8">
|
||||
{#each sortedCategories as category}
|
||||
<div class="rounded-lg border overflow-hidden
|
||||
border-bg-dark-tertiary [.light_&]:border-bg-light-tertiary">
|
||||
<div class="px-4 py-3 bg-bg-dark-secondary [.light_&]:bg-bg-light-secondary">
|
||||
<h4 class="font-semibold text-text-dark [.light_&]:text-text-light">
|
||||
{category}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-bg-dark-tertiary/50 [.light_&]:bg-bg-light-tertiary/50">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left font-medium text-text-dark-muted [.light_&]:text-text-light-muted">
|
||||
{$t.materials.columns.article}
|
||||
</th>
|
||||
<th class="px-4 py-2 text-left font-medium text-text-dark-muted [.light_&]:text-text-light-muted">
|
||||
{$t.materials.columns.quantity}
|
||||
</th>
|
||||
<th class="px-4 py-2 text-left font-medium text-text-dark-muted [.light_&]:text-text-light-muted">
|
||||
{$t.materials.columns.specification}
|
||||
</th>
|
||||
<th class="px-4 py-2 text-right font-medium text-text-dark-muted [.light_&]:text-text-light-muted">
|
||||
{$t.materials.columns.link}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-bg-dark-tertiary [.light_&]:divide-bg-light-tertiary">
|
||||
{#each groupedMaterials[category] as item}
|
||||
<tr class="hover:bg-bg-dark-secondary/30 [.light_&]:hover:bg-bg-light-secondary/30 transition-colors">
|
||||
<td class="px-4 py-3 text-text-dark [.light_&]:text-text-light">
|
||||
{item.article}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-text-dark [.light_&]:text-text-light">
|
||||
{item.quantity}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-text-dark-muted [.light_&]:text-text-light-muted">
|
||||
{item.specification}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
{#if item.link}
|
||||
<a
|
||||
href={item.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors
|
||||
bg-accent/10 text-accent hover:bg-accent/20"
|
||||
>
|
||||
<ExternalLink class="w-3 h-3" />
|
||||
{$t.materials.buyNow}
|
||||
</a>
|
||||
{:else}
|
||||
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium
|
||||
bg-primary-light/50 text-text-dark-muted [.light_&]:bg-bg-light-tertiary [.light_&]:text-text-light-muted">
|
||||
<Printer class="w-3 h-3" />
|
||||
{$t.materials.printedPart}
|
||||
</span>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
74
src/lib/components/Navigation.svelte
Normal file
74
src/lib/components/Navigation.svelte
Normal file
@@ -0,0 +1,74 @@
|
||||
<script lang="ts">
|
||||
import { t } from '$lib/stores/language';
|
||||
import { Box, List, Gauge, Wrench, Layers, Zap, Battery, Lightbulb, Fan, Cpu, LayoutGrid, Code, CheckSquare, Puzzle } from 'lucide-svelte';
|
||||
|
||||
interface NavItem {
|
||||
id: string;
|
||||
labelKey: keyof typeof $t.nav;
|
||||
icon: typeof Box;
|
||||
}
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{ id: 'overview', labelKey: 'overview', icon: Box },
|
||||
{ id: 'materials', labelKey: 'materials', icon: List },
|
||||
{ id: 'standards', labelKey: 'standards', icon: Gauge },
|
||||
{ id: 'mechanical', labelKey: 'mechanical', icon: Wrench },
|
||||
{ id: 'zones', labelKey: 'zones', icon: Layers },
|
||||
{ id: 'ac-wiring', labelKey: 'acWiring', icon: Zap },
|
||||
{ id: 'dc-distribution', labelKey: 'dcDistribution', icon: Battery },
|
||||
{ id: 'leds', labelKey: 'leds', icon: Lightbulb },
|
||||
{ id: 'fans', labelKey: 'fans', icon: Fan },
|
||||
{ id: 'esp32', labelKey: 'esp32', icon: Cpu },
|
||||
{ id: 'panel', labelKey: 'panel', icon: LayoutGrid },
|
||||
{ id: 'software', labelKey: 'software', icon: Code },
|
||||
{ id: 'checklist', labelKey: 'checklist', icon: CheckSquare },
|
||||
{ id: 'extensions', labelKey: 'extensions', icon: Puzzle }
|
||||
];
|
||||
|
||||
let activeSection = $state('overview');
|
||||
|
||||
function handleScroll() {
|
||||
const sections = navItems.map(item => document.getElementById(item.id));
|
||||
const scrollPos = window.scrollY + 100;
|
||||
|
||||
for (let i = sections.length - 1; i >= 0; i--) {
|
||||
const section = sections[i];
|
||||
if (section && section.offsetTop <= scrollPos) {
|
||||
activeSection = navItems[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<nav class="hidden lg:block fixed left-0 top-16 bottom-0 w-64 overflow-y-auto border-r
|
||||
bg-bg-dark border-bg-dark-tertiary
|
||||
[.light_&]:bg-bg-light [.light_&]:border-bg-light-tertiary">
|
||||
<div class="py-6 px-4">
|
||||
<ul class="space-y-1">
|
||||
{#each navItems as item}
|
||||
{@const Icon = item.icon}
|
||||
<li>
|
||||
<a
|
||||
href="#{item.id}"
|
||||
class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors
|
||||
{activeSection === item.id
|
||||
? 'bg-accent text-white'
|
||||
: 'text-text-dark-muted hover:text-text-dark hover:bg-bg-dark-secondary [.light_&]:text-text-light-muted [.light_&]:hover:text-text-light [.light_&]:hover:bg-bg-light-secondary'
|
||||
}"
|
||||
>
|
||||
<Icon class="w-4 h-4 flex-shrink-0" />
|
||||
<span>{$t.nav[item.labelKey]}</span>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
27
src/lib/components/Section.svelte
Normal file
27
src/lib/components/Section.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
children: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let { id, title, subtitle, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<section {id} class="scroll-mt-20 py-12 border-b
|
||||
border-bg-dark-tertiary [.light_&]:border-bg-light-tertiary">
|
||||
<div class="mb-8">
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-text-dark [.light_&]:text-text-light">
|
||||
{title}
|
||||
</h2>
|
||||
{#if subtitle}
|
||||
<p class="mt-2 text-text-dark-muted [.light_&]:text-text-light-muted">
|
||||
{subtitle}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
{@render children()}
|
||||
</div>
|
||||
</section>
|
||||
30
src/lib/components/StepList.svelte
Normal file
30
src/lib/components/StepList.svelte
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
title?: string;
|
||||
steps: string[];
|
||||
}
|
||||
|
||||
let { title, steps }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="rounded-lg border p-4
|
||||
bg-bg-dark-secondary/50 border-bg-dark-tertiary
|
||||
[.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
{#if title}
|
||||
<h4 class="font-semibold mb-4 text-text-dark [.light_&]:text-text-light">
|
||||
{title}
|
||||
</h4>
|
||||
{/if}
|
||||
<ol class="space-y-3">
|
||||
{#each steps as step, index}
|
||||
<li class="flex gap-3">
|
||||
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-accent text-white text-sm font-medium flex items-center justify-center">
|
||||
{index + 1}
|
||||
</span>
|
||||
<span class="text-text-dark [.light_&]:text-text-light pt-0.5">
|
||||
{step}
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
</div>
|
||||
63
src/lib/components/Table.svelte
Normal file
63
src/lib/components/Table.svelte
Normal file
@@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
interface Column {
|
||||
key: string;
|
||||
header: string;
|
||||
wireColor?: 'l' | 'n' | 'pe' | '12v' | '5v' | 'gnd';
|
||||
}
|
||||
|
||||
interface Props {
|
||||
columns: Column[];
|
||||
rows: Record<string, string>[];
|
||||
caption?: string;
|
||||
}
|
||||
|
||||
let { columns, rows, caption }: Props = $props();
|
||||
|
||||
function getWireColorClass(color?: string): string {
|
||||
switch (color) {
|
||||
case 'l': return 'bg-wire-l';
|
||||
case 'n': return 'bg-wire-n';
|
||||
case 'pe': return 'bg-wire-pe';
|
||||
case '12v': return 'bg-wire-12v';
|
||||
case '5v': return 'bg-wire-5v';
|
||||
case 'gnd': return 'bg-wire-gnd';
|
||||
default: return '';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="overflow-x-auto rounded-lg border
|
||||
border-bg-dark-tertiary [.light_&]:border-bg-light-tertiary">
|
||||
<table class="w-full text-sm">
|
||||
{#if caption}
|
||||
<caption class="px-4 py-2 text-left text-text-dark-muted [.light_&]:text-text-light-muted bg-bg-dark-secondary [.light_&]:bg-bg-light-secondary">
|
||||
{caption}
|
||||
</caption>
|
||||
{/if}
|
||||
<thead class="bg-bg-dark-secondary [.light_&]:bg-bg-light-secondary">
|
||||
<tr>
|
||||
{#each columns as column}
|
||||
<th class="px-4 py-3 text-left font-semibold text-text-dark [.light_&]:text-text-light">
|
||||
{column.header}
|
||||
</th>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-bg-dark-tertiary [.light_&]:divide-bg-light-tertiary">
|
||||
{#each rows as row}
|
||||
<tr class="hover:bg-bg-dark-secondary/50 [.light_&]:hover:bg-bg-light-secondary/50 transition-colors">
|
||||
{#each columns as column}
|
||||
<td class="px-4 py-3 text-text-dark [.light_&]:text-text-light">
|
||||
<span class="flex items-center gap-2">
|
||||
{#if column.wireColor}
|
||||
<span class="w-3 h-3 rounded-full {getWireColorClass(column.wireColor)}"></span>
|
||||
{/if}
|
||||
{row[column.key]}
|
||||
</span>
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
54
src/lib/components/Warning.svelte
Normal file
54
src/lib/components/Warning.svelte
Normal file
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import { AlertTriangle, Info, Lightbulb, AlertCircle } from 'lucide-svelte';
|
||||
|
||||
interface Props {
|
||||
type?: 'warning' | 'danger' | 'info' | 'tip';
|
||||
title?: string;
|
||||
children: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let { type = 'warning', title, children }: Props = $props();
|
||||
|
||||
const config = {
|
||||
warning: {
|
||||
icon: AlertTriangle,
|
||||
bgClass: 'bg-warning/10 border-warning/30',
|
||||
iconClass: 'text-warning',
|
||||
titleClass: 'text-warning'
|
||||
},
|
||||
danger: {
|
||||
icon: AlertCircle,
|
||||
bgClass: 'bg-danger/10 border-danger/30',
|
||||
iconClass: 'text-danger',
|
||||
titleClass: 'text-danger'
|
||||
},
|
||||
info: {
|
||||
icon: Info,
|
||||
bgClass: 'bg-wire-blue/10 border-wire-blue/30',
|
||||
iconClass: 'text-wire-blue',
|
||||
titleClass: 'text-wire-blue'
|
||||
},
|
||||
tip: {
|
||||
icon: Lightbulb,
|
||||
bgClass: 'bg-success/10 border-success/30',
|
||||
iconClass: 'text-success',
|
||||
titleClass: 'text-success'
|
||||
}
|
||||
};
|
||||
|
||||
const currentConfig = $derived(config[type]);
|
||||
</script>
|
||||
|
||||
<div class="rounded-lg border p-4 {currentConfig.bgClass}">
|
||||
<div class="flex gap-3">
|
||||
<currentConfig.icon class="w-5 h-5 flex-shrink-0 mt-0.5 {currentConfig.iconClass}" />
|
||||
<div class="flex-1">
|
||||
{#if title}
|
||||
<h4 class="font-semibold mb-1 {currentConfig.titleClass}">{title}</h4>
|
||||
{/if}
|
||||
<div class="text-sm text-text-dark [.light_&]:text-text-light">
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
238
src/lib/data/materials.ts
Normal file
238
src/lib/data/materials.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
export interface Material {
|
||||
category: string;
|
||||
article: string;
|
||||
quantity: string;
|
||||
specification: string;
|
||||
link: string | null;
|
||||
}
|
||||
|
||||
export const materials: Material[] = [
|
||||
// Möbel
|
||||
{
|
||||
category: 'Möbel',
|
||||
article: 'IKEA Tisch',
|
||||
quantity: '1',
|
||||
specification: '55x55cm Grundfläche; 50cm Höhe',
|
||||
link: 'https://www.amazon.de/s?k=ikea+tisch+55x55'
|
||||
},
|
||||
|
||||
// Holz
|
||||
{
|
||||
category: 'Holz',
|
||||
article: 'OSB Platten',
|
||||
quantity: '3',
|
||||
specification: '9–12mm für Seiten & Rückwand',
|
||||
link: 'https://www.amazon.de/s?k=osb+platte+10mm'
|
||||
},
|
||||
|
||||
// Tür
|
||||
{
|
||||
category: 'Tür',
|
||||
article: 'Plexiglas Acryl',
|
||||
quantity: '1',
|
||||
specification: '4–6mm Fronttür',
|
||||
link: 'https://www.amazon.de/s?k=plexiglas+4mm'
|
||||
},
|
||||
|
||||
// Druckteile
|
||||
{
|
||||
category: 'Druckteile',
|
||||
article: 'Corner Brackets',
|
||||
quantity: '12',
|
||||
specification: 'PETG',
|
||||
link: null
|
||||
},
|
||||
{
|
||||
category: 'Druckteile',
|
||||
article: 'Türscharniere',
|
||||
quantity: '2',
|
||||
specification: 'PETG',
|
||||
link: null
|
||||
},
|
||||
{
|
||||
category: 'Druckteile',
|
||||
article: 'Türgriff',
|
||||
quantity: '1',
|
||||
specification: 'PETG',
|
||||
link: null
|
||||
},
|
||||
{
|
||||
category: 'Druckteile',
|
||||
article: 'Türanschläge',
|
||||
quantity: '2–4',
|
||||
specification: 'PETG',
|
||||
link: null
|
||||
},
|
||||
{
|
||||
category: 'Druckteile',
|
||||
article: 'WAGO Halter 221',
|
||||
quantity: '4–6',
|
||||
specification: 'PETG',
|
||||
link: null
|
||||
},
|
||||
{
|
||||
category: 'Druckteile',
|
||||
article: 'Kabelklemme',
|
||||
quantity: '1',
|
||||
specification: 'Zugentlastung, PETG',
|
||||
link: null
|
||||
},
|
||||
|
||||
// Strom
|
||||
{
|
||||
category: 'Strom',
|
||||
article: 'IEC C14 Entry mit Sicherung',
|
||||
quantity: '1',
|
||||
specification: '5x20mm Sicherung',
|
||||
link: 'https://www.amazon.de/s?k=iec+c14+einbaubuchse+sicherung'
|
||||
},
|
||||
{
|
||||
category: 'Strom',
|
||||
article: 'Feinsicherung T5A',
|
||||
quantity: '1',
|
||||
specification: '250V träge',
|
||||
link: 'https://www.amazon.de/s?k=5x20mm+t5a'
|
||||
},
|
||||
{
|
||||
category: 'Strom',
|
||||
article: 'Dual Output PSU',
|
||||
quantity: '1',
|
||||
specification: '230V → 12V + 5V',
|
||||
link: 'https://www.amazon.de/s?k=12v+5v+dual+power+supply'
|
||||
},
|
||||
|
||||
// Verdrahtung
|
||||
{
|
||||
category: 'Verdrahtung',
|
||||
article: 'WAGO 221 Klemmen',
|
||||
quantity: '1 Set',
|
||||
specification: '3- & 5-polig',
|
||||
link: 'https://www.amazon.de/s?k=wago+221'
|
||||
},
|
||||
{
|
||||
category: 'Verdrahtung',
|
||||
article: 'AC Kabel',
|
||||
quantity: '2m',
|
||||
specification: 'H05VV-F 3x1.0mm²',
|
||||
link: 'https://www.amazon.de/s?k=h05vv-f+3x1+0'
|
||||
},
|
||||
{
|
||||
category: 'Verdrahtung',
|
||||
article: 'DC Kabel 12V',
|
||||
quantity: '5m',
|
||||
specification: '2x0.75mm² rot/schwarz',
|
||||
link: 'https://www.amazon.de/s?k=silikonkabel+2x0.75'
|
||||
},
|
||||
{
|
||||
category: 'Verdrahtung',
|
||||
article: 'DC Kabel 5V',
|
||||
quantity: '3m',
|
||||
specification: '2x0.5mm² orange/schwarz',
|
||||
link: 'https://www.amazon.de/s?k=silikonkabel+orange'
|
||||
},
|
||||
{
|
||||
category: 'Verdrahtung',
|
||||
article: 'Aderendhülsen',
|
||||
quantity: '1 Set',
|
||||
specification: '0.25–1.5mm²',
|
||||
link: 'https://www.amazon.de/s?k=aderendhülsen'
|
||||
},
|
||||
{
|
||||
category: 'Verdrahtung',
|
||||
article: 'Crimpzange',
|
||||
quantity: '1',
|
||||
specification: 'für Aderendhülsen',
|
||||
link: 'https://www.amazon.de/s?k=crimpzange'
|
||||
},
|
||||
{
|
||||
category: 'Verdrahtung',
|
||||
article: 'Schrumpfschlauch',
|
||||
quantity: '1 Set',
|
||||
specification: '3:1 mit Kleber',
|
||||
link: 'https://www.amazon.de/s?k=schrumpfschlauch'
|
||||
},
|
||||
{
|
||||
category: 'Verdrahtung',
|
||||
article: 'Kabelverschraubung',
|
||||
quantity: '1',
|
||||
specification: 'M20 / PG13.5',
|
||||
link: 'https://www.amazon.de/s?k=kabelverschraubung+m20'
|
||||
},
|
||||
|
||||
// Controller
|
||||
{
|
||||
category: 'Controller',
|
||||
article: 'ESP32 DevKit',
|
||||
quantity: '1',
|
||||
specification: 'WROOM-32',
|
||||
link: 'https://www.amazon.de/s?k=esp32+devkit'
|
||||
},
|
||||
|
||||
// Sensorik
|
||||
{
|
||||
category: 'Sensorik',
|
||||
article: 'BME280',
|
||||
quantity: '1',
|
||||
specification: 'I2C Temp/Feuchte',
|
||||
link: 'https://www.amazon.de/s?k=bme280'
|
||||
},
|
||||
|
||||
// Elektronik
|
||||
{
|
||||
category: 'Elektronik',
|
||||
article: 'MOSFET PWM Modul',
|
||||
quantity: '5',
|
||||
specification: 'Logic-Level 12V',
|
||||
link: 'https://www.amazon.de/s?k=mosfet+pwm+12v'
|
||||
},
|
||||
|
||||
// Lüfter
|
||||
{
|
||||
category: 'Lüfter',
|
||||
article: '120mm PWM Lüfter',
|
||||
quantity: '4',
|
||||
specification: '1 Zuluft, 3 Abluft',
|
||||
link: 'https://www.amazon.de/s?k=120mm+pwm+l%C3%BCfter'
|
||||
},
|
||||
|
||||
// Filter
|
||||
{
|
||||
category: 'Filter',
|
||||
article: 'Aktivkohlefilter 120mm',
|
||||
quantity: '1',
|
||||
specification: 'Abluft',
|
||||
link: 'https://www.amazon.de/s?k=120mm+aktivkohlefilter'
|
||||
},
|
||||
|
||||
// Beleuchtung
|
||||
{
|
||||
category: 'Beleuchtung',
|
||||
article: 'LED Strip 12V',
|
||||
quantity: '1',
|
||||
specification: '10mm warmweiß',
|
||||
link: 'https://www.amazon.de/s?k=12v+led+strip+10mm'
|
||||
},
|
||||
{
|
||||
category: 'Beleuchtung',
|
||||
article: 'LED Alu Profil Diffusor',
|
||||
quantity: '6',
|
||||
specification: '10mm',
|
||||
link: 'https://www.amazon.de/s?k=led+profil+10mm'
|
||||
},
|
||||
|
||||
// Werkzeug
|
||||
{
|
||||
category: 'Werkzeug',
|
||||
article: 'Lötkolben',
|
||||
quantity: '1',
|
||||
specification: 'regelbar',
|
||||
link: 'https://www.amazon.de/s?k=lötkolben'
|
||||
},
|
||||
{
|
||||
category: 'Werkzeug',
|
||||
article: 'Multimeter',
|
||||
quantity: '1',
|
||||
specification: 'Spannungsprüfung',
|
||||
link: 'https://www.amazon.de/s?k=multimeter'
|
||||
}
|
||||
];
|
||||
335
src/lib/i18n/de.ts
Normal file
335
src/lib/i18n/de.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
export const de = {
|
||||
// Header & Navigation
|
||||
nav: {
|
||||
overview: 'Überblick',
|
||||
materials: 'Materialien',
|
||||
standards: 'Standards',
|
||||
mechanical: 'Mechanik',
|
||||
zones: 'Zonen',
|
||||
acWiring: 'AC-Verdrahtung',
|
||||
dcDistribution: 'DC-Verteilung',
|
||||
leds: 'LEDs',
|
||||
fans: 'Lüfter',
|
||||
esp32: 'ESP32',
|
||||
panel: 'Panel',
|
||||
software: 'Software',
|
||||
config: 'Konfiguration',
|
||||
checklist: 'Checkliste',
|
||||
extensions: 'Erweiterungen'
|
||||
},
|
||||
|
||||
// Hero Section
|
||||
hero: {
|
||||
title: 'DruckWerk',
|
||||
subtitle: 'Professionelles 3D-Drucker Gehäuse',
|
||||
tagline: 'ESP32 gesteuert | Home Assistant Integration | Dual-Zone Design',
|
||||
description: 'Ein IKEA-Tisch wird zu einem geschlossenen, belüfteten Druckergehäuse mit fester 230V-Einspeisung, interner AC-Verteilung über WAGO, und ESP32-Steuerung.'
|
||||
},
|
||||
|
||||
// Overview Section
|
||||
overview: {
|
||||
title: 'Gesamtüberblick',
|
||||
subtitle: 'Was entsteht',
|
||||
features: [
|
||||
'Feste 230V-Einspeisung über IEC C14',
|
||||
'Interne AC-Verteilung über WAGO',
|
||||
'Fest angeschlossener Drucker (kein Stecker innen)',
|
||||
'Dual-Output-PSU (12V + 5V)',
|
||||
'ESP32-Steuerung (PWM für LEDs & Lüfter)',
|
||||
'Home-Assistant-Integration',
|
||||
'Sauber getrennte AC- und DC-Zonen',
|
||||
'Vollständig servicefähiger Aufbau'
|
||||
],
|
||||
conclusion: {
|
||||
title: 'Fazit',
|
||||
points: [
|
||||
'Keine Bastellösung',
|
||||
'Kein Overengineering',
|
||||
'Maschinenbau-sauber'
|
||||
],
|
||||
benefits: 'So kannst du später umbauen, reparieren und erweitern - ohne alles neu zu machen.'
|
||||
}
|
||||
},
|
||||
|
||||
// Materials Section
|
||||
materials: {
|
||||
title: 'Materialliste',
|
||||
subtitle: 'Alle benötigten Komponenten',
|
||||
categories: {
|
||||
furniture: 'Möbel',
|
||||
wood: 'Holz',
|
||||
door: 'Tür',
|
||||
printed: '3D-Druckteile',
|
||||
power: 'Strom',
|
||||
wiring: 'Verdrahtung',
|
||||
controller: 'Controller',
|
||||
sensors: 'Sensorik',
|
||||
electronics: 'Elektronik',
|
||||
fans: 'Lüfter',
|
||||
filter: 'Filter',
|
||||
lighting: 'Beleuchtung',
|
||||
tools: 'Werkzeug'
|
||||
},
|
||||
columns: {
|
||||
article: 'Artikel',
|
||||
quantity: 'Menge',
|
||||
specification: 'Spezifikation',
|
||||
link: 'Link'
|
||||
},
|
||||
printedPart: '3D-Druck',
|
||||
buyNow: 'Kaufen'
|
||||
},
|
||||
|
||||
// Standards Section
|
||||
standards: {
|
||||
title: 'Farb- & Spannungsstandard',
|
||||
subtitle: 'Verbindlich für alle Verbindungen',
|
||||
voltage: 'Spannung',
|
||||
color: 'Farbe',
|
||||
acSection: 'AC (Wechselstrom)',
|
||||
dcSection: 'DC (Gleichstrom)',
|
||||
logicSection: 'Logik'
|
||||
},
|
||||
|
||||
// Mechanical Build Section
|
||||
mechanical: {
|
||||
title: 'Mechanischer Aufbau',
|
||||
tableAndEnclosure: {
|
||||
title: 'Tisch & Gehäuse',
|
||||
steps: [
|
||||
'IKEA-Tisch aufstellen',
|
||||
'OSB-Platten zuschneiden: links, rechts, hinten',
|
||||
'OSB mit gedruckten Corner-Brackets montieren (Material: PETG, Vorbohren 2,5-3mm, Holzschrauben)',
|
||||
'Obere Tischplatte bleibt frei nutzbar'
|
||||
]
|
||||
},
|
||||
door: {
|
||||
title: 'Tür',
|
||||
steps: [
|
||||
'Plexiglas zuschneiden',
|
||||
'Gedruckte Scharniere montieren',
|
||||
'Türgriff + Anschläge montieren',
|
||||
'Optional: Dichtung (Moosgummi) für leiseres & dichteres Gehäuse'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Zones Section
|
||||
zones: {
|
||||
title: 'Zonenkonzept',
|
||||
subtitle: 'Wichtig: Strikte Trennung',
|
||||
acZone: {
|
||||
title: 'AC-Zone (230V)',
|
||||
components: [
|
||||
'IEC C14',
|
||||
'WAGO-Klemmen L / N / PE',
|
||||
'Dual-Output-PSU',
|
||||
'Druckerkabel (fest)'
|
||||
],
|
||||
note: 'Abgedeckt mit 3D-gedrucktem Cover'
|
||||
},
|
||||
dcZone: {
|
||||
title: 'DC-Zone (12V / 5V)',
|
||||
components: [
|
||||
'WAGO-Busse',
|
||||
'MOSFET-Module',
|
||||
'ESP32',
|
||||
'Sensoren',
|
||||
'LEDs / Lüfter'
|
||||
],
|
||||
note: 'Keine 230V hier'
|
||||
}
|
||||
},
|
||||
|
||||
// AC Wiring Section
|
||||
acWiring: {
|
||||
title: 'AC-Teil (230V)',
|
||||
warning: 'Nur spannungsfrei arbeiten!',
|
||||
iecEntry: {
|
||||
title: 'IEC C14 einbauen',
|
||||
steps: [
|
||||
'Ausschnitt nach Datenblatt sägen',
|
||||
'IEC C14 einsetzen und verschrauben',
|
||||
'Sicherung einsetzen (T5A, 5×20 mm)'
|
||||
]
|
||||
},
|
||||
wagoHolder: {
|
||||
title: 'WAGO-Halter (AC)',
|
||||
steps: [
|
||||
'Gedruckte WAGO-Halter (221-413) montieren',
|
||||
'Drei Klemmen: L, N, PE',
|
||||
'Halter auf Elektronik-Backplate schrauben',
|
||||
'Abdeckung vorbereiten (wird später montiert)'
|
||||
]
|
||||
},
|
||||
iecToWago: {
|
||||
title: 'IEC C14 → WAGO',
|
||||
note: 'Aderendhülsen verwenden, keine losen Litzen'
|
||||
},
|
||||
printer: {
|
||||
title: 'Drucker anschließen (fest)',
|
||||
steps: [
|
||||
'Druckerkabel durch Kabelverschraubung (M20/PG13.5) führen',
|
||||
'Innen Zugentlastung setzen',
|
||||
'Schuko-Stecker abschneiden',
|
||||
'Adern abisolieren + Aderendhülsen',
|
||||
'Anschluss an WAGO (Braun→L, Blau→N, Gelb-Grün→PE)'
|
||||
],
|
||||
note: 'Drucker ist jetzt Teil der festen Installation'
|
||||
},
|
||||
psu: {
|
||||
title: 'Dual-Output-PSU anschließen (AC-Seite)',
|
||||
note: 'Von denselben WAGOs: L, N, PE (falls vorhanden). PSU fest verschrauben.'
|
||||
}
|
||||
},
|
||||
|
||||
// DC Distribution Section
|
||||
dcDistribution: {
|
||||
title: 'DC-Teil – Stromverteilung',
|
||||
wagoBuses: {
|
||||
title: 'WAGO-Busse (DC)',
|
||||
description: 'Gedruckte Halter für:',
|
||||
buses: ['12V+', '5V+', 'GND (gemeinsam)'],
|
||||
recommendation: 'Empfohlen: WAGO 221-415 (5-polig)'
|
||||
},
|
||||
psuToWago: {
|
||||
title: 'PSU → DC-WAGOs',
|
||||
note: '12V- und 5V-GND dürfen gemeinsam sein'
|
||||
}
|
||||
},
|
||||
|
||||
// LEDs Section
|
||||
leds: {
|
||||
title: 'LEDs (PWM, 12V)',
|
||||
mechanics: {
|
||||
title: 'Mechanik',
|
||||
description: '6 LED-Segmente in Alu-Profilen mit Diffusor. Je Bein: vorne 1 Segment, hinten 2 Segmente.'
|
||||
},
|
||||
electrical: {
|
||||
title: 'Elektrik',
|
||||
description: 'LED+ direkt an 12V+, LED- über MOSFET Drain.',
|
||||
mosfet: 'MOSFET: Source → GND, Gate → ESP32 PWM-Pin',
|
||||
pwm: 'PWM-Frequenz: 1–5 kHz'
|
||||
}
|
||||
},
|
||||
|
||||
// Fans Section
|
||||
fans: {
|
||||
title: 'Lüfter & Aktivkohle (PWM)',
|
||||
layout: {
|
||||
title: 'Layout',
|
||||
items: ['1× Zuluft', '2× Abluft', '1× Aktivkohle']
|
||||
},
|
||||
electrical: {
|
||||
title: 'Elektrik (pro Lüfter)',
|
||||
description: 'Lüfter+ direkt 12V+, Lüfter- über MOSFET Drain.',
|
||||
mosfet: 'MOSFET: Source → GND, Gate → ESP32 PWM-Pin',
|
||||
pwm: 'PWM-Frequenz: 20–25 kHz (leise)'
|
||||
}
|
||||
},
|
||||
|
||||
// ESP32 Section
|
||||
esp32: {
|
||||
title: 'ESP32 & Sensorik',
|
||||
power: {
|
||||
title: 'Versorgung',
|
||||
description: 'ESP32 VIN/5V → 5V+ (orange), GND → GND'
|
||||
},
|
||||
voltage: {
|
||||
title: '3,3V',
|
||||
description: 'Kommt onboard vom ESP32. Kein Buck nötig, nur kurze Leitungen.'
|
||||
},
|
||||
bme280: {
|
||||
title: 'BME280 Sensor',
|
||||
description: 'I2C Temperatur & Feuchte'
|
||||
}
|
||||
},
|
||||
|
||||
// Panel Section
|
||||
panel: {
|
||||
title: 'Taster / Panel (optional)',
|
||||
description: 'Taster → GPIO mit Pull-Up',
|
||||
functions: ['Licht AN/AUS', 'Lüfter Boost', 'Reset']
|
||||
},
|
||||
|
||||
// Software Section
|
||||
software: {
|
||||
title: 'Software (Überblick)',
|
||||
esphome: {
|
||||
title: 'ESPHome',
|
||||
features: [
|
||||
'PWM Outputs: LEDs, Zuluft, Abluft 1, Abluft 2, Aktivkohle',
|
||||
'BME280 Sensor',
|
||||
'Fail-Safe: bei Verbindungsverlust → Lüfter auf 50%'
|
||||
]
|
||||
},
|
||||
homeAssistant: {
|
||||
title: 'Home Assistant',
|
||||
features: [
|
||||
'Bambu-Integration (Status)',
|
||||
'Automationen: PLA/ABS Profil, Nachlauf Aktivkohle, Übertemperatur'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Config Section
|
||||
config: {
|
||||
title: 'Konfiguration',
|
||||
esphome: {
|
||||
title: 'ESPHome Konfiguration',
|
||||
filename: 'esphome.yaml',
|
||||
placeholder: 'Konfiguration wird hinzugefügt...'
|
||||
},
|
||||
homeAssistant: {
|
||||
title: 'Home Assistant Automationen',
|
||||
filename: 'automations.yaml',
|
||||
placeholder: 'Konfiguration wird hinzugefügt...'
|
||||
},
|
||||
pending: 'Konfiguration ausstehend'
|
||||
},
|
||||
|
||||
// Checklist Section
|
||||
checklist: {
|
||||
title: 'Abschluss-Checkliste',
|
||||
subtitle: 'Pflicht vor Inbetriebnahme',
|
||||
electrical: {
|
||||
title: 'Elektrisch',
|
||||
items: [
|
||||
'PE durchgängig',
|
||||
'AC & DC getrennt',
|
||||
'AC-WAGOs abgedeckt',
|
||||
'Zugentlastung gesetzt',
|
||||
'Keine offenen 230V-Teile'
|
||||
]
|
||||
},
|
||||
functional: {
|
||||
title: 'Funktional',
|
||||
items: [
|
||||
'LEDs dimmen sauber',
|
||||
'Lüfter PWM leise',
|
||||
'Temp/Feuchte sichtbar',
|
||||
'HASS erreichbar'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Extensions Section
|
||||
extensions: {
|
||||
title: 'Nächste sinnvolle Erweiterungen',
|
||||
subtitle: 'Optional',
|
||||
items: [
|
||||
'Rauch-/VOC-Sensor',
|
||||
'Türkontakt',
|
||||
'Geräuschprofil-Automationen',
|
||||
'Druck-abhängige Lüftersteuerung'
|
||||
]
|
||||
},
|
||||
|
||||
// Common
|
||||
common: {
|
||||
warning: 'Warnung',
|
||||
note: 'Hinweis',
|
||||
tip: 'Tipp',
|
||||
important: 'Wichtig'
|
||||
}
|
||||
};
|
||||
335
src/lib/i18n/en.ts
Normal file
335
src/lib/i18n/en.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
export const en = {
|
||||
// Header & Navigation
|
||||
nav: {
|
||||
overview: 'Overview',
|
||||
materials: 'Materials',
|
||||
standards: 'Standards',
|
||||
mechanical: 'Mechanical',
|
||||
zones: 'Zones',
|
||||
acWiring: 'AC Wiring',
|
||||
dcDistribution: 'DC Distribution',
|
||||
leds: 'LEDs',
|
||||
fans: 'Fans',
|
||||
esp32: 'ESP32',
|
||||
panel: 'Panel',
|
||||
software: 'Software',
|
||||
config: 'Configuration',
|
||||
checklist: 'Checklist',
|
||||
extensions: 'Extensions'
|
||||
},
|
||||
|
||||
// Hero Section
|
||||
hero: {
|
||||
title: 'DruckWerk',
|
||||
subtitle: 'Professional 3D Printer Enclosure',
|
||||
tagline: 'ESP32 Controlled | Home Assistant Integration | Dual-Zone Design',
|
||||
description: 'An IKEA table transformed into a sealed, ventilated printer enclosure with fixed 230V power input, internal AC distribution via WAGO, and ESP32 control.'
|
||||
},
|
||||
|
||||
// Overview Section
|
||||
overview: {
|
||||
title: 'Overview',
|
||||
subtitle: 'What We Are Building',
|
||||
features: [
|
||||
'Fixed 230V power input via IEC C14',
|
||||
'Internal AC distribution via WAGO',
|
||||
'Permanently connected printer (no internal plug)',
|
||||
'Dual-output PSU (12V + 5V)',
|
||||
'ESP32 control (PWM for LEDs & fans)',
|
||||
'Home Assistant integration',
|
||||
'Cleanly separated AC and DC zones',
|
||||
'Fully serviceable design'
|
||||
],
|
||||
conclusion: {
|
||||
title: 'Conclusion',
|
||||
points: [
|
||||
'Not a DIY hack',
|
||||
'Not over-engineered',
|
||||
'Machine-building clean'
|
||||
],
|
||||
benefits: 'This allows you to modify, repair, and extend later - without redoing everything.'
|
||||
}
|
||||
},
|
||||
|
||||
// Materials Section
|
||||
materials: {
|
||||
title: 'Materials List',
|
||||
subtitle: 'All Required Components',
|
||||
categories: {
|
||||
furniture: 'Furniture',
|
||||
wood: 'Wood',
|
||||
door: 'Door',
|
||||
printed: '3D Printed Parts',
|
||||
power: 'Power',
|
||||
wiring: 'Wiring',
|
||||
controller: 'Controller',
|
||||
sensors: 'Sensors',
|
||||
electronics: 'Electronics',
|
||||
fans: 'Fans',
|
||||
filter: 'Filter',
|
||||
lighting: 'Lighting',
|
||||
tools: 'Tools'
|
||||
},
|
||||
columns: {
|
||||
article: 'Article',
|
||||
quantity: 'Qty',
|
||||
specification: 'Specification',
|
||||
link: 'Link'
|
||||
},
|
||||
printedPart: '3D Print',
|
||||
buyNow: 'Buy'
|
||||
},
|
||||
|
||||
// Standards Section
|
||||
standards: {
|
||||
title: 'Color & Voltage Standard',
|
||||
subtitle: 'Mandatory for All Connections',
|
||||
voltage: 'Voltage',
|
||||
color: 'Color',
|
||||
acSection: 'AC (Alternating Current)',
|
||||
dcSection: 'DC (Direct Current)',
|
||||
logicSection: 'Logic'
|
||||
},
|
||||
|
||||
// Mechanical Build Section
|
||||
mechanical: {
|
||||
title: 'Mechanical Build',
|
||||
tableAndEnclosure: {
|
||||
title: 'Table & Enclosure',
|
||||
steps: [
|
||||
'Set up IKEA table',
|
||||
'Cut OSB panels: left, right, back',
|
||||
'Mount OSB with printed corner brackets (Material: PETG, pre-drill 2.5-3mm, wood screws)',
|
||||
'Top table surface remains usable'
|
||||
]
|
||||
},
|
||||
door: {
|
||||
title: 'Door',
|
||||
steps: [
|
||||
'Cut plexiglass',
|
||||
'Mount printed hinges',
|
||||
'Mount door handle + stops',
|
||||
'Optional: Seal (foam rubber) for quieter & tighter enclosure'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Zones Section
|
||||
zones: {
|
||||
title: 'Zone Concept',
|
||||
subtitle: 'Important: Strict Separation',
|
||||
acZone: {
|
||||
title: 'AC Zone (230V)',
|
||||
components: [
|
||||
'IEC C14',
|
||||
'WAGO terminals L / N / PE',
|
||||
'Dual-output PSU',
|
||||
'Printer cable (fixed)'
|
||||
],
|
||||
note: 'Covered with 3D-printed cover'
|
||||
},
|
||||
dcZone: {
|
||||
title: 'DC Zone (12V / 5V)',
|
||||
components: [
|
||||
'WAGO buses',
|
||||
'MOSFET modules',
|
||||
'ESP32',
|
||||
'Sensors',
|
||||
'LEDs / Fans'
|
||||
],
|
||||
note: 'No 230V here'
|
||||
}
|
||||
},
|
||||
|
||||
// AC Wiring Section
|
||||
acWiring: {
|
||||
title: 'AC Section (230V)',
|
||||
warning: 'Only work when power is off!',
|
||||
iecEntry: {
|
||||
title: 'Install IEC C14',
|
||||
steps: [
|
||||
'Cut opening per datasheet',
|
||||
'Insert and screw in IEC C14',
|
||||
'Insert fuse (T5A, 5×20 mm)'
|
||||
]
|
||||
},
|
||||
wagoHolder: {
|
||||
title: 'WAGO Holder (AC)',
|
||||
steps: [
|
||||
'Mount printed WAGO holders (221-413)',
|
||||
'Three terminals: L, N, PE',
|
||||
'Screw holder to electronics backplate',
|
||||
'Prepare cover (mounted later)'
|
||||
]
|
||||
},
|
||||
iecToWago: {
|
||||
title: 'IEC C14 → WAGO',
|
||||
note: 'Use ferrules, no loose strands'
|
||||
},
|
||||
printer: {
|
||||
title: 'Connect Printer (permanent)',
|
||||
steps: [
|
||||
'Feed printer cable through cable gland (M20/PG13.5)',
|
||||
'Add strain relief inside',
|
||||
'Cut off Schuko plug',
|
||||
'Strip wires + add ferrules',
|
||||
'Connect to WAGO (Brown→L, Blue→N, Yellow-Green→PE)'
|
||||
],
|
||||
note: 'Printer is now part of the fixed installation'
|
||||
},
|
||||
psu: {
|
||||
title: 'Connect Dual-Output PSU (AC side)',
|
||||
note: 'From same WAGOs: L, N, PE (if available). Screw PSU down firmly.'
|
||||
}
|
||||
},
|
||||
|
||||
// DC Distribution Section
|
||||
dcDistribution: {
|
||||
title: 'DC Section – Power Distribution',
|
||||
wagoBuses: {
|
||||
title: 'WAGO Buses (DC)',
|
||||
description: 'Printed holders for:',
|
||||
buses: ['12V+', '5V+', 'GND (shared)'],
|
||||
recommendation: 'Recommended: WAGO 221-415 (5-pole)'
|
||||
},
|
||||
psuToWago: {
|
||||
title: 'PSU → DC WAGOs',
|
||||
note: '12V and 5V GND can be shared'
|
||||
}
|
||||
},
|
||||
|
||||
// LEDs Section
|
||||
leds: {
|
||||
title: 'LEDs (PWM, 12V)',
|
||||
mechanics: {
|
||||
title: 'Mechanics',
|
||||
description: '6 LED segments in aluminum profiles with diffuser. Per leg: front 1 segment, back 2 segments.'
|
||||
},
|
||||
electrical: {
|
||||
title: 'Electrical',
|
||||
description: 'LED+ direct to 12V+, LED- via MOSFET drain.',
|
||||
mosfet: 'MOSFET: Source → GND, Gate → ESP32 PWM pin',
|
||||
pwm: 'PWM frequency: 1–5 kHz'
|
||||
}
|
||||
},
|
||||
|
||||
// Fans Section
|
||||
fans: {
|
||||
title: 'Fans & Carbon Filter (PWM)',
|
||||
layout: {
|
||||
title: 'Layout',
|
||||
items: ['1× Intake', '2× Exhaust', '1× Carbon Filter']
|
||||
},
|
||||
electrical: {
|
||||
title: 'Electrical (per fan)',
|
||||
description: 'Fan+ direct 12V+, Fan- via MOSFET drain.',
|
||||
mosfet: 'MOSFET: Source → GND, Gate → ESP32 PWM pin',
|
||||
pwm: 'PWM frequency: 20–25 kHz (quiet)'
|
||||
}
|
||||
},
|
||||
|
||||
// ESP32 Section
|
||||
esp32: {
|
||||
title: 'ESP32 & Sensors',
|
||||
power: {
|
||||
title: 'Power Supply',
|
||||
description: 'ESP32 VIN/5V → 5V+ (orange), GND → GND'
|
||||
},
|
||||
voltage: {
|
||||
title: '3.3V',
|
||||
description: 'Comes from ESP32 onboard. No buck needed, keep wires short.'
|
||||
},
|
||||
bme280: {
|
||||
title: 'BME280 Sensor',
|
||||
description: 'I2C Temperature & Humidity'
|
||||
}
|
||||
},
|
||||
|
||||
// Panel Section
|
||||
panel: {
|
||||
title: 'Buttons / Panel (optional)',
|
||||
description: 'Button → GPIO with pull-up',
|
||||
functions: ['Light ON/OFF', 'Fan Boost', 'Reset']
|
||||
},
|
||||
|
||||
// Software Section
|
||||
software: {
|
||||
title: 'Software (Overview)',
|
||||
esphome: {
|
||||
title: 'ESPHome',
|
||||
features: [
|
||||
'PWM Outputs: LEDs, Intake, Exhaust 1, Exhaust 2, Carbon Filter',
|
||||
'BME280 Sensor',
|
||||
'Fail-safe: on connection loss → fans at 50%'
|
||||
]
|
||||
},
|
||||
homeAssistant: {
|
||||
title: 'Home Assistant',
|
||||
features: [
|
||||
'Bambu integration (status)',
|
||||
'Automations: PLA/ABS profile, carbon filter follow-up, overtemperature'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Config Section
|
||||
config: {
|
||||
title: 'Configuration',
|
||||
esphome: {
|
||||
title: 'ESPHome Configuration',
|
||||
filename: 'esphome.yaml',
|
||||
placeholder: 'Configuration coming soon...'
|
||||
},
|
||||
homeAssistant: {
|
||||
title: 'Home Assistant Automations',
|
||||
filename: 'automations.yaml',
|
||||
placeholder: 'Configuration coming soon...'
|
||||
},
|
||||
pending: 'Configuration pending'
|
||||
},
|
||||
|
||||
// Checklist Section
|
||||
checklist: {
|
||||
title: 'Final Checklist',
|
||||
subtitle: 'Mandatory Before Operation',
|
||||
electrical: {
|
||||
title: 'Electrical',
|
||||
items: [
|
||||
'PE continuous',
|
||||
'AC & DC separated',
|
||||
'AC WAGOs covered',
|
||||
'Strain relief set',
|
||||
'No exposed 230V parts'
|
||||
]
|
||||
},
|
||||
functional: {
|
||||
title: 'Functional',
|
||||
items: [
|
||||
'LEDs dim smoothly',
|
||||
'Fan PWM quiet',
|
||||
'Temp/humidity visible',
|
||||
'HASS reachable'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Extensions Section
|
||||
extensions: {
|
||||
title: 'Recommended Future Extensions',
|
||||
subtitle: 'Optional',
|
||||
items: [
|
||||
'Smoke/VOC sensor',
|
||||
'Door contact',
|
||||
'Noise profile automations',
|
||||
'Print-dependent fan control'
|
||||
]
|
||||
},
|
||||
|
||||
// Common
|
||||
common: {
|
||||
warning: 'Warning',
|
||||
note: 'Note',
|
||||
tip: 'Tip',
|
||||
important: 'Important'
|
||||
}
|
||||
};
|
||||
3
src/lib/i18n/index.ts
Normal file
3
src/lib/i18n/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { de } from './de';
|
||||
export { en } from './en';
|
||||
export type { Language } from '$lib/stores/language';
|
||||
50
src/lib/stores/language.ts
Normal file
50
src/lib/stores/language.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { writable, derived } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
import { de } from '$lib/i18n/de';
|
||||
import { en } from '$lib/i18n/en';
|
||||
|
||||
export type Language = 'de' | 'en';
|
||||
|
||||
const translations = { de, en };
|
||||
|
||||
function createLanguageStore() {
|
||||
const storedLang = browser ? localStorage.getItem('language') as Language : null;
|
||||
const browserLang = browser ? navigator.language.split('-')[0] : 'de';
|
||||
const initialLang: Language = storedLang || (browserLang === 'en' ? 'en' : 'de');
|
||||
|
||||
const { subscribe, set } = writable<Language>(initialLang);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set: (value: Language) => {
|
||||
if (browser) {
|
||||
localStorage.setItem('language', value);
|
||||
document.documentElement.lang = value;
|
||||
}
|
||||
set(value);
|
||||
},
|
||||
toggle: () => {
|
||||
let current: Language = 'de';
|
||||
subscribe(v => current = v)();
|
||||
const newLang = current === 'de' ? 'en' : 'de';
|
||||
if (browser) {
|
||||
localStorage.setItem('language', newLang);
|
||||
document.documentElement.lang = newLang;
|
||||
}
|
||||
set(newLang);
|
||||
},
|
||||
init: () => {
|
||||
if (browser) {
|
||||
const stored = localStorage.getItem('language') as Language;
|
||||
const browserLang = navigator.language.split('-')[0];
|
||||
const lang = stored || (browserLang === 'en' ? 'en' : 'de');
|
||||
document.documentElement.lang = lang;
|
||||
set(lang);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const language = createLanguageStore();
|
||||
|
||||
export const t = derived(language, ($language) => translations[$language]);
|
||||
54
src/lib/stores/theme.ts
Normal file
54
src/lib/stores/theme.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
type Theme = 'dark' | 'light';
|
||||
|
||||
function createThemeStore() {
|
||||
const storedTheme = browser ? localStorage.getItem('theme') as Theme : null;
|
||||
const prefersDark = browser ? window.matchMedia('(prefers-color-scheme: dark)').matches : true;
|
||||
const initialTheme: Theme = storedTheme || (prefersDark ? 'dark' : 'light');
|
||||
|
||||
const { subscribe, set, update } = writable<Theme>(initialTheme);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set: (value: Theme) => {
|
||||
if (browser) {
|
||||
localStorage.setItem('theme', value);
|
||||
if (value === 'light') {
|
||||
document.documentElement.classList.add('light');
|
||||
} else {
|
||||
document.documentElement.classList.remove('light');
|
||||
}
|
||||
}
|
||||
set(value);
|
||||
},
|
||||
toggle: () => {
|
||||
update(current => {
|
||||
const newTheme = current === 'dark' ? 'light' : 'dark';
|
||||
if (browser) {
|
||||
localStorage.setItem('theme', newTheme);
|
||||
if (newTheme === 'light') {
|
||||
document.documentElement.classList.add('light');
|
||||
} else {
|
||||
document.documentElement.classList.remove('light');
|
||||
}
|
||||
}
|
||||
return newTheme;
|
||||
});
|
||||
},
|
||||
init: () => {
|
||||
if (browser) {
|
||||
const stored = localStorage.getItem('theme') as Theme;
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const theme = stored || (prefersDark ? 'dark' : 'light');
|
||||
if (theme === 'light') {
|
||||
document.documentElement.classList.add('light');
|
||||
}
|
||||
set(theme);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const theme = createThemeStore();
|
||||
28
src/routes/+layout.svelte
Normal file
28
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { onMount } from 'svelte';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { language } from '$lib/stores/language';
|
||||
import Header from '$lib/components/Header.svelte';
|
||||
import Navigation from '$lib/components/Navigation.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
onMount(() => {
|
||||
theme.init();
|
||||
language.init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>DruckWerk - 3D Printer Enclosure</title>
|
||||
</svelte:head>
|
||||
|
||||
<Header />
|
||||
<Navigation />
|
||||
|
||||
<main class="lg:ml-64 pt-16">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
347
src/routes/+page.svelte
Normal file
347
src/routes/+page.svelte
Normal file
@@ -0,0 +1,347 @@
|
||||
<script lang="ts">
|
||||
import { t } from '$lib/stores/language';
|
||||
import Hero from '$lib/components/Hero.svelte';
|
||||
import Section from '$lib/components/Section.svelte';
|
||||
import Table from '$lib/components/Table.svelte';
|
||||
import Warning from '$lib/components/Warning.svelte';
|
||||
import StepList from '$lib/components/StepList.svelte';
|
||||
import MaterialsList from '$lib/components/MaterialsList.svelte';
|
||||
import ConfigPlaceholder from '$lib/components/ConfigPlaceholder.svelte';
|
||||
import Checklist from '$lib/components/Checklist.svelte';
|
||||
import { materials } from '$lib/data/materials';
|
||||
import { Check, ArrowRight } from 'lucide-svelte';
|
||||
</script>
|
||||
|
||||
<Hero />
|
||||
|
||||
<!-- Overview Section -->
|
||||
<Section id="overview" title={$t.overview.title} subtitle={$t.overview.subtitle}>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="rounded-lg border p-6 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-4 text-text-dark [.light_&]:text-text-light">Features</h4>
|
||||
<ul class="space-y-2">
|
||||
{#each $t.overview.features as feature}
|
||||
<li class="flex items-start gap-2 text-sm text-text-dark [.light_&]:text-text-light">
|
||||
<Check class="w-4 h-4 text-success flex-shrink-0 mt-0.5" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="rounded-lg border p-6 bg-accent/5 border-accent/20">
|
||||
<h4 class="font-semibold mb-4 text-accent">{$t.overview.conclusion.title}</h4>
|
||||
<ul class="space-y-2 mb-4">
|
||||
{#each $t.overview.conclusion.points as point}
|
||||
<li class="flex items-center gap-2 text-sm text-text-dark [.light_&]:text-text-light">
|
||||
<ArrowRight class="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<span>{point}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<p class="text-sm text-text-dark-muted [.light_&]:text-text-light-muted">
|
||||
{$t.overview.conclusion.benefits}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- Materials Section -->
|
||||
<Section id="materials" title={$t.materials.title} subtitle={$t.materials.subtitle}>
|
||||
<MaterialsList {materials} />
|
||||
</Section>
|
||||
|
||||
<!-- Standards Section -->
|
||||
<Section id="standards" title={$t.standards.title} subtitle={$t.standards.subtitle}>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.standards.acSection}</h4>
|
||||
<Table
|
||||
columns={[
|
||||
{ key: 'voltage', header: $t.standards.voltage },
|
||||
{ key: 'color', header: $t.standards.color }
|
||||
]}
|
||||
rows={[
|
||||
{ voltage: 'L', color: 'Braun / Brown' },
|
||||
{ voltage: 'N', color: 'Blau / Blue' },
|
||||
{ voltage: 'PE', color: 'Gelb-Grün / Yellow-Green' }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.standards.dcSection}</h4>
|
||||
<Table
|
||||
columns={[
|
||||
{ key: 'voltage', header: $t.standards.voltage },
|
||||
{ key: 'color', header: $t.standards.color }
|
||||
]}
|
||||
rows={[
|
||||
{ voltage: '12V', color: 'Rot / Red + Schwarz / Black' },
|
||||
{ voltage: '5V', color: 'Orange + Schwarz / Black' },
|
||||
{ voltage: '3.3V', color: 'ESP32 onboard' }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- Mechanical Build Section -->
|
||||
<Section id="mechanical" title={$t.mechanical.title}>
|
||||
<div class="space-y-6">
|
||||
<StepList title={$t.mechanical.tableAndEnclosure.title} steps={$t.mechanical.tableAndEnclosure.steps} />
|
||||
<StepList title={$t.mechanical.door.title} steps={$t.mechanical.door.steps} />
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- Zones Section -->
|
||||
<Section id="zones" title={$t.zones.title} subtitle={$t.zones.subtitle}>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="rounded-lg border p-6 bg-danger/5 border-danger/20">
|
||||
<h4 class="font-semibold mb-4 text-danger">{$t.zones.acZone.title}</h4>
|
||||
<ul class="space-y-2 mb-4">
|
||||
{#each $t.zones.acZone.components as component}
|
||||
<li class="text-sm text-text-dark [.light_&]:text-text-light">• {component}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<p class="text-xs text-text-dark-muted [.light_&]:text-text-light-muted italic">
|
||||
→ {$t.zones.acZone.note}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border p-6 bg-success/5 border-success/20">
|
||||
<h4 class="font-semibold mb-4 text-success">{$t.zones.dcZone.title}</h4>
|
||||
<ul class="space-y-2 mb-4">
|
||||
{#each $t.zones.dcZone.components as component}
|
||||
<li class="text-sm text-text-dark [.light_&]:text-text-light">• {component}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<p class="text-xs text-text-dark-muted [.light_&]:text-text-light-muted italic">
|
||||
→ {$t.zones.dcZone.note}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- AC Wiring Section -->
|
||||
<Section id="ac-wiring" title={$t.acWiring.title}>
|
||||
<Warning type="danger" title={$t.common.warning}>
|
||||
<p>{$t.acWiring.warning}</p>
|
||||
</Warning>
|
||||
|
||||
<div class="space-y-6 mt-6">
|
||||
<StepList title={$t.acWiring.iecEntry.title} steps={$t.acWiring.iecEntry.steps} />
|
||||
<StepList title={$t.acWiring.wagoHolder.title} steps={$t.acWiring.wagoHolder.steps} />
|
||||
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.acWiring.iecToWago.title}</h4>
|
||||
<Table
|
||||
columns={[
|
||||
{ key: 'from', header: 'IEC C14' },
|
||||
{ key: 'to', header: 'WAGO' }
|
||||
]}
|
||||
rows={[
|
||||
{ from: 'L (Braun)', to: 'WAGO L' },
|
||||
{ from: 'N (Blau)', to: 'WAGO N' },
|
||||
{ from: 'PE (Gelb-Grün)', to: 'WAGO PE' }
|
||||
]}
|
||||
/>
|
||||
<Warning type="info">
|
||||
<p>{$t.acWiring.iecToWago.note}</p>
|
||||
</Warning>
|
||||
</div>
|
||||
|
||||
<StepList title={$t.acWiring.printer.title} steps={$t.acWiring.printer.steps} />
|
||||
|
||||
<Warning type="tip">
|
||||
<p>{$t.acWiring.printer.note}</p>
|
||||
</Warning>
|
||||
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.acWiring.psu.title}</h4>
|
||||
<p class="text-sm text-text-dark-muted [.light_&]:text-text-light-muted">{$t.acWiring.psu.note}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- DC Distribution Section -->
|
||||
<Section id="dc-distribution" title={$t.dcDistribution.title}>
|
||||
<div class="space-y-6">
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.dcDistribution.wagoBuses.title}</h4>
|
||||
<p class="text-sm mb-3 text-text-dark-muted [.light_&]:text-text-light-muted">{$t.dcDistribution.wagoBuses.description}</p>
|
||||
<ul class="space-y-1 mb-3">
|
||||
{#each $t.dcDistribution.wagoBuses.buses as bus}
|
||||
<li class="text-sm text-text-dark [.light_&]:text-text-light">• {bus}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<p class="text-xs text-accent">{$t.dcDistribution.wagoBuses.recommendation}</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.dcDistribution.psuToWago.title}</h4>
|
||||
<Table
|
||||
columns={[
|
||||
{ key: 'psu', header: 'PSU' },
|
||||
{ key: 'wago', header: 'WAGO' }
|
||||
]}
|
||||
rows={[
|
||||
{ psu: '+12V', wago: '12V+ (rot)' },
|
||||
{ psu: '0V', wago: 'GND (schwarz)' },
|
||||
{ psu: '+5V', wago: '5V+ (orange)' }
|
||||
]}
|
||||
/>
|
||||
<Warning type="info">
|
||||
<p>{$t.dcDistribution.psuToWago.note}</p>
|
||||
</Warning>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- LEDs Section -->
|
||||
<Section id="leds" title={$t.leds.title}>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.leds.mechanics.title}</h4>
|
||||
<p class="text-sm text-text-dark-muted [.light_&]:text-text-light-muted">{$t.leds.mechanics.description}</p>
|
||||
</div>
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.leds.electrical.title}</h4>
|
||||
<p class="text-sm mb-2 text-text-dark-muted [.light_&]:text-text-light-muted">{$t.leds.electrical.description}</p>
|
||||
<p class="text-sm mb-1 text-text-dark [.light_&]:text-text-light">{$t.leds.electrical.mosfet}</p>
|
||||
<p class="text-xs text-accent">{$t.leds.electrical.pwm}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- Fans Section -->
|
||||
<Section id="fans" title={$t.fans.title}>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.fans.layout.title}</h4>
|
||||
<ul class="space-y-1">
|
||||
{#each $t.fans.layout.items as item}
|
||||
<li class="text-sm text-text-dark [.light_&]:text-text-light">• {item}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.fans.electrical.title}</h4>
|
||||
<p class="text-sm mb-2 text-text-dark-muted [.light_&]:text-text-light-muted">{$t.fans.electrical.description}</p>
|
||||
<p class="text-sm mb-1 text-text-dark [.light_&]:text-text-light">{$t.fans.electrical.mosfet}</p>
|
||||
<p class="text-xs text-accent">{$t.fans.electrical.pwm}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- ESP32 Section -->
|
||||
<Section id="esp32" title={$t.esp32.title}>
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.esp32.power.title}</h4>
|
||||
<p class="text-sm text-text-dark-muted [.light_&]:text-text-light-muted">{$t.esp32.power.description}</p>
|
||||
</div>
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.esp32.voltage.title}</h4>
|
||||
<p class="text-sm text-text-dark-muted [.light_&]:text-text-light-muted">{$t.esp32.voltage.description}</p>
|
||||
</div>
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.esp32.bme280.title}</h4>
|
||||
<p class="text-sm text-text-dark-muted [.light_&]:text-text-light-muted">{$t.esp32.bme280.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<Table
|
||||
caption="BME280 Pinout"
|
||||
columns={[
|
||||
{ key: 'bme', header: 'BME280' },
|
||||
{ key: 'esp', header: 'ESP32' }
|
||||
]}
|
||||
rows={[
|
||||
{ bme: 'VCC', esp: '3V3' },
|
||||
{ bme: 'GND', esp: 'GND' },
|
||||
{ bme: 'SDA', esp: 'GPIO 21' },
|
||||
{ bme: 'SCL', esp: 'GPIO 22' }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- Panel Section -->
|
||||
<Section id="panel" title={$t.panel.title}>
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<p class="text-sm mb-3 text-text-dark-muted [.light_&]:text-text-light-muted">{$t.panel.description}</p>
|
||||
<h4 class="font-semibold mb-2 text-text-dark [.light_&]:text-text-light">Functions:</h4>
|
||||
<ul class="space-y-1">
|
||||
{#each $t.panel.functions as func}
|
||||
<li class="text-sm text-text-dark [.light_&]:text-text-light">• {func}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- Software Section -->
|
||||
<Section id="software" title={$t.software.title}>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.software.esphome.title}</h4>
|
||||
<ul class="space-y-1">
|
||||
{#each $t.software.esphome.features as feature}
|
||||
<li class="text-sm text-text-dark [.light_&]:text-text-light">• {feature}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="rounded-lg border p-4 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<h4 class="font-semibold mb-3 text-text-dark [.light_&]:text-text-light">{$t.software.homeAssistant.title}</h4>
|
||||
<ul class="space-y-1">
|
||||
{#each $t.software.homeAssistant.features as feature}
|
||||
<li class="text-sm text-text-dark [.light_&]:text-text-light">• {feature}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Config Placeholders -->
|
||||
<div class="mt-8 space-y-6">
|
||||
<h3 class="text-xl font-semibold text-text-dark [.light_&]:text-text-light">{$t.config.title}</h3>
|
||||
<ConfigPlaceholder
|
||||
filename={$t.config.esphome.filename}
|
||||
title={$t.config.esphome.title}
|
||||
placeholder={$t.config.esphome.placeholder}
|
||||
/>
|
||||
<ConfigPlaceholder
|
||||
filename={$t.config.homeAssistant.filename}
|
||||
title={$t.config.homeAssistant.title}
|
||||
placeholder={$t.config.homeAssistant.placeholder}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- Checklist Section -->
|
||||
<Section id="checklist" title={$t.checklist.title} subtitle={$t.checklist.subtitle}>
|
||||
<Checklist
|
||||
sections={[
|
||||
{ title: $t.checklist.electrical.title, items: $t.checklist.electrical.items },
|
||||
{ title: $t.checklist.functional.title, items: $t.checklist.functional.items }
|
||||
]}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<!-- Extensions Section -->
|
||||
<Section id="extensions" title={$t.extensions.title} subtitle={$t.extensions.subtitle}>
|
||||
<div class="rounded-lg border p-6 bg-bg-dark-secondary/50 border-bg-dark-tertiary [.light_&]:bg-bg-light-secondary/50 [.light_&]:border-bg-light-tertiary">
|
||||
<ul class="grid md:grid-cols-2 gap-3">
|
||||
{#each $t.extensions.items as item}
|
||||
<li class="flex items-center gap-2 text-text-dark [.light_&]:text-text-light">
|
||||
<span class="w-2 h-2 rounded-full bg-accent"></span>
|
||||
{item}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="py-12 text-center border-t border-bg-dark-tertiary [.light_&]:border-bg-light-tertiary">
|
||||
<p class="text-sm text-text-dark-muted [.light_&]:text-text-light-muted">
|
||||
DruckWerk - Built with Svelte & Tailwind CSS
|
||||
</p>
|
||||
</footer>
|
||||
8
static/favicon.svg
Normal file
8
static/favicon.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="6" fill="#f97316"/>
|
||||
<rect x="4" y="7" width="24" height="18" rx="1.5" fill="none" stroke="#fff" stroke-width="2"/>
|
||||
<line x1="4" y1="12" x2="28" y2="12" stroke="#fff" stroke-width="1.5"/>
|
||||
<circle cx="8" cy="9.5" r="1" fill="#fff"/>
|
||||
<circle cx="11" cy="9.5" r="1" fill="#fff"/>
|
||||
<rect x="10" y="16" width="12" height="6" rx="1" fill="none" stroke="#fff" stroke-width="1.2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 485 B |
18
svelte.config.js
Normal file
18
svelte.config.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
preprocess: vitePreprocess(),
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
pages: 'build',
|
||||
assets: 'build',
|
||||
fallback: 'index.html',
|
||||
precompress: false,
|
||||
strict: true
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
}
|
||||
7
vite.config.ts
Normal file
7
vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()]
|
||||
});
|
||||
Reference in New Issue
Block a user