diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml index 433c4a3..d11ecbd 100644 --- a/.github/workflows/build-deploy.yml +++ b/.github/workflows/build-deploy.yml @@ -3,8 +3,14 @@ name: Build & Deploy moonweb-site on: push: branches: [main] + paths-ignore: + - 'stefankoelle/**' + - '.github/workflows/deploy-stefankoelle.yml' pull_request: branches: [main] + paths-ignore: + - 'stefankoelle/**' + - '.github/workflows/deploy-stefankoelle.yml' # Cancel in-progress runs for the same branch concurrency: diff --git a/.github/workflows/deploy-stefankoelle.yml b/.github/workflows/deploy-stefankoelle.yml new file mode 100644 index 0000000..a966b8c --- /dev/null +++ b/.github/workflows/deploy-stefankoelle.yml @@ -0,0 +1,107 @@ +name: Build & Deploy stefankoelle.de + +on: + push: + branches: [main] + paths: + - 'stefankoelle/**' + - 'shared/**' + - '.github/workflows/deploy-stefankoelle.yml' + pull_request: + branches: [main] + paths: + - 'stefankoelle/**' + - 'shared/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + NODE_VERSION: "22" + +jobs: + build: + name: Build stefankoelle.de + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: ${{ env.NODE_VERSION }} + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Build stefankoelle + run: npm run build:stefankoelle + + - name: Validate build output + run: | + if [ ! -d "dist/stefankoelle" ]; then + echo "Error: dist/stefankoelle not found" + exit 1 + fi + if [ -z "$(ls -A dist/stefankoelle 2>/dev/null)" ]; then + echo "Error: dist/stefankoelle is empty" + exit 1 + fi + echo "Build output:" + find dist/stefankoelle -type f | head -20 + + - name: Upload build artifact + uses: actions/upload-artifact@v7 + with: + name: dist-stefankoelle + path: dist/stefankoelle + retention-days: 7 + + deploy: + name: Deploy stefankoelle.de + needs: build + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + steps: + - name: Download build artifact + uses: actions/download-artifact@v8 + with: + name: dist-stefankoelle + path: dist/stefankoelle + + - name: Deploy via SFTP to IONOS + uses: wlixcc/sftp-deploy-action@v1 + with: + local_path: './dist/stefankoelle/*' + server: ${{ secrets.IONOS_SFTP_HOST }} + username: ${{ secrets.IONOS_SFTP_USER }} + password: ${{ secrets.IONOS_SFTP_PASSWORD }} + server_path: '/deploy/stefankoelle/' + port: 22 + + summary: + name: Deploy Summary + needs: deploy + runs-on: ubuntu-latest + if: always() + steps: + - name: Print deployment summary + run: | + echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ needs.deploy.result }}" = "success" ]; then + echo "✅ stefankoelle.de deployed successfully!" >> $GITHUB_STEP_SUMMARY + else + echo "❌ Deployment failed. Check the deploy job for details." >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Site" >> $GITHUB_STEP_SUMMARY + echo "| Site | URL |" >> $GITHUB_STEP_SUMMARY + echo "|------|-----|" >> $GITHUB_STEP_SUMMARY + echo "| stefankoelle | https://stefankoelle.de |" >> $GITHUB_STEP_SUMMARY + +# Required repo secrets: +# IONOS_SFTP_HOST — IONOS SFTP server hostname +# IONOS_SFTP_USER — IONOS SFTP username +# IONOS_SFTP_PASSWORD — IONOS SFTP password diff --git a/AGENTS.md b/AGENTS.md index 370d47c..8878f56 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,9 +2,9 @@ ## Projektübersicht -Monorepo für 5 statische Websites unter moonweb.org, basierend auf Eleventy (11ty), deployt auf Cloudflare Pages. +Monorepo für 5 statische Websites unter moonweb.org + stefankoelle.de, basierend auf Eleventy (11ty). -### Websites +### Websites (Cloudflare Pages) | Site | Domain | Zweck | Status | |------|--------|-------|--------| @@ -14,82 +14,76 @@ Monorepo für 5 statische Websites unter moonweb.org, basierend auf Eleventy (11 | code | code.moonweb.org | GitHub-Projekte (aggregiert via .moonweb.yml) | Fertig (10 Repos) | | retro | retro.moonweb.org | Physische Retro-Hardware | Nur Übersicht (WIP) | -### Externe Sites (nicht im Monorepo) +### Externe Sites (SFTP-Deployment) + +| Site | Domain | Zweck | Status | +|------|--------|-------|--------| +| stefankoelle | stefankoelle.de | Lebenslauf, Kontakt, Projekte | Onepager + LED-Matrix-Detailseite | + +### Andere Sites (nicht im Monorepo) -- stefankoelle.de — Lebenslauf, Kontakt (extern gehostet) - www.moonweb.org — 2000er Internet-Zeitkapsel - 28k8.moonweb.org — 90er BBS/Scene-Archiv - ## Dateistruktur ``` moonweb-site/ ├── hub/ # Eleventy-Config + index.njk ├── infra/ # Eleventy-Config + index.njk + 3 Subseiten -│ ├── backup-strategy/ -│ ├── monitoring/ -│ └── dev-workflow/ ├── smarthome/ # Eleventy-Config + index.njk + 6 Subseiten -│ ├── homematic-mqtt/ -│ ├── tasmota-energy/ -│ ├── balkonpi/ -│ ├── airplay-audio/ -│ ├── octoprint/ -│ └── tubearchivist/ ├── code/ # Eleventy-Config + index.njk -│ └── _data/repos.json # Vom GitHub-Aggregator generiert +│ └── _data/repos.json ├── retro/ # Eleventy-Config + index.njk +├── stefankoelle/ # Eleventy-Config + Onepager +│ ├── eleventy.config.js +│ ├── _includes/ +│ │ ├── stefankoelle.njk # Layout (Header, Nav, Footer) +│ │ └── cv-content.njk # CV-Partial (zentral fuer Web + PDF) +│ ├── index.njk # Onepager (alle Sections) +│ ├── cv-print.njk # CV-only fuer spaeteren PDF-Druck +│ ├── assets/ # CSS, JS, Bilder, Favicons +│ ├── ledmatrix/ # LED Matrix WebServer Dokumentation +│ └── pdf/ # PDF-CSS (fuer spaeteren WeasyPrint-Bau) ├── shared/ # Gemeinsame Komponenten │ ├── _includes/ │ │ ├── base.njk # Basis-Layout (Header, Site-Switcher, Footer) │ │ └── card-grid.njk # Card-Grid Template -│ ├── base.css # Basis-CSS (Layout, Cards, Typography) -│ └── theme-*.css # Accent-Farben pro Site +│ ├── base.css +│ └── theme-*.css ├── scripts/ -│ └── github-aggregator/ # Python-Skript für code.moonweb.org -│ ├── aggregate.py -│ ├── example.moonweb.yml -│ └── README.md +│ └── github-aggregator/ ├── .github/workflows/ -│ └── build-deploy.yml # CI/CD: Build + Deploy zu Cloudflare Pages -├── DESIGN.md # Initiales Konzept mit Design-Entscheidungen (deutsch) -├── SPEC.md # Vollständige Spezifikation (148 Zeilen) -├── PLAN.md # Implementierungsplan (65 Zeilen) -├── README.md # Projekt-README -├── TODO.md # Offene Items + Workflow -└── package.json # npm scripts für dev/build +│ ├── build-deploy.yml # CI/CD: Cloudflare Pages (5 Sites) +│ └── deploy-stefankoelle.yml # CI/CD: IONOS SFTP (stefankoelle.de) +├── DESIGN.md +├── SPEC.md +├── PLAN.md +├── README.md +├── TODO.md +└── package.json ``` -## Dokumentation - -- `DESIGN.md` — Initiales Konzept mit Design-Entscheidungen (deutsch) -- `SPEC.md` — Vollständige Spezifikation (148 Zeilen) -- `PLAN.md` — Implementierungsplan (65 Zeilen) -- `README.md` — Projekt-README -- `TODO.md` — Offene Items + Workflow für weitere Arbeit -- `TODO.md` — Offene Items + Workflow für weitere Arbeit - ## Technischer Stack - **SSG:** Eleventy (11ty) v3.1.6 - **Templates:** Nunjucks (.njk) - **CSS:** Variables-basiert mit Accent-Farben pro Site -- **Deploy:** Cloudflare Pages (5 separate Projects) -- **CI/CD:** GitHub Actions -- **GitHub-Aggregator:** Python (liest .moonweb.yml aus Repos) -- **Deploy:** Cloudflare Pages (5 separate Projects) +- **Deploy (moonweb):** Cloudflare Pages (5 separate Projects) +- **Deploy (stefankoelle):** IONOS SFTP +- **CI/CD:** GitHub Actions (2 Workflows) ## Entwicklung ```bash npm install -npm run dev:hub # localhost:8081 -npm run dev:infra # localhost:8082 -npm run dev:smarthome # localhost:8083 -npm run dev:code # localhost:8084 -npm run dev:retro # localhost:8085 -npm run build # Alle Sites bauen +npm run dev:hub # localhost:8081 +npm run dev:infra # localhost:8082 +npm run dev:smarthome # localhost:8083 +npm run dev:code # localhost:8084 +npm run dev:retro # localhost:8085 +npm run dev:stefankoelle # localhost:8086 +npm run build # Alle Sites bauen ``` ## Design-Prinzipien @@ -101,36 +95,35 @@ npm run build # Alle Sites bauen 5. **Keine Analytics** — Keine Tracking-Tools 6. **Sensible Daten** — Infra-Content wird manuell redigiert (keine IPs, Keys, Passwörter) -## Content-Regeln (SPEC.md §5) +## stefankoelle.de -| Site | Detailseiten? | Regel | -|------|---------------|-------| -| smarthome | Ja | Nur wenn genug Content vorhanden | -| code | Nie | Nur Übersicht + GitHub-Links | -| infra | Kaum | Bewusst oberflächlich (sensible Daten) | -| retro | Ja, minimal | Rudimentär, WIP erlaubt | - -## GitHub-Aggregator - -`scripts/github-aggregator/aggregate.py` liest `.moonweb.yml` aus allen public Repos von `skoelle` und schreibt `code/_data/repos.json`. Manuell ausführen: - -```bash -export GITHUB_TOKEN=ghp_xxx -python scripts/github-aggregator/aggregate.py -``` +- Eigene Eleventy-Config (nicht shared base.njk) +- Eigenes CSS-Design (nicht moonweb design system) +- Onepager mit Anchor-Links (bleibt so) +- Deploy via SFTP auf IONOS `/deploy/stefankoelle/` +- CV-Partial zentral pflegbar (einmal aendern → Web + PDF aktualisieren) +- LED Matrix als eigene Seite unter stefankoelle.de/ledmatrix/ ## CI/CD -`.github/workflows/build-deploy.yml` baut alle 5 Sites (hub, infra, smarthome, code, retro) und deployt sie zu Cloudflare Pages. +### Cloudflare Pages (moonweb) +`.github/workflows/build-deploy.yml` baut hub, infra, smarthome, code, retro. Benötigte Secrets: - `CLOUDFLARE_API_TOKEN` - `CLOUDFLARE_ACCOUNT_ID` +### IONOS SFTP (stefankoelle.de) +`.github/workflows/deploy-stefankoelle.yml` baut stefankoelle.de und deployed per SFTP. + +Benötigte Secrets: +- `IONOS_SFTP_HOST` +- `IONOS_SFTP_USER` +- `IONOS_SFTP_PASSWORD` ## Offene Punkte - retro/ ist bewusst rudimentär gehalten -- Einige Cards in smarthome/ und infra/ verlinken noch auf "#" (Placeholder) -- Deploy erfolgt noch nicht automatisch (Phase 3 ausstehend) -- stefankoelle.de ist extern gehostet, wird nur verlinkt (nicht im Monorepo) +- PDF-Build fuer CV (WeasyPrint) — lokal testen, spaeter in CI integrieren +- Querverlinkungen stefankoelle.de <-> smarthome (zukuenftig) +- Ledmatrix ggf. nach smarthome verschieben (when ready) diff --git a/TODO.md b/TODO.md index 029bf22..9825c17 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,6 @@ # TODO — moonweb-site Offene Items -Stand: 2026-08-13 +Stand: 2026-08-14 ## Aktueller Status @@ -10,20 +10,34 @@ Stand: 2026-08-13 | Phase 1 — Content | ✅ fertig | hub, code, infra, smarthome komplett, retro bewusst rudimentär | | Phase 2 — CI/CD | ✅ fertig | build-deploy.yml mit Caching + Validation + Summary | | Phase 3 — Launch | ✅ fertig | Cloudflare Pages live, Smoke-Test bestanden | -| Phase 4 — Deferred | ⏸️ zurückgestellt | Ledmatrix-Migration, 28k8, Analytics | +| Phase 4 — Deferred | ⏸️ zurückgestellt | 28k8, Analytics | +| Phase 5 — stefankoelle.de | ✅ fertig | Ins Monorepo migriert, Eleventy-Build, SFTP-Deployment | ## Offene Items ### P4 — Deferred (nicht dringend) -- [ ] Ledmatrix-Migration nach smarthome - [ ] 28k8.moonweb.org Zukunft klären - [ ] Perplexity-Rückkanal - [ ] Apex-Domain Redirect (moonweb.org → hub.moonweb.org) -- [ ] stefankoelle.de in Monorepo portieren +- [ ] PDF-Build fuer CV (WeasyPrint, lokal testen) +- [ ] Querverlinkungen stefankoelle.de <-> smarthome +- [ ] Ledmatrix ggf. nach smarthome verschieben -## Schon erledigt (Stand 2026-08-13) +## Schon erledigt (Stand 2026-08-14) +### stefankoelle.de Migration (2026-08-14) +- [x] Eleventy-Config + package.json erweitert +- [x] Onepager-Layout mit Anchor-Links beibehalten +- [x] CV-Partial (zentral fuer Web + PDF) +- [x] CSS ausgelagert + modernisiert (Custom Properties, box-sizing) +- [x] Assets in assets/ verschoben +- [x] Ledmatrix als eigene Seite integriert +- [x] Alte Dateien geloescht (index.html, browserconfig.xml, sitemap.txt) +- [x] Deployment-Workflow fuer IONOS SFTP +- [x] Cloudflare-Workflow um stefankoelle erweitert (paths-ignore) + +### Vorherige Erledigungen (Stand 2026-08-13) - [x] Impressum angelegt + Footer-Link auf allen Sites - [x] Favicons (SVG mit Emojis) pro Subdomain — `shared/favicon/*.svg`, per Passthrough Copy - [x] Meta descriptions auf allen 33 Seiten @@ -51,3 +65,7 @@ Alle 5 Sites live auf Cloudflare Pages: - ✅ smarthome.moonweb.org — Smart Home Übersicht + Detailseiten - ✅ infra.moonweb.org — Infra-Übersicht (redigiert) - ✅ retro.moonweb.org — Ehrliche minimale Übersicht (WIP) + +stefankoelle.de live auf IONOS (SFTP-Deployment): +- ✅ stefankoelle.de — Onepager mit CV, Projects, Languages, Impressum +- ✅ stefankoelle.de/ledmatrix/ — LED Matrix WebServer Dokumentation diff --git a/hub/impressum.njk b/hub/impressum.njk index 414ce4c..c930afc 100644 --- a/hub/impressum.njk +++ b/hub/impressum.njk @@ -19,17 +19,17 @@ layout: base.njk

- This website network (hub.moonweb.org, smarthome.moonweb.org and related subdomains) is a private, non-commercial hobby project documenting my home lab, retro-computing and personal coding projects. It is operated from Germany, is not run for profit, and does not offer any goods, services, or advertising. + Scope of this legal notice: This legal notice and the privacy policy below apply to the following websites, all operated by Stefan Kölle: hub.moonweb.org, smarthome.moonweb.org, infra.moonweb.org, code.moonweb.org, retro.moonweb.org, www.moonweb.org, and stefankoelle.de. All of these sites are part of the same personal project network and are covered jointly by this single document. +

+ +

+ The moonweb.org sites document my private home lab, retro-computing and personal coding projects and are non-commercial. stefankoelle.de presents my professional background (CV) as the same author.

Responsible for content according to § 18 (2) MStV: Stefan Kölle, address as above.

-

- A separate professional profile with its own legal notice and privacy policy is available at stefankoelle.de and is referenced from the "About Me" section only as background information about the author; it is operated and hosted independently of moonweb.org. -

-

Privacy Policy

1. Controller

@@ -39,36 +39,44 @@ layout: base.njk Email: info@inseco.de -

2. Purpose of this Website

+

2. Purpose of these Websites

- moonweb.org and its subdomains (hub, smarthome, infra, code, retro) document my private home-lab, home-automation and retro-computing hobby projects for my own reference and for anyone interested in similar topics. This is not a commercial or promotional site. The "About Me" section links out to stefankoelle.de, a separate site with its own legal notice and privacy policy, purely as background information about the author. + The moonweb.org subdomains (hub, smarthome, infra, code, retro) document my private home-lab, home-automation and retro-computing hobby projects. stefankoelle.de presents my professional CV and background as the same person. Both are part of one personal project network and share this legal notice and privacy policy.

-

3. Hosting — Cloudflare Pages

+

3. Hosting

- This website is hosted via Cloudflare Pages, operated by Cloudflare, Inc., 101 Townsend St, San Francisco, CA 94107, USA. To deliver and secure the site, Cloudflare processes connection-level data (e.g. your IP address, request headers) as part of its content delivery and DDoS/security systems. This processing is outside of my direct control and is governed by Cloudflare's own Data Processing Addendum, which incorporates the EU Standard Contractual Clauses (2021) as a safeguard for the transfer of data to the USA. -

-

- Legal basis: Art. 6(1)(f) GDPR (legitimate interest in reliable, secure delivery of the website). -

-

- Cloudflare's privacy policy: see cloudflare.com/privacypolicy. -

-

- I do not run any additional analytics, counter, or tracking mechanism of my own on top of Cloudflare's standard hosting. + Two different hosting providers are used within this network:

-

4. No Tracking, No Cookies, No Analytics

+

a) moonweb.org and subdomains — Cloudflare Pages

- This website does not use cookies, does not set any identifiers on your device, and does not run any analytics, counting pixel, or third-party tracking script. Beyond the technically necessary connection data processed by Cloudflare (see section 3), no personal data about your visit is collected, stored, or evaluated by me. + Hosted via Cloudflare Pages, operated by Cloudflare, Inc., 101 Townsend St, San Francisco, CA 94107, USA. Cloudflare processes connection-level data (e.g. your IP address, request headers) as part of its content delivery and DDoS/security systems. This is governed by Cloudflare's Data Processing Addendum, which incorporates the EU Standard Contractual Clauses (2021) as a safeguard for the transfer of data to the USA. See cloudflare.com/privacypolicy. +

+ +

b) stefankoelle.de — IONOS SE

+

+ Hosted on web space provided by IONOS SE, Elgendorfer Str. 57, 56410 Montabaur, Germany, acting as a processor under Art. 28 GDPR (Data Processing Agreement in place). +

+ +

+ Legal basis for both: Art. 6(1)(f) GDPR (legitimate interest in reliable, secure delivery of the websites). +

+ +

4. No Tracking, No Cookies, No Analytics (current state)

+

+ None of the websites covered by this document currently use cookies, set any identifiers on your device, or run any analytics, counting pixel, or third-party tracking script. Beyond the technically necessary connection data processed by the respective hosting provider (see section 3), no personal data about your visit is collected, stored, or evaluated by me.

No consent banner is shown, because nothing is stored on or read from your device — there is simply nothing that would require your consent under § 25 TDDDG (formerly TTDSG).

+

+ If this changes in the future (e.g. a cookie-free visit counter is added to stefankoelle.de), this document will be updated before it goes live, describing exactly what is collected, why, and for how long. +

5. Contact

- If you email me, your email address, name (if provided) and message content are processed solely to answer your inquiry, based on Art. 6(1)(f) GDPR. + If you email me, your email address, name (if provided) and message content are processed solely to answer your inquiry, based on Art. 6(1)(b) or (f) GDPR depending on the nature of your inquiry (e.g. a job opportunity or a hobby-related question).

6. Your Rights

@@ -89,7 +97,7 @@ layout: base.njk

7. Changes to this Policy

- I may update this policy if the setup of this website network changes, for example if a visit counter or analytics tool is added in the future — I will document any such change here before it goes live. + I may update this policy if the setup of any website in this network changes — for example, if a visit counter or analytics tool is added to stefankoelle.de or to any moonweb.org subdomain. Any such change will be documented here before it goes live.

Last updated: August 2026

diff --git a/package.json b/package.json index fe93e5e..b984688 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,15 @@ "dev:smarthome": "npx @11ty/eleventy --config=smarthome/eleventy.config.js --serve --port=8083", "dev:code": "npx @11ty/eleventy --config=code/eleventy.config.js --serve --port=8084", "dev:retro": "npx @11ty/eleventy --config=retro/eleventy.config.js --serve --port=8085", - "dev": "npm run dev:hub & npm run dev:infra & npm run dev:smarthome & npm run dev:code & npm run dev:retro", + "dev:stefankoelle": "npx @11ty/eleventy --config=stefankoelle/eleventy.config.js --serve --port=8086", + "dev": "npm run dev:hub & npm run dev:infra & npm run dev:smarthome & npm run dev:code & npm run dev:retro & npm run dev:stefankoelle", "build:hub": "npx @11ty/eleventy --config=hub/eleventy.config.js", "build:infra": "npx @11ty/eleventy --config=infra/eleventy.config.js", "build:smarthome": "npx @11ty/eleventy --config=smarthome/eleventy.config.js", "build:code": "npx @11ty/eleventy --config=code/eleventy.config.js", "build:retro": "npx @11ty/eleventy --config=retro/eleventy.config.js", - "build": "npm run build:hub && npm run build:infra && npm run build:smarthome && npm run build:code && npm run build:retro" + "build:stefankoelle": "npx @11ty/eleventy --config=stefankoelle/eleventy.config.js", + "build": "npm run build:hub && npm run build:infra && npm run build:smarthome && npm run build:code && npm run build:retro && npm run build:stefankoelle" }, "devDependencies": { "@11ty/eleventy": "^3.1.6" diff --git a/stefankoelle/_includes/cv-content.njk b/stefankoelle/_includes/cv-content.njk new file mode 100644 index 0000000..c72c241 --- /dev/null +++ b/stefankoelle/_includes/cv-content.njk @@ -0,0 +1,97 @@ +
+

CV

+ +
+

Stefan Kölle

+

Senior Software Developer & Software Architect
+ Neumarkter Str. 86c, 81673 München
+ E-Mail: [email protected]

+
+ +
+

Profile

+

Experienced Senior Software Developer and Software Architect with over 20 years of experience in developing and managing microservices and IT architectures. Specialized in .NET Core and Kubernetes, with hands-on experience in AI-assisted development using Claude Code. Clear focus on efficient, innovative, and sustainable solutions. Highly skilled in executing complex projects, building cloud-native services, and optimizing processes through modern technologies.

+
+ +
+

Core Technologies

+ +
+ +

Senior Software Developer / Senior Software Architect

+

TENHIL GmbH & Co. KG (2025 - present)

+ + +

TENHIL GmbH & Co. KG (08/2022 - 2024)

+ + +

Senior Software Developer / Senior Software Architect

+

stellenanzeigen.de GmbH & Co. KG (11/2018 - 07/2022)

+ + +

Managing Director

+

Regio-Jobanzeiger GmbH & Co. KG (10/2014 - 12/2017)

+ +

Managing Director, CTO

+

stellenanzeigen.de GmbH & Co. KG (01/2011 - 10/2018)

+ + +

CTO, Member of Executive Management

+

stellenanzeigen.de GmbH & Co. KG (01/2004 - 12/2010)

+ + +

Head of IT / Development

+

stellenanzeigen.de GmbH & Co. KG (06/2000 - 12/2003)

+ + +

Web Developer

+

IXOS AG (05/1999 - 05/2000)

+ + +

Education

+

FH Augsburg - Computer Science (1996 - 1999)

+ +

Internships

+

IXOS AG - Working Student (10/1997 - 04/1999)

+ +
diff --git a/stefankoelle/_includes/stefankoelle.njk b/stefankoelle/_includes/stefankoelle.njk new file mode 100644 index 0000000..ac93f08 --- /dev/null +++ b/stefankoelle/_includes/stefankoelle.njk @@ -0,0 +1,73 @@ + + + + + + + + {% if title %}{{ title }}{% else %}Stefan Koelle{% endif %} + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + {{ content | safe }} + +
+
+ + + + diff --git a/stefankoelle/assets/android-icon-144x144.png b/stefankoelle/assets/android-icon-144x144.png new file mode 100755 index 0000000..196c0a5 Binary files /dev/null and b/stefankoelle/assets/android-icon-144x144.png differ diff --git a/stefankoelle/assets/android-icon-192x192.png b/stefankoelle/assets/android-icon-192x192.png new file mode 100755 index 0000000..993b8c9 Binary files /dev/null and b/stefankoelle/assets/android-icon-192x192.png differ diff --git a/stefankoelle/assets/android-icon-36x36.png b/stefankoelle/assets/android-icon-36x36.png new file mode 100755 index 0000000..582eca9 Binary files /dev/null and b/stefankoelle/assets/android-icon-36x36.png differ diff --git a/stefankoelle/assets/android-icon-48x48.png b/stefankoelle/assets/android-icon-48x48.png new file mode 100755 index 0000000..57438e6 Binary files /dev/null and b/stefankoelle/assets/android-icon-48x48.png differ diff --git a/stefankoelle/assets/android-icon-72x72.png b/stefankoelle/assets/android-icon-72x72.png new file mode 100755 index 0000000..db036c2 Binary files /dev/null and b/stefankoelle/assets/android-icon-72x72.png differ diff --git a/stefankoelle/assets/android-icon-96x96.png b/stefankoelle/assets/android-icon-96x96.png new file mode 100755 index 0000000..3e1ffe1 Binary files /dev/null and b/stefankoelle/assets/android-icon-96x96.png differ diff --git a/stefankoelle/assets/apple-icon-114x114.png b/stefankoelle/assets/apple-icon-114x114.png new file mode 100755 index 0000000..91fe4a8 Binary files /dev/null and b/stefankoelle/assets/apple-icon-114x114.png differ diff --git a/stefankoelle/assets/apple-icon-120x120.png b/stefankoelle/assets/apple-icon-120x120.png new file mode 100755 index 0000000..8686d9e Binary files /dev/null and b/stefankoelle/assets/apple-icon-120x120.png differ diff --git a/stefankoelle/assets/apple-icon-144x144.png b/stefankoelle/assets/apple-icon-144x144.png new file mode 100755 index 0000000..196c0a5 Binary files /dev/null and b/stefankoelle/assets/apple-icon-144x144.png differ diff --git a/stefankoelle/assets/apple-icon-152x152.png b/stefankoelle/assets/apple-icon-152x152.png new file mode 100755 index 0000000..4150129 Binary files /dev/null and b/stefankoelle/assets/apple-icon-152x152.png differ diff --git a/stefankoelle/assets/apple-icon-180x180.png b/stefankoelle/assets/apple-icon-180x180.png new file mode 100755 index 0000000..4bea384 Binary files /dev/null and b/stefankoelle/assets/apple-icon-180x180.png differ diff --git a/stefankoelle/assets/apple-icon-57x57.png b/stefankoelle/assets/apple-icon-57x57.png new file mode 100755 index 0000000..7a889aa Binary files /dev/null and b/stefankoelle/assets/apple-icon-57x57.png differ diff --git a/stefankoelle/assets/apple-icon-60x60.png b/stefankoelle/assets/apple-icon-60x60.png new file mode 100755 index 0000000..de30334 Binary files /dev/null and b/stefankoelle/assets/apple-icon-60x60.png differ diff --git a/stefankoelle/assets/apple-icon-72x72.png b/stefankoelle/assets/apple-icon-72x72.png new file mode 100755 index 0000000..db036c2 Binary files /dev/null and b/stefankoelle/assets/apple-icon-72x72.png differ diff --git a/stefankoelle/assets/apple-icon-76x76.png b/stefankoelle/assets/apple-icon-76x76.png new file mode 100755 index 0000000..adad9d8 Binary files /dev/null and b/stefankoelle/assets/apple-icon-76x76.png differ diff --git a/stefankoelle/assets/apple-icon-precomposed.png b/stefankoelle/assets/apple-icon-precomposed.png new file mode 100755 index 0000000..172a814 Binary files /dev/null and b/stefankoelle/assets/apple-icon-precomposed.png differ diff --git a/stefankoelle/assets/apple-icon.png b/stefankoelle/assets/apple-icon.png new file mode 100755 index 0000000..172a814 Binary files /dev/null and b/stefankoelle/assets/apple-icon.png differ diff --git a/stefankoelle/assets/email.js b/stefankoelle/assets/email.js new file mode 100644 index 0000000..a3bc267 --- /dev/null +++ b/stefankoelle/assets/email.js @@ -0,0 +1,17 @@ +function obfuscateEmail(user, domain, tld) { + return user + '@' + domain + '.' + tld; +} +document.addEventListener('DOMContentLoaded', function() { + var emailLink = document.getElementById('email-link'); + if (emailLink) { + emailLink.href = 'mailto:' + obfuscateEmail('cv', 'stefankoelle', 'de'); + emailLink.textContent = obfuscateEmail('cv', 'stefankoelle', 'de'); + } +}); +document.addEventListener('DOMContentLoaded', function() { + var emailLink = document.getElementById('email-impressum'); + if (emailLink) { + emailLink.href = 'mailto:' + obfuscateEmail('impressum', 'stefankoelle', 'de'); + emailLink.textContent = obfuscateEmail('impressum', 'stefankoelle', 'de'); + } +}); diff --git a/stefankoelle/assets/favicon-16x16.png b/stefankoelle/assets/favicon-16x16.png new file mode 100755 index 0000000..1f2a551 Binary files /dev/null and b/stefankoelle/assets/favicon-16x16.png differ diff --git a/stefankoelle/assets/favicon-32x32.png b/stefankoelle/assets/favicon-32x32.png new file mode 100755 index 0000000..19a372a Binary files /dev/null and b/stefankoelle/assets/favicon-32x32.png differ diff --git a/stefankoelle/assets/favicon-96x96.png b/stefankoelle/assets/favicon-96x96.png new file mode 100755 index 0000000..3e1ffe1 Binary files /dev/null and b/stefankoelle/assets/favicon-96x96.png differ diff --git a/stefankoelle/assets/favicon.ico b/stefankoelle/assets/favicon.ico new file mode 100755 index 0000000..db60ab9 Binary files /dev/null and b/stefankoelle/assets/favicon.ico differ diff --git a/stefankoelle/assets/manifest.json b/stefankoelle/assets/manifest.json new file mode 100755 index 0000000..013d4a6 --- /dev/null +++ b/stefankoelle/assets/manifest.json @@ -0,0 +1,41 @@ +{ + "name": "App", + "icons": [ + { + "src": "\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} \ No newline at end of file diff --git a/stefankoelle/assets/ms-icon-144x144.png b/stefankoelle/assets/ms-icon-144x144.png new file mode 100755 index 0000000..196c0a5 Binary files /dev/null and b/stefankoelle/assets/ms-icon-144x144.png differ diff --git a/stefankoelle/assets/ms-icon-150x150.png b/stefankoelle/assets/ms-icon-150x150.png new file mode 100755 index 0000000..3f1279c Binary files /dev/null and b/stefankoelle/assets/ms-icon-150x150.png differ diff --git a/stefankoelle/assets/ms-icon-310x310.png b/stefankoelle/assets/ms-icon-310x310.png new file mode 100755 index 0000000..288ebfc Binary files /dev/null and b/stefankoelle/assets/ms-icon-310x310.png differ diff --git a/stefankoelle/assets/ms-icon-70x70.png b/stefankoelle/assets/ms-icon-70x70.png new file mode 100755 index 0000000..1f32fa6 Binary files /dev/null and b/stefankoelle/assets/ms-icon-70x70.png differ diff --git a/stefankoelle/assets/stefan-koelle-foto.jpg b/stefankoelle/assets/stefan-koelle-foto.jpg new file mode 100755 index 0000000..a031e6a Binary files /dev/null and b/stefankoelle/assets/stefan-koelle-foto.jpg differ diff --git a/stefankoelle/assets/stefan-koelle-vollbild.jpg b/stefankoelle/assets/stefan-koelle-vollbild.jpg new file mode 100755 index 0000000..071a19c Binary files /dev/null and b/stefankoelle/assets/stefan-koelle-vollbild.jpg differ diff --git a/stefankoelle/assets/style.css b/stefankoelle/assets/style.css new file mode 100644 index 0000000..4233042 --- /dev/null +++ b/stefankoelle/assets/style.css @@ -0,0 +1,199 @@ +/* stefankoelle.de — Modernized CSS (visually identical to original) */ + +:root { + --color-text: #665544; + --color-text-dark: #333; + --color-bg: #f4f4f4; + --color-surface: rgba(255, 255, 255, 0.9); + --color-surface-solid: #ffffff; + --color-link: #333; + --color-nav-bg: #eeeeee; + --color-shadow: rgba(0, 0, 0, 0.1); + --font-main: 'Roboto', sans-serif; + --transition: color 0.3s; +} + +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + color: var(--color-text); + font: 1em/1.6 var(--font-main); + background-color: var(--color-bg); +} + +a { color: var(--color-link); } + +h1 { font-size: 2.5em; } +h2 { font-size: 2em; margin-bottom: 10px; } +h3 { font-size: 1.5em; margin-top: 20px; margin-bottom: 10px; } + +p { margin-bottom: 10px; } + +.profile, .core-technologies, .contact-info { + margin-bottom: 20px; + max-width: 500px; +} + +/* Navigation */ +#head { display: none; } + +#links { + padding: 20px; + font-size: 1.25em; + text-align: center; + min-height: 20px; + background: var(--color-nav-bg); + position: fixed; + top: 0; + width: 100%; + z-index: 1000; + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + gap: 10px; +} + +.flex-break { + flex-basis: 100%; +} + +#links a { + color: var(--color-text-dark); + text-decoration: none; + font-weight: normal; + transition: var(--transition); +} + +#links a.separator::after { + content: " | "; + margin-left: 4px; +} + +#links i.fa-brands { + margin-right: 8px; +} + +/* Sections */ +.headlinespace { + height: 720px; + padding: 80px; + position: relative; +} + +#headline { + padding: 20px; + background: var(--color-surface); + width: calc(25% - 20px); + position: relative; + left: 20px; + box-shadow: 0 2px 4px var(--color-shadow); +} + +.space { + margin-top: 1060px; + padding: 150px; + position: relative; +} + +#cvcontent, +#personalprojectscontent, +#programminglanguagescontent, +#impressumcontent { + padding: 20px; + background: var(--color-surface); + width: calc(50% - 20px); + position: relative; + left: 20px; + box-shadow: 0 2px 4px var(--color-shadow); +} + +#impressumcontent { +} + +#cvcontent li, +#personalprojectscontent li, +#programminglanguagescontent li { + margin-left: 35px; +} + +/* Background */ +#background-image { + position: fixed; + top: 120px; + left: 0; + width: 100%; + height: calc(100% - 120px); + background: url('/assets/stefan-koelle-vollbild.jpg') no-repeat top center fixed; + background-size: cover; + background-position: 0 120px; + z-index: -1; +} + +/* Responsive */ +@media (max-width: 1024px) { + #container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; + } + + body { + color: var(--color-text-dark); + font-size: 1em; + } + + h1 { font-size: 2em; } + h2 { font-size: 1.75em; } + h3 { font-size: 1.25em; } + + #headline, + #cvcontent, + #personalprojectscontent, + #programminglanguagescontent, + #impressumcontent { + width: 100%; + padding: 20px; + left: 0; + background: var(--color-surface-solid); + margin-bottom: 20px; + box-shadow: 0 2px 4px var(--color-shadow); + } + + #cvcontent li, + #personalprojectscontent li, + #programminglanguagescontent li { + margin-left: 35px; + } + + #impressumcontent { + padding-top: 20px; + padding-bottom: 20px; + } + + .headlinespace { + height: 20px; + padding: 20px; + } + + .space { + margin-top: 40px; + width: 20px; + padding: 20px; + } + + #background-image { display: none; } + + #links { + padding: 20px; + background: var(--color-nav-bg); + position: relative; + width: 100%; + } + + #head { display: block; padding-top: 20px; } +} diff --git a/stefankoelle/cv-print.njk b/stefankoelle/cv-print.njk new file mode 100644 index 0000000..6979699 --- /dev/null +++ b/stefankoelle/cv-print.njk @@ -0,0 +1,38 @@ + + + + + + CV - Stefan Koelle + + + + +
+
+
+

Stefan Kölle

+ Senior Software Developer & Software Architect +
+ {% include "cv-content.njk" %} +
+
+ + diff --git a/stefankoelle/eleventy.config.js b/stefankoelle/eleventy.config.js new file mode 100644 index 0000000..b6d740f --- /dev/null +++ b/stefankoelle/eleventy.config.js @@ -0,0 +1,19 @@ +module.exports = function (eleventyConfig) { + eleventyConfig.addGlobalData("site", { url: "https://stefankoelle.de" }); + eleventyConfig.addFilter("date", (d) => d.toISOString()); + eleventyConfig.addPassthroughCopy({ "stefankoelle/assets": "assets" }); + eleventyConfig.addPassthroughCopy({ "stefankoelle/ledmatrix/assets": "ledmatrix/assets" }); + eleventyConfig.addPassthroughCopy({ "stefankoelle/robots.txt": "robots.txt" }); + + return { + dir: { + input: "stefankoelle", + includes: "_includes", + output: "dist/stefankoelle" + }, + serverOptions: { + host: "0.0.0.0", + port: 8086 + } + }; +}; diff --git a/stefankoelle/index.njk b/stefankoelle/index.njk new file mode 100644 index 0000000..49a3799 --- /dev/null +++ b/stefankoelle/index.njk @@ -0,0 +1,275 @@ +--- +title: "Stefan Koelle" +description: "Experienced Senior Software Developer and Software Architect with over 20 years of experience. Specialized in .NET Core and Kubernetes, with hands-on experience in AI-assisted development using Claude Code." +layout: stefankoelle.njk +--- +
+ +
+
+

Stefan Kölle

+ Senior Software Developer & Software Architect +
+ +
+ +
+{% include "cv-content.njk" %} + +
+ +
+
+

Personal Projects

+ +

Home Network Projects (2025-2026)

+ + +

Retro Computing (2022/2023)

+ + +

Upgrade of old Atari Mega 4 (2021)

+ + +

Home Automation (since 2018)

+ + +

Proxmox Server with Various LxC Linux (since 2020)

+ + +

Various Docker Systems (since 2016)

+ + +

Walk-on Song (2019)

+ + +

Kids RFID MP3 Player (2018)

+ + +

Dotnet Community Activity (2009 - 2011)

+ + +

Owner & Developer, inseco.de (2003 - 2008)

+ + +

Home-Network Projects during IT Manager Phase (2000 - 2006)

+ + +

Mailbox/Fidonet (1994 - 2000)

+ + +

PC Intro / Game / Software Development (1992 - 1994)

+ + +

Atari ST Software Development (1990 - 1992)

+ +
+ +
+ +
+
+

Programming Languages

+ +

Since 2025, AI-assisted development with Claude Code has become a core part of my workflow. This approach enables faster delivery from idea to production, better documentation artifacts, and cross-technology work across .NET, TypeScript, and Kubernetes.

+ +

.NET Core

+ + +

TypeScript

+ + +

Python

+ + +

.NET Framework

+ + +

PHP

+ + +

ASP-Classic

+ + +

Java

+ + +

C++

+ + +

PASCAL

+ + +

Assembler

+ + +

C

+ + +

BASIC

+ +
+ +
+ +
+
+

Legal Notice (Impressum, § 5 DDG)

+

Stefan Kölle
Neumarkter Str. 86c
81673 München

+

Phone/Fax: +49-(89)-20006547
E-Mail: [email protected]

+

Responsible for content according to § 18 (2) MStV: Stefan Kölle, address as above.

+

Full legal notice & privacy policy for this website and the moonweb.org network: hub.moonweb.org/impressum +

diff --git a/stefankoelle/ledmatrix/assets/ledmatrix-az.jpg b/stefankoelle/ledmatrix/assets/ledmatrix-az.jpg new file mode 100755 index 0000000..1459d83 Binary files /dev/null and b/stefankoelle/ledmatrix/assets/ledmatrix-az.jpg differ diff --git a/stefankoelle/ledmatrix/assets/ledmatrix-blue.jpg b/stefankoelle/ledmatrix/assets/ledmatrix-blue.jpg new file mode 100755 index 0000000..c02781a Binary files /dev/null and b/stefankoelle/ledmatrix/assets/ledmatrix-blue.jpg differ diff --git a/stefankoelle/ledmatrix/assets/ledmatrix-green.jpg b/stefankoelle/ledmatrix/assets/ledmatrix-green.jpg new file mode 100755 index 0000000..73cafed Binary files /dev/null and b/stefankoelle/ledmatrix/assets/ledmatrix-green.jpg differ diff --git a/stefankoelle/ledmatrix/assets/ledmatrix-kind.jpg b/stefankoelle/ledmatrix/assets/ledmatrix-kind.jpg new file mode 100755 index 0000000..0a166e9 Binary files /dev/null and b/stefankoelle/ledmatrix/assets/ledmatrix-kind.jpg differ diff --git a/stefankoelle/ledmatrix/assets/ledmatrix-yellow.jpg b/stefankoelle/ledmatrix/assets/ledmatrix-yellow.jpg new file mode 100755 index 0000000..c2516db Binary files /dev/null and b/stefankoelle/ledmatrix/assets/ledmatrix-yellow.jpg differ diff --git a/stefankoelle/ledmatrix/assets/ledmatrix.jpg b/stefankoelle/ledmatrix/assets/ledmatrix.jpg new file mode 100755 index 0000000..63e7300 Binary files /dev/null and b/stefankoelle/ledmatrix/assets/ledmatrix.jpg differ diff --git a/stefankoelle/ledmatrix/assets/style.css b/stefankoelle/ledmatrix/assets/style.css new file mode 100644 index 0000000..e277d63 --- /dev/null +++ b/stefankoelle/ledmatrix/assets/style.css @@ -0,0 +1,416 @@ +/* LED Matrix WebServer — Standalone CSS */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --color-primary: #2196F3; + --color-primary-dark: #1976D2; + --color-accent: #00BCD4; + --color-success: #4CAF50; + --color-warning: #FF9800; + --color-error: #F44336; + + --color-bg: #f5f7fa; + --color-surface: #ffffff; + --color-text: #333333; + + --shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.15); + + --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; + background: var(--color-bg); + color: var(--color-text); + line-height: 1.6; +} + +/* Header & Navigation */ +header { + background: var(--color-surface); + box-shadow: var(--shadow); + position: sticky; + top: 0; + z-index: 100; +} + +nav { + max-width: 1200px; + margin: 0 auto; + padding: 1rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + font-size: 1.5rem; + font-weight: 700; + color: var(--color-primary); + text-decoration: none; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.nav-menu { + display: flex; + gap: 2rem; + list-style: none; + align-items: center; +} + +.nav-menu a { + color: var(--color-text); + text-decoration: none; + font-weight: 500; + transition: var(--transition); + position: relative; +} + +.nav-menu a::after { + content: ''; + position: absolute; + bottom: -4px; + left: 0; + width: 0; + height: 2px; + background: var(--color-primary); + transition: var(--transition); +} + +.nav-menu a:hover::after { + width: 100%; +} + +/* Hero Section */ +.hero { + background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-accent) 100%); + color: white; + padding: 4rem 2rem; + text-align: center; +} + +.hero-content { + max-width: 1200px; + margin: 0 auto; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 3rem; + align-items: center; +} + +.hero-text h1 { + font-size: 2.5rem; + margin-bottom: 1rem; + line-height: 1.2; +} + +.hero-text p { + font-size: 1.1rem; + margin-bottom: 2rem; + opacity: 0.95; +} + +.version-badge { + display: inline-block; + background: rgba(255, 255, 255, 0.2); + padding: 0.5rem 1rem; + border-radius: 20px; + font-size: 0.9rem; + margin-bottom: 1rem; + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.cta-buttons { + display: flex; + gap: 1rem; + justify-content: flex-start; +} + +.btn { + padding: 0.75rem 1.5rem; + border-radius: 6px; + border: none; + font-weight: 600; + cursor: pointer; + transition: var(--transition); + text-decoration: none; + display: inline-block; + font-size: 1rem; +} + +.btn-primary { + background: white; + color: var(--color-primary); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); +} + +.btn-secondary { + background: rgba(255, 255, 255, 0.2); + color: white; + border: 2px solid white; +} + +.btn-secondary:hover { + background: white; + color: var(--color-primary); +} + +.hero-image { + text-align: center; +} + +.hero-image img { + max-width: 100%; + height: auto; +} + +@media (max-width: 768px) { + .hero-content { + grid-template-columns: 1fr; + } + .hero-text h1 { + font-size: 1.8rem; + } + .cta-buttons { + flex-direction: column; + } +} + +/* Main Content */ +main { + max-width: 1200px; + margin: 0 auto; + padding: 3rem 2rem; +} + +.section { + margin-bottom: 3rem; +} + +.section-title { + font-size: 2rem; + margin-bottom: 1.5rem; + color: var(--color-primary); + display: flex; + align-items: center; + gap: 0.5rem; +} + +.section-title::before { + content: ''; + width: 4px; + height: 2rem; + background: var(--color-primary); + border-radius: 2px; +} + +/* Cards */ +.cards-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 2rem; + margin-bottom: 2rem; +} + +.card { + background: var(--color-surface); + border-radius: 8px; + padding: 1.5rem; + box-shadow: var(--shadow); + transition: var(--transition); + border-left: 4px solid var(--color-primary); +} + +.card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-lg); +} + +.card-title { + font-size: 1.2rem; + font-weight: 600; + margin-bottom: 0.5rem; + color: var(--color-primary); +} + +.card-subtitle { + font-size: 0.85rem; + color: var(--color-accent); + margin-bottom: 1rem; +} + +.card-content { + font-size: 0.95rem; + line-height: 1.6; +} + +/* Features */ +.features { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 2rem; +} + +.feature { + display: flex; + gap: 1rem; +} + +.feature-icon { + font-size: 2rem; + color: var(--color-primary); + flex-shrink: 0; +} + +.feature-text h3 { + margin-bottom: 0.5rem; + color: var(--color-primary); +} + +.feature-text p { + font-size: 0.95rem; +} + +/* Specs Table */ +.specs-table { + background: var(--color-surface); + border-radius: 8px; + overflow: hidden; + box-shadow: var(--shadow); + margin-bottom: 2rem; +} + +.specs-table table { + width: 100%; + border-collapse: collapse; +} + +.specs-table th { + background: var(--color-primary); + color: white; + padding: 1rem; + text-align: left; + font-weight: 600; +} + +.specs-table td { + padding: 1rem; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); +} + +.specs-table tr:last-child td { + border-bottom: none; +} + +.specs-table tr:nth-child(even) { + background: rgba(33, 150, 243, 0.02); +} + +/* Code Block */ +.code-block { + background: var(--color-surface); + border-left: 4px solid var(--color-accent); + padding: 1.5rem; + border-radius: 6px; + overflow-x: auto; + margin: 1.5rem 0; + box-shadow: var(--shadow); +} + +.code-block pre { + margin: 0; + font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace; + font-size: 0.9rem; + line-height: 1.5; +} + +.code-label { + font-size: 0.85rem; + color: var(--color-accent); + font-weight: 600; + margin-bottom: 0.5rem; + text-transform: uppercase; + letter-spacing: 1px; +} + +/* Footer */ +footer { + background: var(--color-surface); + border-top: 1px solid rgba(0, 0, 0, 0.1); + padding: 3rem 2rem; + margin-top: 4rem; +} + +.footer-content { + max-width: 1200px; + margin: 0 auto; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 2rem; + margin-bottom: 2rem; +} + +.footer-section h4 { + margin-bottom: 1rem; + color: var(--color-primary); +} + +.footer-section ul { + list-style: none; +} + +.footer-section a { + color: var(--color-text); + text-decoration: none; + opacity: 0.8; + transition: var(--transition); +} + +.footer-section a:hover { + color: var(--color-primary); + opacity: 1; +} + +.footer-bottom { + text-align: center; + padding-top: 2rem; + border-top: 1px solid rgba(0, 0, 0, 0.1); + opacity: 0.7; +} + +/* Responsive */ +@media (max-width: 768px) { + nav { + flex-direction: column; + gap: 1rem; + } + + .nav-menu { + flex-direction: column; + gap: 1rem; + } + + main { + padding: 2rem 1rem; + } + + .section-title { + font-size: 1.5rem; + } + + .hero-text h1 { + font-size: 2rem; + } +} diff --git a/stefankoelle/ledmatrix/index.njk b/stefankoelle/ledmatrix/index.njk new file mode 100644 index 0000000..df42127 --- /dev/null +++ b/stefankoelle/ledmatrix/index.njk @@ -0,0 +1,563 @@ +--- +title: "LED Matrix WebServer" +description: "WiFi-controlled LED display solution with REST API, DHT22 sensor, and OTA updates." +--- + + + + + + + + {{ title }} - ESP32 Projekt + + + +
+ +
+ +
+
+
+
v1.08 (DHT) • January 2026
+

LED Matrix WebServer

+

WiFi-controlled LED display solution with REST API, DHT22 sensor, and OTA updates for your home network.

+ +
+
+

📊

+
+
+
+ +
+
+

Overview

+

+ The LED Matrix WebServer system is a fully configurable LED display solution for home networks. + It is based on the NodeMCU-32S ESP32 with up to 12 cascaded MAX7219 8x8 LED modules. + Using a simple REST API, messages can be displayed, animations controlled, and sensor data queried. +

+ +
+
+
🎯 Easy to use
+
Web-based control
+
Control your LED matrix via HTTP REST API from anywhere in the network
+
+
+
🌐 WiFi integrated
+
Fritz!Box Mesh compatible
+
Automatic WiFi connection and reconnect
+
+
+
📡 OTA updates
+
Update firmware wirelessly
+
Update your firmware without a USB connection
+
+
+
🌡️ Sensors
+
DHT22 integrated
+
Read temperature and humidity
+
+
+
🔊 Buzzer sounds
+
Tone sequences
+
Predefined melodies for alarms and notifications
+
+
+
📦 Extensible
+
Analog inputs
+
GPIO 34/35/36 available for external sensors
+
+
+
+ +
+

Hardware Components

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ComponentModel / TypeFeatures
MicrocontrollerNodeMCU-32S ESP3232-Bit Dual-Core 240MHz, WiFi + Bluetooth
LED Display12× MAX7219 8x8 Modulecascaded
SensorDHT22 (AM2302)Temperature ±0.5°C, Humidity ±2%
BuzzerPassive BuzzerGPIO 16 PWM controlled via NPN transistor
Power SupplyUSB-C oder externe 5V PSU5V for MAX7219, 3.3V internal for logic
+
+ +

Pin Configuration

+
+
GPIO Pin Assignment
+
LedMatrix Software V1.08
+NodeMCU-32S ESP32 (USB powered)
+
+POWER SUPPLY:
+  Micro USB ──→ ESP32 (5V regulator generates 3.3V internal)
+
+SPI SIGNALS (MAX7219):
+  VIN 5V  ──→ MAX7219 VCC
+  GND     ──→ MAX7219 GND
+  GPIO 5  ──→ MAX7219 CS  (Chip Select)
+  GPIO 18 ──→ MAX7219 CLK (Clock)
+  GPIO 23 ──→ MAX7219 DIN (Data In)
+  Modul 1 DOUT → Modul 2 DIN → Modul 3 DIN
+
+DHT22 SENSOR:
+  GPIO 27 ──→ DHT22 Data
+  3V3    ──→ DHT22 VCC
+  GND     ──→ DHT22 GND
+
+BUZZER:
+  GPIO 16 ──→ Buzzer Control (via transistor)
+  GPIO 16 ──1kΩ── NPN Transistor BASE
+                    NPN Transistor COLLECTOR ──→ Buzzer +
+                    NPN Transistor EMITTER ──→ GND
+  Buzzer - ──→ GND (ESP32)
+
+ANALOG INPUTS (optional):
+  GPIO 34 ──→ Analog Sensor 1
+  GPIO 35 ──→ Analog Sensor 2
+  GPIO 36 ──→ Analog Sensor 3
+
+
+ +
+

Installation & Setup

+

+ Here are photos of various LED Matrix installations in the home network. The systems show different colors and configurations. +

+ +
+
+ Red LED Matrix Installation +
+

Red Installation

+

LED Matrix with red backlight on iMac

+
+
+ +
+ LED Matrix above AnyCubic Printer +
+

AnyCubic Printer Setup

+

LED Matrix installation above 3D printer

+
+
+ +
+ Green LED Matrix Installation +
+

Green-yellow Installation

+

LED Matrix with green/yellow LED configuration

+
+
+ +
+ Green LED Matrix Close-up +
+

Green Close-up

+

Detailed view of a green LED Matrix with wiring

+
+
+ +
+ Blue LED Matrix Installation +
+

Blue Installation

+

LED Matrix with blue brightness
Many pixels defective

+
+
+ +
+ Cyan LED Matrix Installation +
+

Cyan Installation

+

Multiple modules with cyan-colored lighting
Some pixels defective

+
+
+
+
+ +
+

Feature Overview

+ +
+
+
📱
+
+

REST API

+

HTTP-based control of all functions from anywhere in the network

+
+
+
+
🎬
+
+

Animations

+

MD_Parola Library with various scroll modes

+
+
+
+
💾
+
+

EEPROM Storage

+

Persistently save and load messages

+
+
+
+
🔄
+
+

Asynchronous Web Engine

+

Non-blocking AsyncWebServer for reliable performance

+
+
+
+
🌡️
+
+

Sensor Integration

+

DHT22 read every 60 seconds

+
+
+
+
🔊
+
+

Sounds & Alarms

+

4 predefined melodies: Error, Done, Warning, Completed

+
+
+
+
+ +
+

REST API Documentation

+ +

+ All endpoints are accessible via HTTP. The LED Matrix is recognized as ledmatrix.fritz.box in the network. +

+ +

Base Endpoints

+ +
+
Welcome Page HTML
+
GET /
+Response: HTML Welcome Page with menu
+
+ +
+
Firmware-Version
+
GET /version
+Response: v1.08 (dht)
+
+ +

Message & Display

+ +
+
Display message (static)
+
GET /print?text=Hallo%20Welt
+Displays a static message on the LED matrix (max 75 characters)
+
+ +
+
Message with scroll animation
+
GET /scroll?text=Scrolling%20Text
+Text scrolls from left to right across the matrix
+Switches back to the last print message afterwards
+
+ +
+
Test display
+
GET /test
+Displays "Hello world" on the LED matrix
+
+ +
+
Change LED brightness
+
GET /set?intensity=8
+Value: 0 (dark) to 15 (bright)
+
+ +
+
Turn off display
+
GET /off
+Turns off the LED matrix (power saving)
+
+ +

Sensor Data

+ +
+
Read temperature
+
GET /temperature
+Response: "23.5" (degrees Celsius)
+
+ +
+
Read humidity
+
GET /humidity
+Response: "65.2" (percent relative humidity)
+
+ +
+
Read analog inputs
+
GET /analog34
+GET /analog35
+GET /analog36
+Response: Value between 0-4095
+
+GET /analogvalues
+Response: "1234,2345,3456" (comma-separated values)
+
+ +

Sounds & Buzzer

+ +
+
Success melody (Done)
+
GET /sound/erledigt
+Plays positive tone sequence
+
+ +
+
Warnings melody
+
GET /sound/warnung
+Plays warning tone sequence
+
+ +
+
Error melody
+
GET /sound/fehler
+Plays error tone sequence
+
+ +
+
Completed melody
+
GET /sound/abgeschlossen
+Plays success tone sequence
+
+ +

Control & System

+ +
+
Restart system
+
GET /restart
+ESP32 will be restarted (with display shutdown)
+
+ +

curl Examples

+ +
+
curl: display text
+
curl "http://ledmatrix.fritz.box/print?text=Hallo%20Welt"
+
+ +
+
curl: text with scroll
+
curl "http://ledmatrix.fritz.box/scroll?text=Breaking%20News"
+
+ +
+
curl: read temperature
+
curl "http://ledmatrix.fritz.box/temperature"
+
+ +
+
curl: play sound
+
curl "http://ledmatrix.fritz.box/sound/erledigt"
+
+ +
+
curl: set brightness
+
curl "http://ledmatrix.fritz.box/set?intensity=10"
+
+
+ +
+

Technical Specifications

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyValueNotes
Firmware Versionv1.08 (DHT)January 2026
Power SupplyUSB-C 5V~500mA for logic
WiFi Standard802.11 b/g/n2.4GHz
LED Display12× 8x8 Module (96×8 Pixel)
DHT22 Range-40°C to +80°CHumidity 0-100% RH
Update MethodOTA (AsyncElegantOTA)Wireless via HTTP
DHT22 Update Interval60 secondsMinimum read interval
Message Buffer75 charactersMax. length of a message
+
+
+ +
+

Code Quality

+

+ The code has been thoroughly reviewed and classified as production-ready. +

+ +
+
+
✅ Strengths
+
+
    +
  • Robust WiFi management
  • +
  • Non-blocking Async-WebServer
  • +
  • Error handling for DHT
  • +
  • OTA updates implemented
  • +
+
+
+
+
⚠️ Potential for Improvement
+
+
    +
  • Hard-coded SSID/password
  • +
  • Expand input validation
  • +
+
+
+
+
📊 Evaluation
+
+
+ Grade: A-
+ Production ready
+ Uptime: 30-45 days normal +
+
+
+
+
+
+ + + + + + diff --git a/stefankoelle/pdf/cv-style.css b/stefankoelle/pdf/cv-style.css new file mode 100644 index 0000000..342f269 --- /dev/null +++ b/stefankoelle/pdf/cv-style.css @@ -0,0 +1,94 @@ +/* cv-style.css — Clean CV layout for PDF print */ + +:root { + --color-text: #333; + --color-heading: #1a1a1a; + --color-accent: #665544; + --color-border: #ddd; + --font-main: 'Roboto', sans-serif; +} + +@page { + size: A4; + margin: 20mm; +} + +body { + font-family: var(--font-main); + font-size: 10pt; + line-height: 1.5; + color: var(--color-text); +} + +h1 { + font-size: 22pt; + color: var(--color-heading); + margin-bottom: 4pt; + border: none; +} + +h2 { + font-size: 14pt; + color: var(--color-accent); + border-bottom: 2px solid var(--color-accent); + padding-bottom: 4pt; + margin-top: 16pt; + margin-bottom: 8pt; + border-left: none; +} + +h3 { + font-size: 11pt; + color: var(--color-heading); + margin-top: 10pt; + margin-bottom: 4pt; +} + +p { margin-bottom: 6pt; } + +ul { + margin: 4pt 0 8pt 16pt; + padding: 0; +} + +li { + margin-bottom: 3pt; +} + +a { + color: var(--color-text); + text-decoration: none; +} + +.contact-info { + margin-bottom: 12pt; +} + +.profile, .core-technologies { + margin-bottom: 12pt; +} + +/* Hide non-CV elements when generating PDF */ +#links, #head, #background-image, +.headlinespace, .space, +#personalprojectscontent, +#programminglanguagescontent, +#impressumcontent { + display: none !important; +} + +#headline { + position: static; + width: 100%; + box-shadow: none; + background: none; + padding: 0 0 12pt 0; +} + +#cvcontent { + position: static; + width: 100%; + box-shadow: none; + background: none; + padding: 0; +} diff --git a/stefankoelle/robots.txt b/stefankoelle/robots.txt new file mode 100644 index 0000000..b7f7f4c --- /dev/null +++ b/stefankoelle/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / +Disallow: /deploy/ + +Sitemap: https://stefankoelle.de/sitemap.xml