branch-coexist: 3 neue decks 223015b-2026 / 223015c-2026 / dhbw-2026 daneben main

per user-request einen branch der main-content UND rewrite-content trägt — saubere separierung statt symlink/merge-conflict.

struktur:
- slides/<course>/ → main-content restored, eigene assets (kein new-demo-leak)
- slides/<course>-2026/ → rewrite-content, eigene assets (komplett kopiert + new demos)

Makefile: COURSES += 223015b-2026 223015c-2026 dhbw-2026 mit eigenen NAME/KAPITEL/DEPLOY/KLAUSUR-konfig. Klausur für b+c=1, dhbw=leer. Deploy-paths /hdm/223015b-2026 etc.

make build durchlaufen für alle 6 decks, 0 fehler.

dedup: 223015c+dhbw assets identisch in beiden ordnern (kein neu-demos auf b ergänzt). 223015b 86 → 120 files in -2026 (= +34 neue eroeffner/usb-c-drei-achsen/container-vs-codec/etc).
This commit is contained in:
libretech committed 2026-05-14 21:54:15 +02:00
1 parent 6209f9f3a0
commit 351e3dfc23
467 files changed
+32391 -4701

No files matched your search

+837
View File
@@ -0,0 +1,837 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering – DHBW Stuttgart"
footer: "Michael Czechowski – SoSe 2026"
title: Web Engineering
---
<style>
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; --color-dimmed: #4a4a6a; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
section.invert h2 { color: #f48fb1; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: invert -->
<!-- _header: '' -->
<!-- _backgroundColor: #000 -->
![bg cover opacity:0.2](./assets/background.png)
# Web Engineering
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
**Sommersemester 2026**
---
# Ressourcen zum Selbstlernen
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
- **MDN** — [developer.mozilla.org](https://developer.mozilla.org/de/)
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/)
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
---
<!-- _class: lead -->
# Herzlich Willkommen!
## 1. Sitzung: 08.05.2026
---
# Über mich
![bg right:33% fit](./assets/logos/collage.png)
Michael Czechowski
- Software Engineer, ehemals freischaffend
- Lektor an HdM Stuttgart + DHBW
- librete.ch
---
# Vorlesungsplan (Auszug)
| Termin | Thema |
|--------|-------|
| 1 | Web Engineering (heute) |
| 2 | CSS Extended |
| 3 | Node.js Basics |
| 4 | Node.js Advanced |
| 5 | Testing |
| 6 | TypeScript |
| 7 | Docker |
| 8 | Best Practices |
---
# Prüfungsleistung — 75 P + 10 Bonus
**Projekt-Abgabe** statt Klausur.
| Bereich | Punkte |
|---------|-------:|
| Projekt-Struktur + README | 10 |
| Code-Qualität | 15 |
| Git-Hygiene | 10 |
| Tests | 10 |
| Docker | 10 |
| HTTP / API | 10 |
| Frontend + A11y | 10 |
| Präsentation + Bonus | 5 + 10 |
---
<!-- _class: lead -->
# Kontext: was ist „das Web"?
---
# Internet-Timeline
| Jahr | Was |
|------|-----|
| 1969 | ARPANET — 4 Knoten |
| 1971 | Email (Ray Tomlinson, `@`) |
| 1983 | TCP/IP-Standard |
| **1989** | **WWW** (Berners-Lee, CERN) |
| 1993 | Mosaic-Browser → Web wird populär |
---
# Das WWW (1989)
**Tim Berners-Lee am CERN.** Drei Standards:
- **HTTP** — Protokoll
- **HTML** — Format
- **URL** — Adresse
Patentfrei → Web konnte explodieren.
---
# Heute: physische Infrastruktur
- ~400 Unterseekabel
- 1,3 Mio km Glasfaser weltweit
- DE-CIX Frankfurt: größter Internet-Knoten (~12 Tbit/s)
Das Web ist **physisch.**
---
# URL → Pixel in 7 Schritten
1. **URL parsen** — Schema, Host, Port, Path
2. **DNS-Lookup** — Host-Name → IP
3. **TCP-Handshake** mit Server
4. **TLS-Handshake** (https)
5. **HTTP-Request** senden
6. Server antwortet mit **HTML + CSS + JS**
7. Browser **parsed + rendert** → Pixel
---
# HTTP — Anatomie
```http
GET /index.html HTTP/1.1
Host: librete.ch
User-Agent: Mozilla/5.0 ...
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
<!DOCTYPE html>...
```
**Statuscodes:** 200 OK · 301 Moved · 404 Not Found · 500 Server Error
---
<!-- _class: lead -->
# Onboarding
## Tools, Runtimes, Workflows
---
# Werkzeuge & Betriebssystem
| Bereich | Empfehlung |
|---------|------------|
| Editor | **VS Code** · JetBrains (edu lizenz frei) |
| Browser | **Chromium / Firefox** |
| Terminal | macOS Terminal · Linux Terminal · Windows Terminal |
| Paket-Manager | macOS: brew · Linux: apt/dnf · Windows: winget/scoop/choco |
| Versions-Control | Git + GitHub/Gitea |
---
# Node.js installieren
**Best Practice:** Versions-Manager statt Direkt-Install.
| OS | Empfehlung |
|----|------------|
| macOS | `brew install fnm` → `fnm install --lts` |
| Linux | `curl -fsSL https://fnm.vercel.app/install \| bash` |
| Windows | `winget install Schniz.fnm` |
**Aktuelle LTS:** Node 24.
---
# Warum Versions-Manager?
**Direkt-Install** (z.B. `brew install node`) bringt Probleme:
- Projekt-A braucht Node 18, Projekt-B braucht Node 22 → Konflikt
- Update bricht alte Projekte
- Kein automatisches Switching pro Projekt
**fnm / nvm / volta:** pro Projekt Node-Version, automatisch via `.nvmrc`.
---
# Alternative Runtimes
| Runtime | Stärke |
|---------|--------|
| **Node** | Standard, größtes Ökosystem |
| **Deno** | Sicher by default, TypeScript nativ |
| **Bun** | Schnell, all-in-one (runtime + bundler + tester) |
Für DHBW-Kurs: **Node 24 LTS.**
---
# Browser DevTools
**F12** öffnet das Power-Tool jeder Webentwicklung.
| Tab | Wofür |
|-----|-------|
| Elements | DOM-Baum inspizieren + live editieren |
| Console | JS ausführen, Errors lesen |
| Network | HTTP-Requests sehen, Latenz |
| Performance | Render + Memory profilen |
| Lighthouse | A11y · SEO · Perf-Audit |
---
# HTTP-Clients
Tools zum Testen von APIs:
- **curl** — CLI, überall
- **Hoppscotch** — Browser, Open Source
- **Postman** — GUI, kommerziell
- **VS Code REST Client** — Plugin
---
# Git Setup
```sh
git config --global user.name "Dein Name"
git config --global user.email "du@dhbw.de"
git config --global init.defaultBranch main
git config --global core.autocrlf input # Windows: true
```
**SSH-Key** für GitHub/Gitea ohne Passwort-Prompt:
```sh
ssh-keygen -t ed25519 -C "du@dhbw.de"
cat ~/.ssh/id_ed25519.pub # zu GitHub kopieren
```
---
# Projekt-Skeleton
```
mein-projekt/
├── .git/
├── .gitignore # node_modules/, .env, build/
├── README.md # was, wie, warum
├── package.json # npm metadata + scripts
├── src/ # eigener Code
├── public/ # statische Assets (für Web)
├── tests/ # Unit + Integration
└── Dockerfile # für Container-Deploy
```
---
<!-- _class: lead -->
# HTML
## Hypertext Markup Language
---
# Was ist HTML?
**Tim Berners-Lee, 1989.** Struktur-Format des Webs.
**Syntax:** Tags mit Attributen umfassen Inhalt.
**Browser parst HTML** → DOM (Document Object Model) → rendert.
Plain-Text mit Tags. Lesbar mit jedem Editor.
---
# HTML-Anatomie
```html
<a href="https://librete.ch" target="_blank">Mein Link</a>
```
- `<a` — Opening-Tag mit Tag-Namen
- `href="..."` — Attribut (Name + Wert)
- `Mein Link` — Inhalt
- `</a>` — Closing-Tag
- Alles zusammen = **Element**
---
# Self-closing Tags
```html
<img src="foto.jpg" alt="Sonnenuntergang">
<br>
<input type="text" name="username">
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
```
Kein Closing-Tag. Inhalt aus Attributen + (für `<input>`) User-Eingabe.
---
# Document-Grundgerüst
```html
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Meine Seite</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hallo Welt</h1>
</body>
</html>
```
---
# Semantische HTML-Tags
| Tag | Bedeutung |
|-----|-----------|
| `<header>` | Kopfbereich (Logo, Nav) |
| `<nav>` | Navigation |
| `<main>` | Hauptinhalt |
| `<section>` | Thematischer Abschnitt |
| `<article>` | eigenständiger Inhalt |
| `<aside>` | Seiten-Inhalt |
| `<footer>` | Fußbereich |
**Pflicht** für SEO + A11y + Wartbarkeit.
---
# Native HTML-Elemente — nicht vergessen!
Bevor du eine Custom-Component baust: **gibt's das schon nativ?**
| Element | Was |
|---------|-----|
| `<details>` / `<summary>` | Accordion ohne JS |
| `<dialog>` | Modal mit Native Backdrop |
| `<input type="date">` | Date-Picker, native, A11y |
| `<input type="color">` | Color-Picker |
| `<input type="range">` | Slider |
| `<datalist>` | Autocomplete-Vorschläge |
| `<progress>` | Lade-Balken |
**Faustregel:** Native zuerst, Custom nur wenn nötig.
---
<!-- _class: lead -->
# Barrierefreiheit
## A11y — Accessibility
---
# BFSG + EAA — Pflicht ab 28.06.2025
**Barrierefreiheitsstärkungsgesetz** (DE) setzt **European Accessibility Act** (EU) um.
**Pflicht:**
- Online-Shops, Banken, Versicherungen
- E-Books, Buchungssysteme
- Smartphone-Apps mit kommerziellem Bezug
**Ausnahme:** < 10 Mitarbeiter + < 2 Mio € Umsatz.
**Strafe:** bis 100.000 € Bußgeld + Klage durch Behindertenverbände.
---
# Wie nutzen Menschen das Web?
- **Sehen:** Screen-Reader (NVDA, JAWS, VoiceOver)
- **Hören:** Untertitel + Transkripte
- **Motorik:** Tastatur statt Maus, Switch-Control
- **Kognitiv:** klare Sprache, viel Whitespace
- **Vorübergehend:** Sonne im Bildschirm, gebrochener Arm, ÖPNV-Lärm
~10 % der Bevölkerung haben relevante Beeinträchtigungen. *Du auch — irgendwann.*
---
# WCAG · 4 Prinzipien (POUR)
| Prinzip | Was |
|---------|-----|
| **P**erceivable | Alt-Text · Untertitel · Kontrast |
| **O**perable | Keyboard · Skip-Links |
| **U**nderstandable | klare Sprache · Fehler-Hilfe |
| **R**obust | semantisches HTML · Standards-konform |
**WCAG 2.1 Level AA** = Mindest-Standard für BFSG.
---
# Perceivable — wahrnehmbar
- **Alt-Text** für jedes Bild (`<img alt="...">`)
- **Untertitel** für jedes Video
- **Kontrast 4.5:1** für Text (Level AA)
- **Skalierbar** bis 200 %
```html
<img src="foto.jpg" alt="Sonnenuntergang am Bodensee">
<!-- LEER alt="" für rein dekorative Bilder -->
```
---
# Operable — bedienbar
- Alle interaktiven Elemente **mit Tastatur erreichbar**
- **Fokus sichtbar** (`:focus-visible` mit Outline)
- **Skip-Links** für Screen-Reader (Hauptnav überspringen)
- **Genug Zeit** für Aufgaben (Auto-Logout pausierbar)
```css
button:focus-visible {
outline: 3px solid hotpink;
outline-offset: 2px;
}
```
---
# Understandable — verständlich
- **Lese-Niveau** angemessen (Level AA: 9. Klasse)
- **Klare Fehlermeldungen** (was war falsch, wie korrigieren)
- **Konsistente Navigation** (Header bleibt gleich)
- **`lang`-Attribut** im html-Element
```html
<html lang="de">
<p lang="en">This sentence is in English.</p>
</html>
```
---
# Robust — Tech-stabil
- **Semantisches HTML** statt div-Suppe
- **ARIA** *nur als Ergänzung*, nicht als Ersatz
- **Standards-konform** (Validator: validator.w3.org)
```html
<!-- ❌ semantisch falsch -->
<div onclick="...">Klick</div>
<!-- ✓ semantisch korrekt -->
<button onclick="...">Klick</button>
```
---
# A11y-Testen — Werkzeuge
| Tool | Was |
|------|-----|
| **Lighthouse** (Chrome DevTools) | automatischer Audit |
| **axe DevTools** | Plugin, präziser |
| **WAVE** | Browser-Plugin, visuell |
| **Manuell** | Maus weglegen, nur Tab |
| **Screen-Reader** | macOS: VoiceOver (Cmd+F5) · Win: NVDA |
**Automatisierte Tools** finden ~30 %. Rest = manuell.
---
<!-- _class: lead -->
# CSS
## Cascading Style Sheets
*(Vertiefung in Kap 2 — CSS Extended)*
---
# CSS einbinden
```html
<!-- External (best practice) -->
<link rel="stylesheet" href="./style.css">
<!-- Internal -->
<style>
body { background: #fafafa; }
</style>
<!-- Inline (vermeiden) -->
<button style="background: hotpink">Klick</button>
```
---
# CSS-Anatomie
```css
button {
background-color: hotpink;
padding: 10px 20px;
border-radius: 8px;
}
```
- `button` = **Selektor** (welche Elemente)
- `background-color` = **Property**
- `hotpink` = **Value**
- alles in `{ }` = **Declaration Block**
---
# Selektoren
| Selektor | Was |
|----------|-----|
| `p` | alle `<p>` |
| `.btn` | alle mit `class="btn"` |
| `#header` | das Element mit `id="header"` |
| `button:hover` | Button beim Hover |
| `nav > a` | direkte `<a>` unter `<nav>` |
| `*` | alles |
---
# Spezifizität — wer gewinnt?
```css
p { color: blue; } /* 1 */
.notice { color: red; } /* 10 */
#warn { color: green; } /* 100 */
[style] /* inline */ /* 1000 */
```
**Inline > ID > Klasse > Element.** Bei Gleichstand: **letzte Regel.**
`!important` umgeht alles — *vermeiden.*
---
# Box-Model
```
┌──────────────────────┐
│ margin │
│ ┌────────────────┐ │
│ │ border │ │
│ │ ┌──────────┐ │ │
│ │ │ padding │ │ │
│ │ │ content │ │ │
│ │ └──────────┘ │ │
│ └────────────────┘ │
└──────────────────────┘
```
**Best Practice:** `* { box-sizing: border-box; }` global.
---
# Farben
| Notation | Beispiel |
|----------|----------|
| Hex | `#FF5733` |
| RGB | `rgb(255, 87, 51)` |
| RGBA | `rgb(255, 87, 51, 0.5)` |
| HSL | `hsl(11, 100%, 60%)` |
| OKLCH (modern) | `oklch(0.7 0.2 30)` |
| Schlüsselwort | `hotpink`, `tomato` |
---
# Einheiten
| Einheit | Was |
|---------|-----|
| `px` | Pixel, fix |
| `rem` | relativ zu Wurzel-Font (16 px default) |
| `em` | relativ zu Eltern-Font |
| `%` | relativ zur Eltern-Größe |
| `vw` / `vh` | Viewport-Breite/-Höhe |
| `ch` | Breite eines Zeichens |
**Faustregel:** Font + Spacing in `rem`. Layout in `%` oder `fr`.
---
# Pseudo-Klassen & -Elemente
```css
/* Pseudo-Klassen (Zustand) */
a:hover { color: hotpink; }
input:focus { outline: 2px solid; }
button:disabled { opacity: 0.5; }
li:nth-child(2n) { background: #eee; }
/* Pseudo-Elemente (Virtuelles) */
p::first-letter { font-size: 2em; }
.tooltip::after { content: " 💡"; }
```
---
<!-- _class: lead -->
# JavaScript
## Die Sprache des Webs
*(Vertiefung in Kap 3+4 — Node.js)*
---
# ECMAScript
**ECMA International** (Schweiz) standardisiert die Sprache.
| Version | Jahr | Highlight |
|---------|------|-----------|
| ES5 | 2009 | strict mode, JSON |
| ES6 / 2015 | 2015 | `let` `const`, arrow, classes, modules |
| ES2017 | 2017 | async/await |
| ES2020 | 2020 | optional chaining `?.` |
| ES15 | 2024 | aktuell |
---
# JavaScript-Grundlagen
```javascript
// Variablen
const name = "Ada";
let alter = 56;
// Funktionen
const begrüßen = (name) => `Hallo ${name}`;
// Arrays + Objects
const fruits = ["A", "B"];
const user = { name: "Ada", admin: true };
// Schleifen
fruits.map(f => f.toUpperCase());
// async
async function laden() {
const res = await fetch('/api');
return res.json();
}
```
---
# DOM Manipulation
```javascript
// Element finden
const btn = document.querySelector('#myBtn');
// Inhalt ändern
btn.textContent = 'Neu';
btn.classList.add('active');
// Event-Handler
btn.addEventListener('click', () => {
console.log('Klick!');
});
// Element erzeugen
const li = document.createElement('li');
li.textContent = 'Item';
document.querySelector('ul').appendChild(li);
```
---
# Fetch + JSON
```javascript
// GET
const res = await fetch('https://api.github.com/users/torvalds');
const data = await res.json();
console.log(data.public_repos);
// POST
await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Hallo', body: '...' })
});
```
---
<!-- _class: lead -->
# Frameworks-Vorschau
---
# Framework-Landschaft
| Framework | Typ | Use-Case |
|-----------|-----|----------|
| **React** | Library | dominant, große Community |
| **Vue 3** | Framework | sanfter Einstieg |
| **Svelte** | Compiler | kein Virtual DOM, kleines Bundle |
| **Astro** | Hybrid | Content-First, Multi-Framework |
| **Angular** | Framework | Enterprise, Google |
Alle: **Komponenten + reaktive State + Re-Render.**
---
# Counter — Vanilla + React
**Vanilla JS:**
```javascript
let count = 0;
btn.onclick = () => { count++; display.textContent = count; };
```
**React (JSX):**
```jsx
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
```
---
# Counter — Vue + Svelte
**Vue 3 (Composition):**
```vue
<script setup>const count = ref(0)</script>
<template><button @click="count++">{{ count }}</button></template>
```
**Svelte:**
```svelte
<script>let count = 0</script>
<button on:click={() => count++}>{count}</button>
```
Vier Frameworks, **dasselbe Pattern:** State → UI re-rendert.
---
# Build-Tools
| Tool | Stärke |
|------|--------|
| **Vite** | schnell, modern, Default für React/Vue/Svelte |
| **Webpack** | etabliert, mächtig, verbreitet |
| **Parcel** | zero-config |
| **Bun** | all-in-one, sehr schnell |
**Heute:** Vite ist der Default für neue Projekte.
---
# Rendering-Modi
| Modus | Wann |
|-------|------|
| **CSR** (Client-Side) | Single-Page-Apps (Dashboard, Editor) |
| **SSR** (Server-Side) | SEO + erste Latenz wichtig (News, Shop) |
| **SSG** (Static Site Gen) | reine Content-Sites (Blog, Doku) |
| **ISR** (Incremental SSG) | Hybrid: Content selten ändert |
Astro / Next.js / Nuxt unterstützen alle.
---
<!-- _class: aufgabe -->
# Selbstlernen — Erste eigene Seite
1. **Mappe anlegen** `meine-seite/`
2. `index.html` mit dem **Grundgerüst** (DOCTYPE, head, body, h1, p)
3. `style.css` daneben — mit Hotpink-Background
4. **VS Code Live Server** starten → im Browser öffnen
5. **DevTools** auf, F12, Tab Elements → DOM-Baum anschauen
**Bonus:** Lighthouse Audit laufen lassen. Welchen Score erreicht ihr?
---
# Zusammenfassung
**Heute gelernt:**
- Internet-Geschichte → WWW → URL→Pixel-7-Schritte
- Onboarding-Stack: Node (fnm), VS Code, Git, DevTools
- HTML: Anatomie, semantisch, A11y-Foundation
- A11y: BFSG/EAA-Pflicht, WCAG POUR
- CSS-Basics (Vertiefung in Kap 2)
- JS-Basics (Vertiefung in Kap 3+4)
- Framework-Landschaft (Vorschau)
**Nächste Stunde:** CSS deeper — Flexbox, Grid, Responsive, Animations.
+453
View File
@@ -0,0 +1,453 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering – DHBW Stuttgart"
footer: "Michael Czechowski – SoSe 2026"
---
<style>
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; --color-dimmed: #4a4a6a; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
section.invert h2 { color: #f48fb1; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: lead -->
# Kapitel 2 — CSS Extended
## Box-Model · Flexbox · Grid · Responsive · Animations
---
# Recap Kap 1
| Bereich | Was wir kennen |
|---------|----------------|
| Anatomie | `selector { property: value }` |
| Selektoren | element, `.class`, `#id`, `:hover` |
| Spezifizität | inline > id > class > element |
| Farben | hex, rgb, hsl, oklch |
| Einheiten | px, rem, em, %, vw, vh |
**Heute:** Layout + Animation in Tiefe.
---
# Box-Model — die Erinnerung
```
┌──────────────────┐
│ margin │
│ ┌────────────┐ │
│ │ border │ │
│ │ ┌────────┐ │ │
│ │ │padding │ │ │
│ │ │content │ │ │
│ │ └────────┘ │ │
│ └────────────┘ │
└──────────────────┘
```
`box-sizing: border-box` global setzen.
---
# Box-Model in Code
```css
* { box-sizing: border-box; }
.card {
width: 320px; /* inkl. padding + border */
padding: 1.5rem;
border: 2px solid #ccc;
border-radius: 0.75rem;
margin: 1rem auto; /* Außen + horizontal zentriert */
}
```
Inspector im DevTools zeigt das Box-Model live an.
---
<!-- _class: lead -->
# Flexbox
## 1D-Layout (Reihe ODER Spalte)
---
# Flexbox — Basics
```css
.container {
display: flex;
gap: 1rem; /* Abstand zwischen Items */
flex-direction: row; /* row | column */
justify-content: space-between; /* Haupt-Achse */
align-items: center; /* Quer-Achse */
flex-wrap: wrap; /* umbruchen erlaubt */
}
```
```html
<div class="container">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
```
---
# Flexbox — Achsen
**`flex-direction: row` (default):** Haupt-Achse horizontal.
```
┌─────────────────────────────────────┐
│ ◄── justify-content ──► │
│ ▲ │
│ │ │
│ align-items │
│ │ │
│ ▼ │
└─────────────────────────────────────┘
```
`column`: Achsen drehen sich 90°.
---
# Flexbox — Item-Properties
```css
.item {
flex: 1; /* nimm gleiche Anteile vom verfügbaren Raum */
flex: 0 0 200px; /* fest 200px, kein wachsen, kein schrumpfen */
flex-grow: 2; /* doppelte Anteile */
order: -1; /* visuell zuerst, im DOM hinten */
align-self: end; /* override align-items für DIESES Item */
}
```
**Shorthand:** `flex: <grow> <shrink> <basis>`.
---
# Typische Flex-Patterns
```css
/* Navigation: Logo links, Items rechts */
.nav { display: flex; justify-content: space-between; }
/* Karte: Bild + Text nebeneinander */
.card { display: flex; gap: 1rem; align-items: center; }
/* Button-Gruppe: alle gleich groß */
.btn-group { display: flex; gap: 0.5rem; }
.btn-group button { flex: 1; }
/* Footer: gleichmäßig verteilt */
.footer { display: flex; justify-content: space-around; }
```
---
<!-- _class: lead -->
# Grid
## 2D-Layout (Reihe UND Spalte)
---
# Grid — Basics
```css
.container {
display: grid;
grid-template-columns: 1fr 2fr 1fr; /* 3 Spalten, Verhältnis 1:2:1 */
grid-template-rows: auto 1fr auto; /* Header, Body, Footer */
gap: 1rem;
}
```
`fr` = Fraction Unit — verbleibender Raum aufgeteilt.
---
# Grid — Areas (named layout)
```css
.layout {
display: grid;
grid-template-areas:
"header header"
"nav main"
"footer footer";
grid-template-columns: 200px 1fr;
}
.layout header { grid-area: header; }
.layout nav { grid-area: nav; }
.layout main { grid-area: main; }
.layout footer { grid-area: footer; }
```
Lesbarer als Spalten-/Zeilen-Nummern.
---
# Grid — Auto-Layout
```css
/* So viele Spalten wie reinpassen, jede min. 200px */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
```
Mobile: 1 Spalte. Tablet: 2-3. Desktop: 4-5. **Ohne Media Queries.**
---
# Flexbox vs. Grid
| | Flexbox | Grid |
|--|---------|------|
| Dimension | 1D | 2D |
| Use-Case | Navigation, Karten-Reihe, Button-Gruppe | Seiten-Layout, komplexes Raster |
| Item-Größe | flexibel | meist fest |
| Direction | row/column | rows + columns |
**Beide in einem Layout möglich:** Grid für Seite, Flexbox in Komponenten.
---
<!-- _class: lead -->
# Responsive Design
---
# Mobile-First mit Media Queries
```css
/* Default: Mobile */
.sidebar { display: none; }
.content { padding: 1rem; }
/* Tablet aufwärts */
@media (min-width: 768px) {
.sidebar { display: block; width: 240px; }
.content { padding: 2rem; }
}
/* Desktop */
@media (min-width: 1280px) {
.sidebar { width: 320px; }
}
```
**Mobile-First**: kleinste Größe als Default, dann größer.
---
# Viewport-Meta
```html
<meta name="viewport" content="width=device-width, initial-scale=1">
```
**Pflicht** im `<head>`. Ohne dieses Meta zoomt iPhone & Android alles auf 980 px Breite.
---
# Fluid Sizing — ohne Media Queries
```css
/* Font: skaliert zwischen 14px und 22px */
h1 { font-size: clamp(1.4rem, 2.5vw + 1rem, 2.5rem); }
/* Padding: skaliert mit Viewport */
.card { padding: clamp(1rem, 3vw, 3rem); }
/* Breite: nie mehr als 70 Zeichen für Lesetext */
.prose { max-width: 70ch; margin: auto; }
```
`clamp(min, ideal, max)` ist modernes Allzweck-Werkzeug.
---
<!-- _class: lead -->
# Animationen
---
# Transitions — sanfter Wechsel
```css
button {
background: hotpink;
transition: background 0.2s ease, transform 0.15s;
}
button:hover {
background: deeppink;
transform: translateY(-2px);
}
```
**Was animiert performant:** `transform`, `opacity`. **Was nicht:** `width`, `height`, `top/left`.
---
# Keyframe-Animationen
```css
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.7; }
100% { transform: scale(1); opacity: 1; }
}
.notification {
animation: pulse 1.5s ease infinite;
}
```
`animation: <name> <duration> <timing> <iteration>`.
---
# Reduced Motion — Pflicht
Nicht alle wollen Animationen. (Vestibuläre Probleme, ADHS, Migräne.)
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
```
**Standard-Bestandteil** jeder Seite — ein User-Setting respektieren.
---
<!-- _class: lead -->
# CSS-Variablen + Modern
---
# Custom Properties (CSS Variables)
```css
:root {
--primary: hotpink;
--gap: 1rem;
--radius: 0.75rem;
}
button {
background: var(--primary);
padding: var(--gap);
border-radius: var(--radius);
}
/* Dark Mode: nur Variablen umsetzen */
@media (prefers-color-scheme: dark) {
:root { --primary: deeppink; }
}
```
---
# CSS Functions
```css
.box {
width: calc(100% - 2rem); /* Mathe */
font-size: clamp(1rem, 2vw, 2rem); /* min, ideal, max */
background: linear-gradient(45deg, hotpink, deeppink);
color: oklch(0.7 0.2 30); /* moderner Farbraum */
filter: blur(2px) brightness(1.2); /* Bildeffekte */
}
```
---
# CSS-Frameworks
| Framework | Stil |
|-----------|------|
| **Tailwind CSS** | Utility-Klassen direkt im HTML |
| **Bootstrap** | Komponenten-Bibliothek (klassisch) |
| **Pico** | klassen-frei, semantisches HTML reicht |
| **Open Props** | CSS-Variablen-Set |
```html
<!-- Tailwind: alle Styles als Klassen -->
<button class="bg-pink-500 hover:bg-pink-600 px-4 py-2 rounded-md">
Klick
</button>
```
---
# Tailwind — Argumente dafür/dagegen
**Pro:**
- Schnell, weniger Context-Switch (HTML + CSS in einer Datei)
- Konsistent (Design-System eingebaut)
- Tree-Shaking → kleines Bundle
**Contra:**
- HTML voller Klassen — schwerer zu lesen
- Lernkurve eigene
- Versions-Lock-in mit Setup
**Faustregel:** für neue Projekte ausprobieren, vorhandene Projekte nicht zwanghaft migrieren.
---
<!-- _class: aufgabe -->
# Selbstlernen — Layout-Übung
1. **Flexbox-Spiel:** [flexboxfroggy.com](https://flexboxfroggy.com) — alle 24 Level durch.
2. **Grid-Spiel:** [cssgridgarden.com](https://cssgridgarden.com) — 28 Level durch.
3. **Eigene Karten-Galerie:** `repeat(auto-fit, minmax(...))` Pattern anwenden.
**Bonus:** Dark-Mode mit CSS-Variablen + `prefers-color-scheme`.
---
# Zusammenfassung
**Heute gelernt:**
- Box-Model + `box-sizing: border-box` global
- **Flexbox** für 1D-Layouts (Navigation, Karten-Reihen)
- **Grid** für 2D-Layouts (Seiten-Layout, Galerien)
- Mobile-First Media Queries + `clamp()` für fluid
- Transitions + Keyframes — `prefers-reduced-motion` respektieren
- CSS-Variablen für Theming
- Tailwind & Co. als moderner Framework-Stil
**Nächste Stunde:** Node.js — JavaScript außerhalb des Browsers.
+361
View File
@@ -0,0 +1,361 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering – DHBW Stuttgart"
footer: "Michael Czechowski – SoSe 2026"
---
<style>
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; --color-dimmed: #4a4a6a; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: lead -->
# Kapitel 3 — Node.js Basics
## JavaScript außerhalb des Browsers
---
# Was ist Node.js?
**Ryan Dahl, 2009.** JavaScript-Runtime auf V8 (Chrome's JS-Engine).
Vorher: JS war Browser-only.
Mit Node: JS auch als Server, CLI-Tools, Build-Skripte.
**Heute:** Node ist Standard-Backend für viele Web-Apps. Netflix, LinkedIn, NASA, Walmart.
---
# Warum Node?
| Vorteil | Was bedeutet das |
|---------|------------------|
| **JS überall** | gleiche Sprache Frontend + Backend |
| **NPM** | größtes Package-Ökosystem (~3 Mio Packages) |
| **Non-blocking I/O** | viele Verbindungen gleichzeitig |
| **Schnell genug** | V8 optimiert JS aggressiv |
| **Cross-platform** | Linux, macOS, Windows |
---
# Node Versions-Manager
**fnm** (oder `nvm`, `volta`) — pro Projekt Node-Version.
```sh
# Installieren (macOS/Linux)
brew install fnm # oder curl-script
# Aktuelle LTS
fnm install --lts
fnm use lts-latest # 24.x
# Pro Projekt
echo "24" > .nvmrc
fnm use # liest .nvmrc
```
**Aktuell:** Node 24 LTS.
---
# Projekt initialisieren
```sh
mkdir mein-projekt && cd mein-projekt
npm init -y # erzeugt package.json
# Abhängigkeiten installieren
npm install express # → dependencies
npm install -D vitest # → devDependencies (nur dev)
# Skripte ausführen
npm run start
npm test
```
`-y` = alle Defaults akzeptieren.
---
# package.json
```json
{
"name": "mein-projekt",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "node --watch src/server.js",
"start": "node src/server.js",
"test": "vitest"
},
"dependencies": {
"express": "^5.0.0"
},
"devDependencies": {
"vitest": "^2.0.0"
}
}
```
`"type": "module"` aktiviert ESM.
---
# ESM — modern
```javascript
// utils.js
export function greet(name) {
return `Hallo ${name}`;
}
export const VERSION = "1.0";
// main.js
import { greet, VERSION } from './utils.js';
console.log(greet('Ada'), VERSION);
```
**Pflicht:** `.js`-Endung in import-Pfad.
---
# CommonJS — Legacy
```javascript
// utils.cjs
function greet(name) {
return `Hallo ${name}`;
}
module.exports = { greet };
// main.cjs
const { greet } = require('./utils.cjs');
```
**Wann CJS?** Alte Codebases, npm-Packages, die nicht migriert sind. **Default heute:** ESM.
---
# Built-in Modules
```javascript
import { readFile } from 'node:fs/promises';
import { createServer } from 'node:http';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
```
`node:` Prefix macht explizit klar — kein npm-Package.
---
<!-- _class: lead -->
# HTTP-Server
---
# Eingebauter HTTP-Server
```javascript
import { createServer } from 'node:http';
const server = createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hallo Welt');
});
server.listen(3000, () => {
console.log('Server läuft auf :3000');
});
```
Nackt — ohne Framework. **Funktioniert.**
---
# Express — der Klassiker
```javascript
import express from 'express';
const app = express();
app.use(express.json()); // parsed JSON-Body
app.use(express.static('public')); // statische Dateien
app.get('/', (req, res) => {
res.send('Hallo Welt');
});
app.post('/api/echo', (req, res) => {
res.json({ youSent: req.body });
});
app.listen(3000);
```
---
# REST mit Express
```javascript
const users = [{ id: 1, name: 'Ada' }];
app.get('/users', (req, res) => res.json(users));
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id == req.params.id);
user ? res.json(user) : res.sendStatus(404);
});
app.post('/users', (req, res) => {
const user = { id: users.length + 1, ...req.body };
users.push(user);
res.status(201).json(user);
});
```
---
# Alternative Frameworks
| Framework | Stärke |
|-----------|--------|
| **Express** | etabliert, größte Community |
| **Fastify** | schneller, schemas eingebaut |
| **Hono** | leichtgewichtig, Edge-deployable |
| **Koa** | von Express-Autoren, modern |
Für DHBW: **Express** oder **Hono**.
---
# Routing-Pattern
```javascript
import { Router } from 'express';
const userRouter = Router();
userRouter.get('/', (req, res) => { /* alle Users */ });
userRouter.get('/:id', (req, res) => { /* ein User */ });
userRouter.post('/', (req, res) => { /* User anlegen */ });
userRouter.put('/:id', (req, res) => { /* User update */ });
userRouter.delete('/:id', (req, res) => { /* User löschen */ });
app.use('/users', userRouter); // mountet unter /users/*
```
---
<!-- _class: lead -->
# Fetch in Node
---
# fetch — global ab Node 18
```javascript
// GET
const res = await fetch('https://api.github.com/users/torvalds');
const data = await res.json();
console.log(data.public_repos);
// POST
const post = await fetch('https://api.example.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Hallo' })
});
```
Genau wie im Browser. **Kein extra Package** (vorher: `node-fetch`, `axios`).
---
# Error-Handling
```javascript
try {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const data = await res.json();
return data;
} catch (error) {
console.error('API call failed:', error.message);
throw error; // re-throw für caller
}
```
`res.ok` ist `true` für 2xx-Statuscodes.
---
# Environment Variables
```javascript
// .env (gitignored!)
DATABASE_URL=postgres://...
API_KEY=sk-...
PORT=3000
// src/config.js
import 'dotenv/config';
export const config = {
port: process.env.PORT || 3000,
dbUrl: process.env.DATABASE_URL,
apiKey: process.env.API_KEY,
};
```
**Niemals** API-Keys ins Repo committen.
---
<!-- _class: aufgabe -->
# Selbstlernen — Mini-API
1. **Projekt initialisieren:** `npm init -y`, ESM aktivieren
2. **Express installieren**
3. **REST-API** für eine Ressource (Books / Movies / TODO)
- `GET /items`, `GET /items/:id`, `POST /items`, `DELETE /items/:id`
4. **In-Memory-Store** als Array (echte DB kommt Kap 4)
5. **Mit Hoppscotch oder curl** testen
**Bonus:** `npm run dev` mit `--watch`-Flag.
---
# Zusammenfassung
**Heute gelernt:**
- Node.js als JS-Runtime außerhalb Browser
- **fnm + Node 24 LTS** als Versions-Setup
- **npm init**, package.json, scripts, dependencies
- **ESM** (import/export) als modernes Modul-System
- Built-in HTTP-Server + **Express** für REST
- **fetch** global ab Node 18
- **.env + dotenv** für Secrets
**Nächste Stunde:** async/await, Filesystem, Datenbank, Auth.
+380
View File
@@ -0,0 +1,380 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering – DHBW Stuttgart"
footer: "Michael Czechowski – SoSe 2026"
---
<style>
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; --color-dimmed: #4a4a6a; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: lead -->
# Kapitel 4 — Node.js Advanced
## async · Files · Database · Auth · Errors
---
<!-- _class: lead -->
# Asynchron + Promises
---
# Synchron vs. Asynchron
**Synchron** (blockierend):
```javascript
const data = readFileSync('huge.txt'); // wartet
console.log('weiter'); // erst danach
```
**Asynchron** (non-blocking):
```javascript
const data = await readFile('huge.txt'); // wartet ohne blockieren
console.log('weiter'); // erst danach
```
Node's Stärke: **non-blocking I/O** für viele parallele Operationen.
---
# Promise — Anatomie
```javascript
const promise = fetch('https://api.example.com');
// then-style (alt)
promise
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
// async/await (modern)
try {
const res = await fetch('https://api.example.com');
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
```
---
# Sequenziell vs. Parallel
```javascript
// SEQUENZIELL — langsam (warten + warten + warten)
const a = await fetch('/api/a');
const b = await fetch('/api/b');
const c = await fetch('/api/c');
// Gesamtzeit: a + b + c
// PARALLEL — schnell (alle gleichzeitig starten)
const [a, b, c] = await Promise.all([
fetch('/api/a'),
fetch('/api/b'),
fetch('/api/c'),
]);
// Gesamtzeit: max(a, b, c)
```
---
# Promise-Methoden
| Methode | Was |
|---------|-----|
| `Promise.all([…])` | alle erfolgreich ODER ein Fehler |
| `Promise.allSettled([…])` | alle, Resultat = { status, value/reason } |
| `Promise.race([…])` | erstes Resultat gewinnt (auch Fehler) |
| `Promise.any([…])` | erstes erfolgreiches, ignore Fehler |
```javascript
const fastest = await Promise.race([
fetch('/api'),
new Promise((_, rej) => setTimeout(() => rej('timeout'), 3000))
]);
```
---
<!-- _class: lead -->
# Filesystem
---
# fs/promises — Files lesen + schreiben
```javascript
import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';
// Lesen
const text = await readFile('input.txt', 'utf-8');
const buf = await readFile('image.png'); // Buffer
// Schreiben
await writeFile('output.txt', 'Hallo Welt');
await writeFile('data.json', JSON.stringify({ x: 1 }, null, 2));
// Verzeichnis lesen
const files = await readdir('./uploads');
// Verzeichnis anlegen
await mkdir('./tmp', { recursive: true });
```
---
# Streams für große Dateien
```javascript
import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
// 10 GB Logfile ohne RAM-Explosion verarbeiten
await pipeline(
createReadStream('huge.log'),
// ... transform-stream ...
createWriteStream('output.log')
);
```
Streams = Daten **stückweise** verarbeiten, nicht alles in RAM.
---
<!-- _class: lead -->
# Datenbank
---
# Welche DB für DHBW-Projekte?
| DB | Wann |
|----|------|
| **SQLite** | klein, embedded, eine Datei. Lokal/dev/demo. |
| **PostgreSQL** | Standard für „echte" Apps. Open Source, mächtig. |
| **MongoDB** | NoSQL, dokumentenorientiert. Selten erste Wahl. |
| **Redis** | Cache, Queue, schnell. |
Für Prüfungsleistung: **SQLite** (kein Setup) oder **Postgres** (production-realistisch).
---
# SQLite mit `better-sqlite3`
```javascript
import Database from 'better-sqlite3';
const db = new Database('app.db');
db.prepare(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`).run();
// Insert
const insert = db.prepare('INSERT INTO users (name) VALUES (?)');
insert.run('Ada');
// Query
const users = db.prepare('SELECT * FROM users').all();
const ada = db.prepare('SELECT * FROM users WHERE id = ?').get(1);
```
---
# ORM / Query-Builder
Für komplexere Apps: ORM statt Raw-SQL.
| Tool | Typ |
|------|-----|
| **Drizzle** | TypeScript-first, modern, lightweight |
| **Prisma** | umfangreich, eigenes Schema |
| **Knex** | Query-Builder, kein ORM |
| **TypeORM** | klassisches ORM |
```typescript
// Drizzle Beispiel
const result = await db.select().from(users).where(eq(users.id, 1));
```
---
# CRUD-Pattern mit Express + SQLite
```javascript
app.get('/users', (req, res) => {
const users = db.prepare('SELECT * FROM users').all();
res.json(users);
});
app.get('/users/:id', (req, res) => {
const user = db.prepare('SELECT * FROM users WHERE id = ?').get(req.params.id);
user ? res.json(user) : res.sendStatus(404);
});
app.post('/users', (req, res) => {
const { name } = req.body;
const result = db.prepare('INSERT INTO users (name) VALUES (?)').run(name);
res.status(201).json({ id: result.lastInsertRowid, name });
});
```
---
<!-- _class: lead -->
# Authentifizierung
---
# Auth-Strategien
| Strategie | Wann |
|-----------|------|
| **Session-Cookie** | klassische Web-App, Server-Side Render |
| **JWT** (JSON Web Token) | API + SPA, stateless |
| **OAuth** | „Mit Google einloggen", Drittpartei-Login |
| **Magic Link** | Email statt Passwort, modern |
| **Passkeys** | passwordless, FIDO2, Zukunft |
Für DHBW-Projekt: **Session-Cookie** (express-session) ODER **JWT**.
---
# Passwörter — *niemals plaintext*
```javascript
import bcrypt from 'bcrypt';
// Registrieren
const hashed = await bcrypt.hash('mein-passwort', 12);
db.prepare('INSERT INTO users (email, password) VALUES (?, ?)').run(email, hashed);
// Login
const user = db.prepare('SELECT * FROM users WHERE email = ?').get(email);
const valid = await bcrypt.compare(password, user.password);
```
**bcrypt** oder **argon2** für Passwort-Hashing. Niemals MD5/SHA1.
---
# JWT — kurzer Überblick
```javascript
import jwt from 'jsonwebtoken';
// Signieren (beim Login)
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '1d' }
);
res.cookie('token', token, { httpOnly: true, secure: true });
// Verifizieren (Middleware)
const token = req.cookies.token;
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload;
```
`httpOnly + secure + sameSite` — XSS/CSRF-Schutz.
---
<!-- _class: lead -->
# Error-Handling
---
# try/catch + Error-Klassen
```javascript
class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = 'NotFoundError';
this.status = 404;
}
}
try {
const user = await getUser(id);
if (!user) throw new NotFoundError(`User ${id} not found`);
res.json(user);
} catch (err) {
if (err instanceof NotFoundError) return res.status(404).json({ error: err.message });
console.error(err);
res.sendStatus(500);
}
```
---
# Globaler Express-Error-Handler
```javascript
// Am Ende aller Routes!
app.use((err, req, res, next) => {
console.error(err);
if (err.status) {
return res.status(err.status).json({ error: err.message });
}
res.status(500).json({ error: 'Internal Server Error' });
});
```
4 Argumente: `(err, req, res, next)` → Express erkennt Error-Handler.
---
<!-- _class: aufgabe -->
# Selbstlernen — REST-API mit DB
Erweitert eure Kap-3-API:
1. **SQLite-DB** mit `better-sqlite3`
2. **CRUD** mit DB statt In-Memory-Array
3. **Validierung** (z.B. mit `zod`) für POST-Body
4. **Auth:** einfache Login-Route mit bcrypt + JWT
5. **Global Error-Handler** für saubere Responses
**Bonus:** Async-Race-Condition simulieren + Promise.all nutzen.
---
# Zusammenfassung
**Heute gelernt:**
- **async/await + Promise** sauber anwenden, sequenziell vs. parallel
- **fs/promises + Streams** für Dateien
- **SQLite + Prepared Statements** als pragmatische DB
- **bcrypt** für Passwörter, **JWT** als Token-Pattern
- **Error-Klassen + Globaler Handler** in Express
**Nächste Stunde:** Testing — Vitest, TDD, Integration, E2E.
+383
View File
@@ -0,0 +1,383 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering – DHBW Stuttgart"
footer: "Michael Czechowski – SoSe 2026"
title: Testing
---
<style>
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: lead -->
# Kapitel 5 — Testing
## Unit · Integration · E2E · TDD
---
# Warum testen?
| Vorteil | Was bedeutet das |
|---------|------------------|
| **Confidence** | Refactor ohne Angst |
| **Regression-Schutz** | alter Bug taucht nicht wieder auf |
| **Living Documentation** | Tests zeigen, was Code soll |
| **CI/CD-Voraussetzung** | automatisch vor jedem Deploy |
**„Untested Code is broken by design."**
---
# Test-Pyramide
```
/\
/E2E\ wenige (langsam, fragil)
/-----\
/ Integ \ einige (mittel)
/---------\
/ Unit \ viele (schnell, isoliert)
/-------------\
```
**Faustregel:** 70 % Unit, 20 % Integration, 10 % E2E.
---
# Vitest — modernes JS-Testing
```sh
npm install -D vitest
```
```javascript
// utils.test.js
import { describe, it, expect } from 'vitest';
import { greet } from './utils.js';
describe('greet', () => {
it('returns greeting with name', () => {
expect(greet('Ada')).toBe('Hallo Ada');
});
it('handles empty string', () => {
expect(greet('')).toBe('Hallo ');
});
});
```
```sh
npx vitest
npx vitest --coverage
```
---
# Test-Struktur (AAA)
```javascript
it('adds two numbers', () => {
// ARRANGE — Setup
const a = 2;
const b = 3;
// ACT — die Operation
const result = add(a, b);
// ASSERT — Erwartung
expect(result).toBe(5);
});
```
**Eine Erwartung pro Test** (Faustregel).
---
# Vitest-Matchers
| Matcher | Was |
|---------|-----|
| `.toBe(x)` | strict equal (`===`) |
| `.toEqual(x)` | deep equal (Objects, Arrays) |
| `.toBeNull()` / `.toBeUndefined()` | |
| `.toBeTruthy()` / `.toBeFalsy()` | |
| `.toContain(item)` | Array enthält |
| `.toHaveLength(n)` | |
| `.toThrow()` | wirft Fehler |
| `.toMatchSnapshot()` | Snapshot-Test |
---
<!-- _class: lead -->
# TDD — Test-Driven Development
---
# TDD-Cycle: Red → Green → Refactor
1. **Red:** Test schreiben (schlägt fehl)
2. **Green:** minimaler Code, damit Test besteht
3. **Refactor:** Code säubern, Test bleibt grün
**Kent Beck, 2002.** Steile Lernkurve, aber stark fokussiert.
```javascript
// 1. Red — Test ohne Implementierung
it('isEven returns true for even', () => {
expect(isEven(4)).toBe(true);
});
// → ReferenceError: isEven is not defined
// 2. Green — minimale Lösung
function isEven(n) { return n % 2 === 0; }
// 3. Refactor — Edge Cases, Lesbarkeit
```
---
<!-- _class: lead -->
# Integration-Tests
---
# API testen mit Supertest
```javascript
import request from 'supertest';
import { app } from './app.js';
describe('GET /users', () => {
it('returns users array', async () => {
const res = await request(app).get('/users');
expect(res.status).toBe(200);
expect(res.body).toBeInstanceOf(Array);
});
});
describe('POST /users', () => {
it('creates a user', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'Ada' });
expect(res.status).toBe(201);
expect(res.body.name).toBe('Ada');
});
});
```
---
# Test-Datenbank
```javascript
import { beforeAll, afterEach, afterAll } from 'vitest';
import Database from 'better-sqlite3';
let db;
beforeAll(() => {
db = new Database(':memory:'); // in-RAM, super schnell
db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
});
afterEach(() => {
db.exec('DELETE FROM users'); // sauberer Start
});
afterAll(() => db.close());
```
`:memory:`-DB = jeder Test isoliert, blitzschnell.
---
# Mocking
```javascript
import { vi, it, expect } from 'vitest';
it('calls fetch with right URL', async () => {
const mockFetch = vi.spyOn(global, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ name: 'Ada' })
});
await getUserData(1);
expect(mockFetch).toHaveBeenCalledWith('https://api/users/1');
});
```
**Mock sparsam** — übermockt-Tests testen Mocks, nicht Code.
---
<!-- _class: lead -->
# E2E — End-to-End
---
# Playwright — moderne E2E
```sh
npm init playwright@latest
```
```javascript
import { test, expect } from '@playwright/test';
test('login flow', async ({ page }) => {
await page.goto('http://localhost:3000');
await page.getByLabel('Email').fill('ada@example.com');
await page.getByLabel('Password').fill('secret123');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Hallo Ada')).toBeVisible();
});
```
Browser tatsächlich öffnen + interagieren. **Realer User-Flow.**
---
# Playwright-Locators
```javascript
// Bevorzugt: rollenbasiert (A11y-konform!)
page.getByRole('button', { name: 'Submit' });
page.getByLabel('Username');
page.getByText('Welcome');
// Fallback: CSS-Selektor
page.locator('.submit-btn');
page.locator('#username');
```
**Rollenbasierte Locators** finden Bugs, die A11y betreffen.
---
# E2E vs. Unit — Trade-offs
| | Unit | E2E |
|--|------|-----|
| Geschwindigkeit | ms | s |
| Isolation | hoch | gering |
| Realismus | gering | hoch |
| Wartung | wenig | viel |
| Wann | jede Funktion | kritische Flows |
E2E nicht für jede Detail-Logik — für **happy path + Login + Bezahlung**.
---
<!-- _class: lead -->
# Coverage + CI
---
# Coverage-Report
```sh
npx vitest --coverage
```
```
File | % Stmts | % Branch | % Funcs | Uncovered
-------------+---------+----------+---------+-----------
utils.js | 100 | 100 | 100 |
api.js | 75.3 | 60 | 80 | 42-48
auth.js | 91.2 | 85 | 100 | 15
```
**100 % ≠ keine Bugs.** Aber 0 % = sicher Bugs.
---
# Coverage-Ziel realistisch
| Code-Typ | Realistisch |
|----------|-------------|
| Pure Funktionen | 100 % |
| Business-Logik | 80-90 % |
| API-Routes | 70-80 % |
| UI-Komponenten | 60-70 % |
| Drittpartei-Wrapper | 0 % |
| Konfig-Files | 0 % |
**Faustregel:** über 80 % Gesamt = gut. Unter 50 % = gefährlich.
---
# CI: Tests auf jedem Push
```yaml
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm ci
- run: npm test
- run: npm run test:e2e
```
Tests sind grün → PR mergebar. Sonst: rot, mergen verboten.
---
<!-- _class: aufgabe -->
# Selbstlernen — Tests schreiben
Erweitert eure API:
1. **3+ Unit-Tests** für eure Helper-Funktionen (`utils.test.js`)
2. **3+ Integration-Tests** für eure REST-Endpunkte (mit Supertest)
3. **In-Memory-DB** in `beforeAll`/`afterEach`
4. **1 E2E-Test** mit Playwright (z.B. Login-Flow)
5. **Coverage-Report** generieren
**Bonus:** GitHub-Actions-Workflow für CI.
---
# Zusammenfassung
**Heute gelernt:**
- **Test-Pyramide:** viele Unit, einige Integration, wenige E2E
- **Vitest** als modernes Test-Runner-Setup
- **AAA-Pattern** + Matchers + Lifecycle-Hooks
- **TDD-Cycle** Red → Green → Refactor
- **Supertest** für API-Integration-Tests
- **Playwright** für E2E mit rollenbasierten Locators
- **Coverage** als Werkzeug, nicht als Ziel
- **CI** mit GitHub-Actions
**Nächste Stunde:** TypeScript — Typen über JavaScript.
+360
View File
@@ -0,0 +1,360 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering – DHBW Stuttgart"
footer: "Michael Czechowski – SoSe 2026"
title: TypeScript
---
<style>
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: lead -->
# Kapitel 6 — TypeScript
## Typen über JavaScript
---
# Was ist TypeScript?
**Microsoft, 2012.** Aufsatz auf JavaScript mit **statischer Typprüfung.**
- Schreibst TypeScript (`.ts`)
- Compiler (`tsc`) prüft Typen + erzeugt JavaScript (`.js`)
- Browser/Node führt das JS aus
**Heute:** Standard für mittlere + große JS-Projekte. ~95 % der React-Repos.
---
# Warum TypeScript?
| Vorteil | Was bedeutet das |
|---------|------------------|
| **Fehler vor Laufzeit** | Compiler fängt typos, falsche Aufrufe |
| **Bessere IDE** | Autocompletion, Refactoring, Jump-to-Definition |
| **Selbst-Doku** | Typ-Signaturen sind ausführbare Doku |
| **Refactoring sicher** | Compiler findet alle Auswirkungen |
**Nachteil:** Build-Step + Lernkurve.
---
# Setup
```sh
npm install -D typescript @types/node tsx
npx tsc --init # tsconfig.json erzeugen
```
`tsx` = TypeScript direkt ausführen ohne Compile-Step (Dev).
```json
// package.json
{
"scripts": {
"dev": "tsx --watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js"
}
}
```
---
# tsconfig.json — Wichtigste
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"outDir": "dist",
"rootDir": "src"
}
}
```
**`strict: true`** = alle strengen Checks an. **Pflicht** für neue Projekte.
---
# Basis-Typen
```typescript
let name: string = "Ada";
let age: number = 56;
let admin: boolean = true;
let nothing: null = null;
let unset: undefined = undefined;
let hobbies: string[] = ["math", "chess"];
let tuple: [string, number] = ["Ada", 56];
let anyValue: any = "vermeiden"; // KEIN typ-check
let unknownValue: unknown = await fetch(); // sicherer als any
```
`any` deaktiviert TS-Schutz — vermeiden.
---
# Object-Typen + Interfaces
```typescript
// inline
function greet(user: { name: string; age?: number }) {
return `Hallo ${user.name}`;
}
// Interface
interface User {
name: string;
age?: number; // optional
readonly id: number; // schreibgeschützt
}
const ada: User = { id: 1, name: "Ada", age: 56 };
// Type-Alias (Interface mit anderem Namen, mehr Flexibilität)
type Status = "pending" | "active" | "deleted";
```
---
# Function-Typen
```typescript
// Argument + Return
function add(a: number, b: number): number {
return a + b;
}
// Arrow + ausgeschrieben
const multiply: (a: number, b: number) => number = (a, b) => a * b;
// async-Funktionen geben Promise zurück
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/users/${id}`);
return res.json();
}
```
---
# Union + Intersection
```typescript
// Union — "ODER"
type Id = string | number;
function lookup(id: string | number) { /* ... */ }
// Discriminated Union
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number };
function area(s: Shape) {
if (s.kind === "circle") return Math.PI * s.radius ** 2;
return s.size ** 2; // TS weiß: hier ist's square
}
// Intersection — "UND"
type Admin = User & { permissions: string[] };
```
---
# Generics — typ-flexible Funktionen
```typescript
// T ist ein Platzhalter
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
first([1, 2, 3]); // T = number
first(["a", "b"]); // T = string
// Generic-Constraints
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest("hello", "hi"); // OK
longest([1, 2], [3, 4, 5]); // OK
longest(5, 10); // FEHLER: number hat kein length
```
---
# Built-in Generic-Typen
```typescript
// Array<T> = T[]
const nums: Array<number> = [1, 2, 3];
// Promise<T>
async function getUser(): Promise<User> { /* ... */ }
// Partial<T> — alle Properties optional
type UserUpdate = Partial<User>;
// Required<T> — alle Pflicht
// Pick<T, K> — nur ausgewählte
// Omit<T, K> — ohne ausgewählte
type PublicUser = Omit<User, "password">;
// Record<K, V> — Dictionary
const config: Record<string, string> = { host: "...", port: "8080" };
```
---
# Typ-Inference
TS errät den Typ oft selbst — **nicht überall annotieren.**
```typescript
let name = "Ada"; // automatisch: string
const numbers = [1, 2, 3]; // automatisch: number[]
function double(n: number) { // Return-Typ inferiert: number
return n * 2;
}
// Wann annotieren?
// - Funktion-Args (immer)
// - Public-API Return-Typen (Pflicht für Library)
// - wenn Inference falsch oder unklar
```
---
# Typen für REST-API
```typescript
// Schema einmal definieren
interface CreateUserBody {
name: string;
email: string;
}
interface UserResponse {
id: number;
name: string;
email: string;
createdAt: string;
}
// Express-Route typisiert
import type { Request, Response } from 'express';
app.post('/users', (req: Request<{}, {}, CreateUserBody>, res: Response<UserResponse>) => {
const { name, email } = req.body; // typsicher
// ...
});
```
---
# Zod — Runtime-Validation
TypeScript prüft zur Compile-Time. **Was kommt aus dem Netz?**
```typescript
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
type User = z.infer<typeof UserSchema>; // Typ aus Schema
app.post('/users', (req, res) => {
const result = UserSchema.safeParse(req.body);
if (!result.success) return res.status(400).json(result.error);
const user = result.data; // typsicher + validated
});
```
---
# `any` vs `unknown` vs `never`
```typescript
let a: any = "test";
a.toFixed(); // OK — TS prüft NICHTS
let u: unknown = "test";
u.toFixed(); // FEHLER — erst type-narrow
if (typeof u === 'number') u.toFixed(); // OK
function fail(msg: string): never {
throw new Error(msg); // kommt nie zurück
}
```
**`unknown` statt `any`** — sicherer Default für fremde Daten.
---
# Best Practices
- **`strict: true`** in tsconfig — keine Diskussion
- **Argument-Typen** annotieren, Return-Typen nur public-API
- **Interfaces** für Objekt-Strukturen, **Type-Aliases** für Unions
- **`unknown`** statt `any`
- **Zod / Valibot** für Runtime-Validation
- **Typen nicht doppelt:** ein Schema, davon Typ ableiten (`z.infer`)
- **CI-Step:** `tsc --noEmit` muss grün sein
---
<!-- _class: aufgabe -->
# Selbstlernen — eure API zu TS migrieren
1. **TypeScript installieren:** `npm install -D typescript @types/node @types/express tsx`
2. **tsconfig.json** mit `strict: true`
3. **`.js` → `.ts`** umbenennen, fehlende Typen ergänzen
4. **Zod-Schema** für POST-Bodies
5. **CI-Step:** `tsc --noEmit` in Tests
**Bonus:** Drizzle-ORM verwenden (TypeScript-first).
---
# Zusammenfassung
**Heute gelernt:**
- TS als statischer Typ-Aufsatz auf JS
- **`strict: true`** als Baseline
- Basis-Typen, Interfaces, Type-Aliases
- **Union** + **Generics** als zentrale Konzepte
- Built-in Generics (Partial, Omit, Record)
- **Typ-Inference** nutzen — nicht über-annotieren
- **Zod** für Runtime-Validation
- `unknown` statt `any`
**Nächste Stunde:** Docker — Container für reproducible Setups.
+383
View File
@@ -0,0 +1,383 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering – DHBW Stuttgart"
footer: "Michael Czechowski – SoSe 2026"
title: Docker und Service Orchestration
---
<style>
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: lead -->
# Kapitel 7 — Docker
## Container für reproducible Setups
---
# „Auf meinem Rechner läuft's"
Das klassische Software-Problem:
- Entwickler: Node 22 + Postgres 16 lokal
- Server: Node 18 + Postgres 14
- → bricht beim Deploy
**Lösung:** App + Abhängigkeiten als **Container** verpacken.
---
# Container vs. VM
| | Container | Virtual Machine |
|--|-----------|-----------------|
| Startzeit | ms | min |
| Größe | MB | GB |
| Kernel | shared (Host-Linux) | eigener |
| Isolation | Prozess-Level | Hardware-Level |
| Overhead | minimal | hoch |
Container = leichtgewichtige Prozess-Isolation.
---
# Docker — die Idee
- **Image** = Schnappschuss (App + Files + OS-Layer)
- **Container** = laufende Instanz eines Images
- **Dockerfile** = Anleitung zum Image-Bauen
- **Registry** = Image-Speicher (Docker Hub, GHCR)
```sh
docker pull node:24
docker run node:24 node --version
```
---
# Dockerfile — Hallo Welt
```dockerfile
FROM node:24-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["node", "src/server.js"]
```
```sh
docker build -t mein-app .
docker run -p 3000:3000 mein-app
```
---
# Dockerfile-Schichten verstehen
Jedes `RUN`/`COPY`/`ADD` = neue Schicht.
```dockerfile
# Schlecht — npm install nach JEDER Code-Änderung
COPY . .
RUN npm ci
# Gut — package.json zuerst, npm install gecached
COPY package*.json ./
RUN npm ci
COPY . .
```
**Build-Cache** spart Minuten.
---
# Multi-Stage Build
```dockerfile
# Stage 1: Build mit allem
FROM node:24-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
# Stage 2: nur Runtime
FROM node:24-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]
```
Finales Image: kleiner, sicherer (kein Build-Tools drin).
---
# Image-Größen reduzieren
| Base | Größe |
|------|------:|
| `node:24` | ~1.1 GB |
| `node:24-slim` | ~280 MB |
| `node:24-alpine` | **~180 MB** |
| Distroless | ~150 MB |
**`alpine`** = Alpine Linux, sehr klein. **Distroless** = nur Runtime, kein Shell.
---
# .dockerignore
Verhindert dass Müll ins Image kommt:
```
node_modules
.git
.env
*.log
build/
dist/
.DS_Store
README.md
.github/
```
Schneller Build, kleineres Image, weniger Secrets im Image.
---
<!-- _class: lead -->
# docker compose
---
# Multi-Container — die Realität
Eine App braucht oft:
- App-Server
- Datenbank
- Cache (Redis)
- Reverse Proxy
**docker compose** orchestriert das.
---
# compose.yml — Beispiel
```yaml
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://user:pass@db:5432/app
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:
```
---
# compose-Commands
```sh
docker compose up # alles starten (Foreground)
docker compose up -d # detached (background)
docker compose down # alles stoppen + entfernen
docker compose down -v # ... und Volumes löschen
docker compose logs -f app # Logs streamen
docker compose exec app sh # Shell im laufenden Container
docker compose ps # Status sehen
```
---
# Networking in compose
Standard: alle Services im selben `default`-Netzwerk.
```yaml
services:
app:
environment:
# `db` ist der Service-Name → DNS-Hostname
DATABASE_URL: postgres://...@db:5432/app
```
**Service-Namen werden zu Hostnamen.** Kein localhost zwischen Containern.
---
# Volumes — Daten überleben
```yaml
volumes:
db-data: # named volume (managed by Docker)
services:
db:
volumes:
- db-data:/var/lib/postgresql/data # named
- ./backups:/backups # bind mount
```
**Named Volumes** für DB-Daten. **Bind Mounts** für lokale Config.
---
# Environment + Secrets
```yaml
services:
app:
env_file: .env # ganze .env-Datei laden
environment:
NODE_ENV: production # einzeln setzen
DATABASE_URL: ${DATABASE_URL} # vom Host
```
**Secrets nicht ins compose.yml.** Lieber `.env` (gitignored) oder Docker Secrets.
---
<!-- _class: lead -->
# CI/CD
---
# Docker in GitHub Actions
```yaml
name: Build + Push
on:
push:
branches: [main]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: user/app:latest
```
---
# Container-Registry
| Registry | Stärke |
|----------|--------|
| **Docker Hub** | Standard, kostenlos für Public |
| **GHCR** (GitHub) | mit Repo verbunden |
| **GitLab Registry** | mit Repo verbunden |
| **AWS ECR** | für AWS-Hosting |
```sh
# Tag + Push
docker tag mein-app ghcr.io/user/mein-app:v1.0.0
docker push ghcr.io/user/mein-app:v1.0.0
```
---
# Health-Checks
```dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
```
```javascript
// In Express
app.get('/health', (req, res) => res.json({ ok: true }));
```
Orchestrators (k8s, ECS, Coolify) entscheiden via HealthCheck, ob neu starten.
---
# Best Practices
- **`alpine`** oder **distroless** als Base
- **Multi-stage builds** für kleinere Images
- **`.dockerignore`** anlegen
- **Non-root user** im Container (`USER node`)
- **`COPY` nach `RUN npm ci`** für Cache-Hit
- **Tags statt `latest`** für Reproducibility
- **Health-Checks** für Orchestration
- **Secrets via env, niemals im Image**
---
<!-- _class: aufgabe -->
# Selbstlernen — App dockerizen
1. **Dockerfile** für eure Node-API (multi-stage)
2. **.dockerignore** mit node_modules, .env, .git
3. **compose.yml** mit App + Postgres
4. **`docker compose up`** lokal starten
5. **Health-Check** auf `/health`-Endpoint
**Bonus:** GitHub-Actions-Workflow, der Image bei jedem Push baut.
---
# Zusammenfassung
**Heute gelernt:**
- Container vs. VM — Prozess-Isolation, leicht + schnell
- **Dockerfile** mit `FROM`, `COPY`, `RUN`, `CMD`
- **Multi-stage Builds** für kleine Production-Images
- **alpine** + Distroless als Base
- **docker compose** für Multi-Container-Setups
- **Volumes** für persistente Daten
- **Service-Namen als Hostnamen** im compose-Netzwerk
- **CI/CD** mit GitHub Actions + Registry
**Nächste Stunde:** Best Practices — Git, README, SOLID, 12-Factor.
+477
View File
@@ -0,0 +1,477 @@
---
marp: true
theme: gaia
paginate: true
backgroundColor: #fff
header: "Web Engineering – DHBW Stuttgart"
footer: "Michael Czechowski – SoSe 2026"
title: Best Practices
---
<style>
:root { --color-foreground: #1a1a2e; --color-highlight: #d63384; }
section.invert { --color-foreground: #fff; }
section { font-size: 1.4rem; }
h1 { color: #a02060; }
section.invert h1 { color: #fff; }
h2 { color: #1f2937; }
pre { background: #0f0f23; color: #f48fb1; border-radius: 8px; border-left: 3px solid #d63384; }
pre code { background: transparent; color: inherit; }
code { background: #1a1a2e; color: #f48fb1; padding: 0.15em 0.4em; border-radius: 4px; }
a { color: var(--color-highlight); }
</style>
<!-- _class: lead -->
# Kapitel 8 — Best Practices
## Git · README · SOLID · 12-Factor · SemVer
---
<!-- _class: lead -->
# Git-Hygiene
---
# Sinnvolle Commits
**Schlecht:**
```
fix
wip
asdf
final
final2
final-final
```
**Gut:**
```
fix(auth): handle expired JWT correctly
feat(api): add /users/:id/posts endpoint
docs(readme): update Docker setup instructions
test(utils): cover edge cases in greet()
```
**Eine Commit-Message = Was + Warum, nicht Wie.**
---
# Conventional Commits
```
<type>(<scope>): <subject>
[optional body]
[optional footer]
```
**Types:**
- `feat:` neue Funktion
- `fix:` Bugfix
- `docs:` Doku
- `refactor:` Code-Restrukturierung
- `test:` Tests
- `chore:` Build/Tools
- `perf:` Performance
- `style:` Formatierung
Automatisches Changelog + SemVer-Bump möglich.
---
# .gitignore — was nie rein gehört
```
# Dependencies + Build
node_modules/
dist/
build/
.cache/
# Secrets
.env
.env.local
*.pem *.key
# Editor + OS
.vscode/ .idea/
.DS_Store
# Logs
*.log
```
Vorlagen: [github.com/github/gitignore](https://github.com/github/gitignore)
---
# Branches + PRs
```sh
# Neuer Feature-Branch
git checkout -b feat/user-profile
# Arbeit, Commits
git add .
git commit -m "feat(profile): add avatar upload"
# Push + PR
git push -u origin feat/user-profile
# → PR via GitHub/Gitea-UI
# Nach Merge: aufräumen
git checkout main
git pull
git branch -d feat/user-profile
```
**Faustregel:** kurzlebige Branches (Tage, nicht Monate).
---
<!-- _class: lead -->
# README
---
# README — das Visitenkarten-Dokument
Wenn jemand euer Projekt im Browser sieht: **die README ist die erste und oft einzige Berührung.**
Schlechte README = Repo nicht verwendet.
**Eine gute README beantwortet:**
1. Was tut das Projekt?
2. Wie installiere ich es?
3. Wie starte ich es?
4. Wie teste ich es?
5. Wie deploye ich es?
6. Lizenz?
---
# README-Template
```markdown
# Mein Projekt
> Eine Zeile Beschreibung.
## Features
- ✓ etwas
- ✓ etwas anderes
## Setup
```sh
git clone https://...
cd projekt
cp .env.example .env
npm install
npm run dev
```
## Tests
```sh
npm test
npm run test:e2e
```
## Stack
Node 24, Express, SQLite, Vitest.
## License
MIT
```
---
<!-- _class: lead -->
# SOLID — OO-Prinzipien
---
# SOLID — die fünf
| | Prinzip | Bedeutung |
|--|---------|-----------|
| **S** | Single Responsibility | jede Klasse/Modul = eine Verantwortung |
| **O** | Open / Closed | offen für Erweiterung, geschlossen für Änderung |
| **L** | Liskov Substitution | Subtyp ersetzbar durch Basistyp |
| **I** | Interface Segregation | kleine, fokussierte Interfaces |
| **D** | Dependency Inversion | abhängig von Abstraktionen |
Robert C. Martin („Uncle Bob"), 2000er.
---
# S — Single Responsibility
```javascript
// Schlecht — alles in einer Klasse
class UserManager {
validate() { /* ... */ }
saveToDb() { /* ... */ }
sendEmail() { /* ... */ }
generateReport() { /* ... */ }
}
// Besser — getrennte Verantwortlichkeiten
class UserValidator { /* ... */ }
class UserRepository { /* ... */ }
class EmailService { /* ... */ }
class ReportGenerator { /* ... */ }
```
---
# D — Dependency Inversion
```javascript
// Schlecht — direkt von konkreter Klasse
class OrderService {
charge(order) {
const stripe = new StripeClient();
stripe.charge(order.amount);
}
}
// Besser — Abhängigkeit injizieren
class OrderService {
constructor(paymentProvider) {
this.payment = paymentProvider;
}
charge(order) {
this.payment.charge(order.amount);
}
}
new OrderService(new StripeClient()); // im Production
new OrderService(new MockClient()); // im Test
```
---
<!-- _class: lead -->
# 12-Factor App
---
# 12-Factor — die wichtigsten
| # | Faktor | Was |
|---|--------|-----|
| 1 | Codebase | ein Repo pro App, mehrere Deploys |
| 2 | Dependencies | explizit deklariert (package.json) |
| 3 | **Config** | über Environment Variables, nicht im Code |
| 4 | Backing Services | als Resources behandeln (z.B. DB-URL) |
| 6 | **Processes** | stateless |
| 9 | Disposability | schnell startbar + stoppbar |
| 12 | Admin Processes | als One-Off im selben Stack |
Heroku, 2011. Heute Industrie-Standard für Cloud-Apps.
---
# Factor 3 — Config in env
```javascript
// SCHLECHT — Hardcoded
const db = new Pool({ host: 'localhost', user: 'dev' });
// GUT — aus env
const db = new Pool({
connectionString: process.env.DATABASE_URL,
});
```
`.env` lokal, **Environment-Variablen** auf Server. Gleicher Code überall.
---
# Factor 6 — Stateless Processes
**Schlecht:** Session-State im RAM.
```javascript
const sessions = new Map(); // pro Server-Instanz!
```
→ Load-Balancer mit 2 Servern: User-Session geht verloren beim 2. Request.
**Gut:** State in DB / Redis.
```javascript
await redis.set(`session:${id}`, JSON.stringify(data));
```
Server-Restart → kein Datenverlust. Horizontale Skalierung möglich.
---
<!-- _class: lead -->
# SemVer — semantisches Versioning
---
# SemVer — Format
```
MAJOR.MINOR.PATCH
1 . 4 . 3
```
| Teil | Bump bei |
|------|----------|
| **MAJOR** | breaking change (API-incompatible) |
| **MINOR** | neues Feature (rückwärts-kompatibel) |
| **PATCH** | Bugfix (rückwärts-kompatibel) |
**Pre-Release:** `1.0.0-beta.1`, `1.0.0-rc.2`.
---
# SemVer in package.json
```json
{
"dependencies": {
"express": "^5.0.0", // ≥ 5.0.0, < 6.0.0
"lodash": "~4.17.0", // ≥ 4.17.0, < 4.18.0
"react": "5.2.1", // exakt
"vite": "*" // jede (vermeiden)
}
}
```
**Faustregel:** `^` (Caret) ist Default — major-version-lock.
---
<!-- _class: lead -->
# Code-Reviews
---
# Constructive Code-Reviews
**Geben:**
- Konkret: Zeilennummern + Beispiel
- Wertschätzend: gut gemachte Stellen erwähnen
- Fragend statt fordernd: „Wäre `X` hier robuster?"
- Klar zwischen MUST/SHOULD/CONSIDER unterscheiden
**Nehmen:**
- Egoless: Code ≠ Person
- Frage zurück bei Unklarheit
- Bedanken für detaillierte Feedback
---
# Code-Review-Checkliste
| Bereich | Was prüfen |
|---------|------------|
| **Correctness** | macht's, was es soll? Edge-cases? |
| **Readability** | Namen, Struktur, Kommentare? |
| **Tests** | abgedeckt? Sinnvolle Assertions? |
| **Security** | Input-Validation, Secrets, Auth |
| **Performance** | N+1, Sync-IO, Memory-Leak |
| **A11y** (Frontend) | Semantik, Kontrast, Keyboard |
| **Doku** | README aktualisiert? CHANGELOG? |
---
<!-- _class: lead -->
# Doku lesen lernen
---
# Wo finde ich was?
| Ziel | Quelle |
|------|--------|
| Web-APIs (HTML, CSS, JS) | **MDN** — developer.mozilla.org |
| Node-APIs | **nodejs.org/docs** |
| npm-Package | `npmjs.com/package/<name>` + GitHub README |
| Sprach-Standards (ES) | **tc39.es** |
| Browser-Kompatibilität | **caniuse.com** |
| Fehler-Suche | Stack Overflow + GitHub Issues |
**Faustregel:** offizielle Doku zuerst, dann Stack Overflow.
---
# Stack Overflow gut nutzen
**Bevor du fragst:**
- Existiert die Frage schon? (Suche oben)
- Gibt es ein Minimal-Reproduction-Beispiel?
- Welche Versionen (Node, OS, Library)?
- Was hast du schon versucht?
**Bei der Frage:**
- Konkreter Titel (nicht "JavaScript Hilfe")
- Code-Block für Code
- Was war erwartet, was passiert
- Error-Message als Text (nicht Screenshot)
---
# AI-Assistenten (ChatGPT, Claude, Copilot)
**Pro:**
- Schneller Antworten zu allgemeinen Fragen
- Boilerplate-Code in Sekunden
- Erklärung fremden Codes
**Contra:**
- Halluziniert APIs, die nicht existieren
- Verstärkt Anti-Patterns aus dem Training
- Lernkurve geht zurück, wenn nur kopiert wird
**Faustregel:** als Sparring-Partner, nicht als Wahrheitsquelle. **Verstehen, nicht kopieren.**
---
<!-- _class: aufgabe -->
# Selbstlernen — Projekt-Hygiene-Check
Geht euer Projekt durch:
1. **README:** hat alle 6 Sektionen?
2. **.gitignore:** komplett, keine Secrets im Repo?
3. **Conventional Commits:** letzte 10 Commits durchgehen
4. **package.json:** `version`, `description`, `scripts`?
5. **Tests:** Coverage > 70 %?
6. **Dockerfile + compose.yml:** funktionieren?
7. **Environment:** `.env.example` vorhanden, `.env` gitignored?
**Bonus:** GitHub-Actions-Workflow für Tests + Build.
---
# Abschluss
**Über 8 Termine gelernt:**
1. Web Engineering — Foundation
2. CSS Extended — Layout + Animation
3. Node.js Basics — Runtime + REST
4. Node.js Advanced — async + DB + Auth
5. Testing — Unit + Integration + E2E
6. TypeScript — Typen + Schemas
7. Docker — Container + compose
8. Best Practices — Git + README + SOLID + 12-Factor
**Nächste Schritte:** eigenes Projekt für die Prüfungsleistung. Code-Abgabe + Präsentation.
View File
Whitespace-only changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,146 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Browser–Server HTTP Sequenz</title>
<style>
:root {
--dark: #1a1a2e;
--muted: #6b7280;
--border: #cbd5e1;
--bg-soft: #f8fafc;
--hl: #005f8a;
--req: #2563eb;
--res: #16a34a;
}
html, body { margin: 0; padding: 0; background: #fff; min-height: 100vh; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
body { padding: 24px 32px; min-height: 100vh; display: flex; flex-direction: column; justify-content: center; }
h1 { margin: 0 0 4px; font-size: 1.45rem; text-align: center; }
.sub { color: var(--muted); margin-bottom: 24px; font-size: 0.88rem; text-align: center; }
.diagram { display: grid; grid-template-columns: 140px 1fr 140px; gap: 0; align-items: start; }
/* Actors */
.actor {
display: flex; flex-direction: column; align-items: center; gap: 6px;
font-weight: 700; font-size: 0.9rem;
}
.actor-box {
border: 2px solid var(--dark); border-radius: 8px;
padding: 8px 18px; background: var(--bg-soft);
white-space: nowrap;
}
.lifeline {
width: 2px; background: var(--border);
height: 100%; min-height: 280px;
margin: 0 auto;
}
/* Messages */
.messages { display: flex; flex-direction: column; gap: 0; padding-top: 48px; }
.msg { display: flex; flex-direction: column; margin-bottom: 6px; }
.msg-arrow {
display: flex; align-items: center; gap: 0; position: relative;
}
.msg-line {
flex: 1; height: 2px;
}
.msg-line.req { background: var(--req); }
.msg-line.res { background: var(--res); background: repeating-linear-gradient(90deg, var(--res) 0 8px, transparent 8px 13px); }
.arrowhead-r { width: 0; height: 0; border-top: 6px solid transparent; border-bottom: 6px solid transparent; border-left: 10px solid var(--req); }
.arrowhead-l { width: 0; height: 0; border-top: 6px solid transparent; border-bottom: 6px solid transparent; border-right: 10px solid var(--res); }
.msg-label {
font-size: 0.75rem; font-family: ui-monospace, monospace;
padding: 2px 0 6px; text-align: center;
}
.msg-label.req { color: var(--req); }
.msg-label.res { color: var(--res); }
.gap { height: 10px; }
.legend {
margin-top: 20px; display: flex; gap: 24px; justify-content: center;
font-size: 0.82rem;
}
.leg { display: flex; align-items: center; gap: 8px; }
.leg-line { width: 28px; height: 2px; }
.leg-line.req { background: var(--req); }
.leg-line.res { background: repeating-linear-gradient(90deg, var(--res) 0 6px, transparent 6px 10px); }
</style>
</head>
<body>
<h1>Browser–Server: HTTP Kommunikation</h1>
<p class="sub">Jede Ressource = eigener Request — Browser blockiert nicht, lädt parallel</p>
<div class="diagram">
<!-- Browser actor -->
<div class="actor">
<div class="actor-box">🌐 Browser</div>
<div class="lifeline"></div>
</div>
<!-- Messages -->
<div class="messages">
<div class="msg">
<div class="msg-arrow"><div class="msg-line req"></div><div class="arrowhead-r"></div></div>
<div class="msg-label req">GET /products</div>
</div>
<div class="msg">
<div class="msg-arrow"><div class="arrowhead-l"></div><div class="msg-line res"></div></div>
<div class="msg-label res">HTTP 200 + index.html</div>
</div>
<div class="gap"></div>
<div class="msg">
<div class="msg-arrow"><div class="msg-line req"></div><div class="arrowhead-r"></div></div>
<div class="msg-label req">GET /style.css</div>
</div>
<div class="msg">
<div class="msg-arrow"><div class="arrowhead-l"></div><div class="msg-line res"></div></div>
<div class="msg-label res">HTTP 200 + style.css</div>
</div>
<div class="gap"></div>
<div class="msg">
<div class="msg-arrow"><div class="msg-line req"></div><div class="arrowhead-r"></div></div>
<div class="msg-label req">GET /script.js</div>
</div>
<div class="msg">
<div class="msg-arrow"><div class="arrowhead-l"></div><div class="msg-line res"></div></div>
<div class="msg-label res">HTTP 200 + script.js</div>
</div>
<div class="gap"></div>
<div class="msg">
<div class="msg-arrow"><div class="msg-line req"></div><div class="arrowhead-r"></div></div>
<div class="msg-label req">GET /api/products</div>
</div>
<div class="msg">
<div class="msg-arrow"><div class="arrowhead-l"></div><div class="msg-line res"></div></div>
<div class="msg-label res">HTTP 200 + JSON</div>
</div>
</div>
<!-- Server actor -->
<div class="actor">
<div class="actor-box">🖥️ Server</div>
<div class="lifeline"></div>
</div>
</div>
<div class="legend">
<div class="leg"><div class="leg-line req"></div><span style="color:var(--req)">Request (Browser → Server)</span></div>
<div class="leg"><div class="leg-line res"></div><span style="color:var(--res)">Response (Server → Browser)</span></div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Docker: Service Orchestration</title>
<style>
:root {
--dark: #1a1a2e;
--muted: #6b7280;
--border: #cbd5e1;
--bg-soft: #f8fafc;
--blue: #2563eb;
--red: #dc2626;
}
html, body { margin: 0; padding: 0; background: #fff; min-height: 100vh; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
body { padding: 28px 36px; min-height: 100vh; display: flex; flex-direction: column; justify-content: center; }
h1 { margin: 0 0 4px; font-size: 1.45rem; }
.sub { color: var(--muted); margin-bottom: 28px; font-size: 0.88rem; }
.diagram { display: flex; align-items: center; gap: 0; }
.node {
display: flex; flex-direction: column; align-items: center; justify-content: center;
border: 2px solid var(--dark); border-radius: 8px;
padding: 12px 16px; background: var(--bg-soft);
font-weight: 700; font-size: 0.9rem; text-align: center; min-width: 90px;
}
.node .sub-label { font-size: 0.72rem; font-weight: 400; color: var(--muted); margin-top: 3px; }
.client { border-radius: 50%; width: 72px; height: 72px; padding: 0; border-color: var(--red); }
.proxy { background: #eff6ff; border-color: var(--blue); min-width: 100px; }
.docker-box {
border: 2px dashed var(--blue); border-radius: 12px;
padding: 16px 20px; display: flex; align-items: center; gap: 0;
background: #f0f7ff;
position: relative;
}
.docker-label {
position: absolute; top: -11px; left: 14px;
background: #fff; padding: 0 6px;
font-size: 0.72rem; font-weight: 700; color: var(--blue);
text-transform: uppercase; letter-spacing: 0.06em;
}
.services { display: flex; flex-direction: column; gap: 12px; }
.db-col { display: flex; flex-direction: column; align-items: center; justify-content: center; }
/* arrows */
.arr { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 0 6px; }
.arr-h { display: flex; flex-direction: column; gap: 3px; align-items: center; }
.line { height: 2px; width: 44px; }
.line.blue { background: var(--blue); }
.line.red { background: var(--red); }
.line.dashed-blue { background: repeating-linear-gradient(90deg, var(--blue) 0 6px, transparent 6px 10px); }
.line.dashed-red { background: repeating-linear-gradient(90deg, var(--red) 0 6px, transparent 6px 10px); }
.arrowhead-r::after { content: '▶'; font-size: 0.65rem; color: inherit; }
.arrowhead-l::before { content: '◀'; font-size: 0.65rem; color: inherit; }
.arr-label { font-size: 0.65rem; color: var(--muted); white-space: nowrap; }
.arr-v { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 2px 0; }
.line-v { width: 2px; height: 28px; }
.line-v.blue { background: var(--blue); }
.line-v.dashed-blue { background: repeating-linear-gradient(180deg, var(--blue) 0 5px, transparent 5px 9px); }
.arr-v .arrowhead-d::after { content: '▼'; font-size: 0.65rem; color: var(--blue); }
.gap-col { display: flex; flex-direction: column; gap: 12px; align-items: center; justify-content: center; }
</style>
</head>
<body>
<h1>Docker: Service Orchestration</h1>
<p class="sub">Client kommuniziert nur mit dem Reverse Proxy — interne Services bleiben isoliert</p>
<div class="diagram">
<!-- Client -->
<div class="node client" style="color:var(--red); border-color:var(--red);">
Client
<div class="sub-label">Browser /<br>Web App</div>
</div>
<!-- arrows client → proxy -->
<div class="arr">
<div class="arr-h" style="color:var(--red)">
<div style="display:flex;align-items:center;gap:2px">
<div class="line red" style="width:36px"></div>
<span class="arrowhead-r" style="color:var(--red)"></span>
</div>
<div class="arr-label">localhost:10007</div>
</div>
<div style="height:8px"></div>
<div class="arr-h" style="color:var(--red)">
<div style="display:flex;align-items:center;gap:2px">
<span class="arrowhead-l" style="color:var(--red)"></span>
<div class="line dashed-red" style="width:36px"></div>
</div>
<div class="arr-label">response</div>
</div>
</div>
<!-- Docker compose boundary -->
<div class="docker-box">
<div class="docker-label">docker compose</div>
<!-- Reverse Proxy -->
<div class="node proxy">
Reverse<br>Proxy
<div class="sub-label">:10007</div>
</div>
<!-- arrows proxy → services -->
<div class="gap-col" style="padding: 0 8px;">
<div class="arr-h" style="color:var(--blue)">
<div style="display:flex;align-items:center;gap:2px">
<div class="line blue" style="width:28px"></div>
<span class="arrowhead-r" style="color:var(--blue)"></span>
</div>
<div class="arr-label">/api/*</div>
</div>
<div style="height:8px"></div>
<div class="arr-h" style="color:var(--blue)">
<div style="display:flex;align-items:center;gap:2px">
<div class="line blue" style="width:28px"></div>
<span class="arrowhead-r" style="color:var(--blue)"></span>
</div>
<div class="arr-label">/*</div>
</div>
</div>
<!-- Services column -->
<div class="services">
<div class="node" style="background:#dbeafe; border-color:var(--blue);">
Express API
<div class="sub-label">:3000</div>
</div>
<div class="node" style="background:#dcfce7; border-color:#16a34a;">
Web App
<div class="sub-label">:5173</div>
</div>
</div>
<!-- arrows → DB -->
<div class="gap-col" style="padding: 0 6px;">
<div class="arr-h" style="color:var(--blue)">
<div style="display:flex;align-items:center;gap:2px">
<div class="line dashed-blue" style="width:24px"></div>
<span class="arrowhead-r" style="color:var(--blue)"></span>
</div>
<div class="arr-label">:5432</div>
</div>
<div style="height:8px"></div>
<!-- web app does not connect to DB -->
<div style="width:48px"></div>
</div>
<!-- DB -->
<div class="db-col">
<div class="node" style="background:#fef9c3; border-color:#ca8a04; min-width:70px;">
DB
<div class="sub-label">PostgreSQL</div>
</div>
</div>
</div><!-- /docker-box -->
</div><!-- /diagram -->
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>JavaScript Frameworks – Übersicht</title>
<style>
:root {
--dark: #1a1a2e;
--muted: #6b7280;
}
html, body { margin: 0; padding: 0; background: #fff; min-height: 100vh; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
body { padding: 24px 28px; min-height: 100vh; display: flex; flex-direction: column; justify-content: center; }
h1 { margin: 0 0 4px; font-size: 1.45rem; text-align: center; }
.sub { color: var(--muted); margin-bottom: 24px; font-size: 0.88rem; text-align: center; }
.grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; }
.col {
border-radius: 12px; padding: 18px 12px 16px;
display: flex; flex-direction: column; gap: 10px;
}
.col.active { outline: 2px solid var(--dark); outline-offset: 2px; }
.col-title {
font-size: 0.78rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--dark); text-align: center;
border-bottom: 1px solid rgba(0,0,0,0.12); padding-bottom: 8px; margin-bottom: 2px;
}
.items { display: flex; flex-direction: column; gap: 6px; }
.item {
display: flex; align-items: center; gap: 7px;
font-size: 0.82rem; font-weight: 600;
}
.item .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.item .note { font-size: 0.7rem; font-weight: 400; color: var(--muted); display: block; }
.css-col { background: #f1f5f9; }
.fe-col { background: #fce7f3; }
.render-col { background: #ede9fe; }
.build-col { background: #dbeafe; }
.be-col { background: #d1fae5; }
</style>
</head>
<body>
<h1>JavaScript Frameworks – Übersicht</h1>
<p class="sub">Kategorien des modernen JS-Ökosystems</p>
<div class="grid">
<div class="col css-col">
<div class="col-title">CSS Frameworks</div>
<div class="items">
<div class="item"><div class="dot" style="background:#38bdf8"></div><span>Tailwind CSS<span class="note">Utility-first</span></span></div>
<div class="item"><div class="dot" style="background:#7c3aed"></div><span>Bootstrap<span class="note">Komponenten</span></span></div>
<div class="item"><div class="dot" style="background:#e879f9"></div><span>Pico CSS<span class="note">Classless</span></span></div>
<div class="item"><div class="dot" style="background:#f97316"></div><span>UnoCSS<span class="note">Atomic</span></span></div>
</div>
</div>
<div class="col fe-col">
<div class="col-title">Frontend Frameworks</div>
<div class="items">
<div class="item"><div class="dot" style="background:#61dafb"></div><span>React<span class="note">Meta / Virtual DOM</span></span></div>
<div class="item"><div class="dot" style="background:#ff3e00"></div><span>Svelte<span class="note">Compiler-based</span></span></div>
<div class="item"><div class="dot" style="background:#42b883"></div><span>Vue<span class="note">Progressive</span></span></div>
<div class="item"><div class="dot" style="background:#dd0031"></div><span>Angular<span class="note">Google / Full</span></span></div>
</div>
</div>
<div class="col render-col">
<div class="col-title">Rendering Frameworks</div>
<div class="items">
<div class="item"><div class="dot" style="background:#000"></div><span>Next.js<span class="note">React / SSR</span></span></div>
<div class="item"><div class="dot" style="background:#ff3e00"></div><span>SvelteKit<span class="note">Svelte / SSR</span></span></div>
<div class="item"><div class="dot" style="background:#42b883"></div><span>Nuxt<span class="note">Vue / SSR</span></span></div>
<div class="item"><div class="dot" style="background:#ff5d01"></div><span>Astro<span class="note">Islands Arch.</span></span></div>
<div class="item"><div class="dot" style="background:#0ea5e9"></div><span>Gatsby<span class="note">React / SSG</span></span></div>
</div>
</div>
<div class="col build-col">
<div class="col-title">Build Tools &amp; Bundler</div>
<div class="items">
<div class="item"><div class="dot" style="background:#646cff"></div><span>Vite<span class="note">Dev-Server / HMR</span></span></div>
<div class="item"><div class="dot" style="background:#2b3a42"></div><span>Webpack<span class="note">Bundler</span></span></div>
<div class="item"><div class="dot" style="background:#f9b640"></div><span>esbuild<span class="note">Go / ultra-fast</span></span></div>
<div class="item"><div class="dot" style="background:#3178c6"></div><span>tsc<span class="note">TypeScript</span></span></div>
<div class="item"><div class="dot" style="background:#cc3534"></div><span>Rollup<span class="note">Libraries</span></span></div>
</div>
</div>
<div class="col be-col active">
<div class="col-title">Backend Frameworks</div>
<div class="items">
<div class="item"><div class="dot" style="background:#68a063"></div><span>Express<span class="note">Minimal / de-facto</span></span></div>
<div class="item"><div class="dot" style="background:#3178c6"></div><span>Fastify<span class="note">Performant</span></span></div>
<div class="item"><div class="dot" style="background:#5a45ff"></div><span>AdonisJS<span class="note">Full-stack</span></span></div>
<div class="item"><div class="dot" style="background:#e11d48"></div><span>NestJS<span class="note">Angular-style</span></span></div>
<div class="item"><div class="dot" style="background:#000"></div><span>Hono<span class="note">Edge-ready</span></span></div>
</div>
</div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Node.js – Einsatzgebiete</title>
<style>
:root {
--dark: #1a1a2e;
--muted: #6b7280;
}
html, body { margin: 0; padding: 0; background: #fff; min-height: 100vh; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
body { padding: 28px 32px; min-height: 100vh; display: flex; flex-direction: column; justify-content: center; }
h1 { margin: 0 0 4px; font-size: 1.45rem; text-align: center; }
.sub { color: var(--muted); margin-bottom: 28px; font-size: 0.88rem; text-align: center; }
.grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.card {
border-radius: 12px; padding: 28px 16px 24px;
display: flex; flex-direction: column; align-items: center; gap: 14px;
text-align: center;
}
.card .icon { font-size: 2rem; line-height: 1; }
.card .title { font-size: 1.1rem; font-weight: 700; }
.card .desc { font-size: 0.8rem; color: var(--muted); line-height: 1.5; }
.card .tags { display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; margin-top: 4px; }
.tag { font-size: 0.72rem; background: rgba(0,0,0,0.07); border-radius: 99px; padding: 2px 9px; font-family: ui-monospace, monospace; }
.cli { background: #fce7f3; }
.api { background: #ede9fe; }
.web { background: #dbeafe; }
.ws { background: #d1fae5; }
</style>
</head>
<body>
<h1>Node.js – Einsatzgebiete</h1>
<p class="sub">Vier Hauptkategorien serverseitiger JavaScript-Anwendungen</p>
<div class="grid">
<div class="card cli">
<div class="icon">⌨️</div>
<div class="title">CLI</div>
<div class="desc">Kommandozeilen-Tools, Skripte, Build-Prozesse, Automatisierung</div>
<div class="tags">
<span class="tag">npm scripts</span>
<span class="tag">process.argv</span>
<span class="tag">fs</span>
</div>
</div>
<div class="card api">
<div class="icon">🔌</div>
<div class="title">API Server</div>
<div class="desc">REST- und GraphQL-APIs, JSON-Endpunkte, CRUD-Operationen</div>
<div class="tags">
<span class="tag">express</span>
<span class="tag">GET/POST</span>
<span class="tag">JSON</span>
</div>
</div>
<div class="card web">
<div class="icon">🌐</div>
<div class="title">Web Server</div>
<div class="desc">Statische Dateien ausliefern, Server-Side Rendering, Reverse Proxy</div>
<div class="tags">
<span class="tag">http</span>
<span class="tag">SSR</span>
<span class="tag">next.js</span>
</div>
</div>
<div class="card ws">
<div class="icon">⚡</div>
<div class="title">Web Sockets</div>
<div class="desc">Echtzeit-Kommunikation, Chat, Live-Updates, bidirektional</div>
<div class="tags">
<span class="tag">ws</span>
<span class="tag">socket.io</span>
<span class="tag">events</span>
</div>
</div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Test Automation Trophy</title>
<style>
:root {
--dark: #1a1a2e;
--muted: #6b7280;
--border: #cbd5e1;
--bg-soft: #f8fafc;
}
html, body { margin: 0; padding: 0; background: #fff; min-height: 100vh; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
body { padding: 28px 32px; display: flex; flex-direction: column; align-items: center; }
h1 { margin: 0 0 4px; font-size: 1.55rem; text-align: center; }
.sub { color: var(--muted); margin-bottom: 28px; font-size: 0.92rem; text-align: center; }
.trophy { display: flex; flex-direction: column; align-items: center; gap: 0; width: 480px; }
.layer {
display: flex; align-items: center; justify-content: center;
font-weight: 700; font-size: 1.05rem; color: var(--dark);
border-radius: 6px; transition: opacity 0.2s;
}
.layer .count {
font-size: 0.78rem; font-weight: 400; color: var(--muted);
margin-left: 8px;
}
.e2e { background: #fca5a5; width: 40%; height: 54px; clip-path: polygon(10% 100%, 90% 100%, 100% 0%, 0% 0%); }
.integration{ background: #60a5fa; width: 80%; height: 86px; }
.unit { background: #c4b5fd; width: 40%; height: 54px; clip-path: polygon(0% 100%, 100% 100%, 85% 0%, 15% 0%); }
.static { background: #fde68a; width: 28%; height: 60px; clip-path: polygon(0% 0%, 100% 0%, 80% 100%, 20% 100%); }
.legend {
margin-top: 28px; display: grid; grid-template-columns: 1fr 1fr;
gap: 8px 24px; font-size: 0.88rem; width: 480px;
}
.leg { display: flex; align-items: center; gap: 10px; }
.swatch { width: 16px; height: 16px; border-radius: 3px; flex-shrink: 0; }
.leg strong { display: block; }
.leg span { color: var(--muted); font-size: 0.82rem; }
</style>
</head>
<body>
<h1>Test Automation Trophy</h1>
<p class="sub">Schicht-Modell nach Kent C. Dodds — Breite = empfohlener Anteil</p>
<div class="trophy">
<div class="layer e2e">E2E</div>
<div class="layer integration">Integration</div>
<div class="layer unit">Unit</div>
<div class="layer static">Static</div>
</div>
<div class="legend">
<div class="leg"><div class="swatch" style="background:#fde68a"></div><div><strong>Static</strong><span>Linting, Type-Check, Format</span></div></div>
<div class="leg"><div class="swatch" style="background:#c4b5fd"></div><div><strong>Unit</strong><span>Einzelne Funktionen / Komponenten</span></div></div>
<div class="leg"><div class="swatch" style="background:#60a5fa"></div><div><strong>Integration</strong><span>Zusammenspiel mehrerer Teile ← Fokus</span></div></div>
<div class="leg"><div class="swatch" style="background:#fca5a5"></div><div><strong>End to End</strong><span>Ganzer User-Flow im Browser</span></div></div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

@@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>TypeScript → JavaScript Compilation</title>
<style>
:root {
--dark: #1a1a2e;
--muted: #6b7280;
--border: #cbd5e1;
--bg-soft: #f8fafc;
--ts: #3178c6;
--js: #f7df1e;
--remove: #dc2626;
}
html, body { margin: 0; padding: 0; background: #fff; min-height: 100vh; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
body { padding: 24px 28px; min-height: 100vh; display: flex; flex-direction: column; justify-content: center; }
h1 { margin: 0 0 4px; font-size: 1.45rem; text-align: center; }
.sub { color: var(--muted); margin-bottom: 24px; font-size: 0.88rem; text-align: center; }
.panels { display: grid; grid-template-columns: 1fr 48px 1fr 48px 1fr; align-items: start; gap: 0; }
.panel { border: 2px solid var(--border); border-radius: 10px; overflow: hidden; }
.panel-head {
padding: 8px 14px; font-size: 0.78rem; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.07em;
display: flex; align-items: center; gap: 8px;
}
.panel-body { padding: 14px; min-height: 100vh; display: flex; flex-direction: column; justify-content: center; }
.ts .panel-head { background: var(--ts); color: #fff; }
.strip .panel-head { background: #fee2e2; color: var(--remove); }
.js .panel-head { background: var(--js); color: var(--dark); }
pre {
margin: 0; font-family: ui-monospace, "SF Mono", Menlo, monospace;
font-size: 0.78rem; line-height: 1.65; white-space: pre;
}
.removed { color: var(--remove); text-decoration: line-through; background: #fee2e2; border-radius: 2px; }
.kept { color: #166534; }
.kw { color: var(--ts); }
.kw-js { color: #ca8a04; }
.type-ann{ color: #7c3aed; }
.str { color: #16a34a; }
.fn { color: #0369a1; }
.arrow-col {
display: flex; flex-direction: column; align-items: center;
justify-content: center; padding-top: 52px; gap: 4px;
}
.arrow-shaft { width: 2px; height: 32px; background: var(--muted); }
.arrow-head { width: 0; height: 0; border-left: 7px solid transparent; border-right: 7px solid transparent; border-top: 10px solid var(--muted); }
.arrow-label { font-size: 0.68rem; color: var(--muted); writing-mode: vertical-rl; text-orientation: mixed; letter-spacing: 0.05em; margin-top: 4px; white-space: nowrap; }
</style>
</head>
<body>
<h1>TypeScript → JavaScript Compilation</h1>
<p class="sub">Der TypeScript-Compiler entfernt alle Typ-Annotationen — nur JavaScript bleibt</p>
<div class="panels">
<!-- TypeScript -->
<div class="panel ts">
<div class="panel-head">📘 TypeScript-Datei</div>
<div class="panel-body">
<pre><span class="kw">type</span> <span class="type-ann">Result</span> = <span class="str">"pass"</span> | <span class="str">"fail"</span>;
<span class="kw">function</span> <span class="fn">verify</span>(result: <span class="type-ann">Result</span>) {
<span class="kw">if</span> (result === <span class="str">"pass"</span>) {
console.<span class="fn">log</span>(<span class="str">"Passed"</span>);
} <span class="kw">else</span> {
console.<span class="fn">log</span>(<span class="str">"Failed"</span>);
}
}</pre>
</div>
</div>
<!-- Arrow 1 -->
<div class="arrow-col">
<div class="arrow-shaft"></div>
<div class="arrow-head"></div>
<div class="arrow-label">tsc</div>
</div>
<!-- Strip phase -->
<div class="panel strip">
<div class="panel-head">✂️ Typen werden entfernt</div>
<div class="panel-body">
<pre><span class="removed">type Result = "pass" | "fail";</span>
<span class="kw">function</span> <span class="fn">verify</span>(result<span class="removed">: Result</span>) {
<span class="kw">if</span> (result === <span class="str">"pass"</span>) {
console.<span class="fn">log</span>(<span class="str">"Passed"</span>);
} <span class="kw">else</span> {
console.<span class="fn">log</span>(<span class="str">"Failed"</span>);
}
}</pre>
</div>
</div>
<!-- Arrow 2 -->
<div class="arrow-col">
<div class="arrow-shaft"></div>
<div class="arrow-head"></div>
<div class="arrow-label">output</div>
</div>
<!-- JavaScript -->
<div class="panel js">
<div class="panel-head">📄 JavaScript-Datei</div>
<div class="panel-body">
<pre><span class="kw-js">function</span> <span class="fn">verify</span>(result) {
<span class="kw-js">if</span> (result === <span class="str">"pass"</span>) {
console.<span class="fn">log</span>(<span class="str">"Passed"</span>);
} <span class="kw-js">else</span> {
console.<span class="fn">log</span>(<span class="str">"Failed"</span>);
}
}</pre>
</div>
</div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Web Ecosystem – Schichten</title>
<style>
:root {
--dark: #1a1a2e;
--muted: #6b7280;
}
html, body { margin: 0; padding: 0; background: #fff; min-height: 100vh; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
body { padding: 20px 28px; display: flex; flex-direction: column; align-items: center; }
h1 { margin: 0 0 4px; font-size: 1.45rem; text-align: center; }
.sub { color: var(--muted); margin-bottom: 20px; font-size: 0.88rem; text-align: center; }
.rings-wrap { position: relative; width: 520px; height: 520px; }
.ring {
position: absolute; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
top: 50%; left: 50%; transform: translate(-50%, -50%);
}
/* layers from outside in */
.r-css-libs { width: 520px; height: 520px; background: #f0fdf4; border: 2px solid #86efac; }
.r-frameworks{ width: 400px; height: 400px; background: #eff6ff; border: 2px solid #93c5fd; }
.r-bundlers { width: 290px; height: 290px; background: #fdf4ff; border: 2px solid #d8b4fe; }
.r-postcss { width: 196px; height: 196px; background: #fff7ed; border: 2px solid #fdba74; }
.r-core { width: 116px; height: 116px; background: var(--dark); border: none; color: #fff; flex-direction: column; gap: 2px; text-align: center; }
.r-core span { font-size: 0.72rem; font-weight: 700; line-height: 1.3; }
/* floating labels */
.label {
position: absolute; font-size: 0.72rem; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.06em;
background: rgba(255,255,255,0.85); padding: 2px 7px;
border-radius: 99px; white-space: nowrap;
}
/* ring label positions — top of each ring */
.lbl-css-libs { top: 12px; left: 50%; transform: translateX(-50%); color: #16a34a; }
.lbl-frameworks { top: 72px; left: 50%; transform: translateX(-50%); color: #2563eb; }
.lbl-bundlers { top: 128px; left: 50%; transform: translateX(-50%); color: #7c3aed; }
.lbl-postcss { top: 178px; left: 50%; transform: translateX(-50%); color: #ea580c; }
/* item pills scattered in each ring band */
.pill {
position: absolute; font-size: 0.7rem; font-weight: 600;
padding: 3px 9px; border-radius: 99px; white-space: nowrap;
background: rgba(255,255,255,0.9); border: 1px solid rgba(0,0,0,0.10);
}
</style>
</head>
<body>
<h1>Web Ecosystem – Schichten</h1>
<p class="sub">Von innen nach außen: Kern → Standards → Werkzeuge → Frameworks → CSS-Utilities</p>
<div class="rings-wrap">
<!-- Rings (painted back-to-front) -->
<div class="ring r-css-libs"></div>
<div class="ring r-frameworks"></div>
<div class="ring r-bundlers"></div>
<div class="ring r-postcss"></div>
<div class="ring r-core">
<span>JS</span>
<span>HTML</span>
<span>CSS</span>
</div>
<!-- Ring labels -->
<div class="label lbl-css-libs">CSS Utilities &amp; Libraries</div>
<div class="label lbl-frameworks">Frontend / Backend Frameworks</div>
<div class="label lbl-bundlers">Bundler &amp; Build Tools</div>
<div class="label lbl-postcss">PostCSS / Preprocessors</div>
<!-- CSS Libs band (outer ring) -->
<div class="pill" style="top:52px; left:30px; color:#16a34a;">Tailwind</div>
<div class="pill" style="top:60px; right:24px; color:#16a34a;">Bootstrap</div>
<div class="pill" style="bottom:52px;left:38px; color:#16a34a;">shadcn/ui</div>
<div class="pill" style="bottom:56px;right:28px;color:#16a34a;">Pico CSS</div>
<div class="pill" style="top:50%; left:4px; transform:translateY(-50%); color:#16a34a;">UnoCSS</div>
<div class="pill" style="top:50%; right:4px; transform:translateY(-50%); color:#16a34a;">DaisyUI</div>
<!-- Frameworks band -->
<div class="pill" style="top:88px; left:86px; color:#2563eb;">React</div>
<div class="pill" style="top:88px; right:72px; color:#2563eb;">Vue</div>
<div class="pill" style="bottom:88px;left:72px; color:#2563eb;">Svelte</div>
<div class="pill" style="bottom:88px;right:60px;color:#2563eb;">Angular</div>
<div class="pill" style="top:42%; left:60px; color:#2563eb;">Next.js</div>
<div class="pill" style="top:56%; left:60px; color:#2563eb;">Express</div>
<div class="pill" style="top:46%; right:54px; color:#2563eb;">Nuxt</div>
<div class="pill" style="top:58%; right:54px; color:#2563eb;">Fastify</div>
<!-- Bundlers band -->
<div class="pill" style="top:148px; left:145px; color:#7c3aed;">Vite</div>
<div class="pill" style="top:148px; right:132px;color:#7c3aed;">Webpack</div>
<div class="pill" style="bottom:145px;left:134px;color:#7c3aed;">esbuild</div>
<div class="pill" style="bottom:145px;right:120px;color:#7c3aed;">Rollup</div>
<!-- PostCSS band -->
<div class="pill" style="top:196px; left:50%; transform:translateX(-50%); color:#ea580c;">PostCSS</div>
<div class="pill" style="bottom:196px;left:50%;transform:translateX(-50%); color:#ea580c;">Sass / SCSS</div>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" id="svg4232" width="541.333" height="226.114"><g id="layer1" transform="translate(-129.334 -393.59)"><path id="path84" d="M271.089 441.054v36.897c0 1.836-1.58 4.865-3.416 6.696l-37.58 37.58c-.92.918-1.86 1.367-2.46 1.367-.602 0-.957-.449-.957-1.367v4.1c0 1.836 1.58 3.416 3.416 3.416h81.857c1.837 0 3.416-1.58 3.416-3.416v-81.994c0-1.836-1.58-3.28-3.416-3.28Zm-52.066 85.273c0 1.836-1.585 3.416-3.416 3.416h-40.86v44.55c0 .918.36 1.367.956 1.367s1.405-.449 2.323-1.367l37.58-37.58c1.832-1.837 3.417-4.86 3.417-6.696Z" style="fill:#1a1a2e;fill-opacity:.5;fill-rule:nonzero;stroke:none"/><path id="path88" d="M270.132 394.59c-.604 0-1.541.449-2.46 1.367l-37.58 37.717c-1.831 1.831-3.416 4.723-3.416 6.56v81.993c0 1.837 1.585 1.837 3.416.00014999999996234692l37.58-37.58c1.837-1.837 3.417-4.86 3.417-6.696v-81.994c0-.918-.353-1.366-.957-1.366M133.75 441.055c-1.837 0-3.416 1.443-3.416 3.28v81.993c0 1.836 1.58 3.416 3.416 3.416h81.857c1.836 0 3.416-1.58 3.416-3.416v-81.994c0-1.836-1.58-3.28-3.416-3.28Z" style="fill:#1a1a2e;fill-opacity:.5;fill-rule:nonzero;stroke:none"/><path id="path100" d="M230.092 441.054c-1.836 0-3.416 1.58-3.416 3.416v77.757c0 .918.355 1.367.956 1.367.602 0 1.542-.449 2.46-1.367l37.58-37.58c1.837-1.837 3.417-4.865 3.417-6.696v-36.897Zm-12.026 5.603c-.606 0-1.541.453-2.46 1.366l-37.58 37.717c-1.83 1.87-3.28 4.728-3.28 6.56v37.443h40.86c1.837 0 3.417-1.58 3.417-3.416v-78.304c0-.913-.355-1.366-.957-1.366" style="fill:#1a1a2e;fill-opacity:.5;fill-rule:nonzero;stroke:none"/><path id="path238" d="M523.082 441.19c-.629 0-1.203.148-1.64.547-.448.404-.683.88-.683 1.503v84.18c0 .618.235 1.192.683 1.64a2.34 2.34 0 0 0 1.64.683h20.225c4.149 0 8.172-.628 11.89-1.776 3.716-1.165 6.914-2.766 9.702-4.92 2.793-2.18 4.968-4.87 6.56-8.063 1.59-3.186 2.459-6.816 2.459-10.795 0-2.75-.476-5.357-1.503-7.79-1.017-2.438-2.378-4.57-4.1-6.423-1.733-1.864-3.755-3.312-6.013-4.51a24.2 24.2 0 0 0-7.106-2.46c1.771-.53 3.553-1.398 5.466-2.595 1.908-1.192 3.734-2.646 5.33-4.373s2.902-3.712 3.963-5.877c1.066-2.17 1.64-4.45 1.64-6.832 0-3.81-.716-7.063-2.187-9.84-1.47-2.793-3.427-5.154-5.876-6.97-2.438-1.808-5.242-3.213-8.473-4.099-3.236-.88-6.646-1.23-10.112-1.23Zm61.358 0c-.617 0-1.098.153-1.503.547-.388.4-.453.885-.273 1.503l16.945 84.18c.18.607.607 1.192 1.093 1.64.487.432 1.028.683 1.64.683h6.696c.624 0 1.154-.251 1.64-.683.476-.448.777-1.033.957-1.64l14.485-74.204 14.623 74.204c.174.607.47 1.192.956 1.64.487.432 1.028.683 1.64.683h6.696c.629 0 1.192-.251 1.64-.683a3.9 3.9 0 0 0 .957-1.64l16.945-84.18c.186-.618.076-1.104-.273-1.503-.367-.394-.755-.547-1.367-.547h-5.33c-1.328 0-2.246.634-2.596 2.05l-13.119 74.34-14.622-74.34c-.356-1.416-1.115-2.05-2.46-2.05h-6.696c-1.41 0-2.252.634-2.596 2.05l-14.486 74.34-13.256-74.34c-.36-1.416-1.322-2.05-2.733-2.05Zm-53.979 7.79h14.486c4.974 0 9.047 1.312 12.162 3.963 3.1 2.65 4.647 6.28 4.647 10.796 0 2.3-.536 4.345-1.504 6.286-.984 1.946-2.224 3.651-3.826 5.056-1.59 1.421-3.433 2.482-5.466 3.28a17.5 17.5 0 0 1-6.423 1.23h-14.076Zm0 38.263h14.486c5.592 0 10.15 1.542 13.666 4.51 3.492 2.963 5.192 6.887 5.192 12.026 0 2.662-.382 5.16-1.366 7.379a17.2 17.2 0 0 1-4.1 5.74c-1.727 1.596-3.706 2.804-6.013 3.69a20.4 20.4 0 0 1-7.38 1.367l-14.485-.001Zm-117.523 62.725v20.225c0 .541.03 1.143.136 1.64.098.498.29.853.547 1.23a3.2 3.2 0 0 0 1.093.957c.426.224.967.273 1.64.273a3.8 3.8 0 0 0 1.093-.136c.404-.11.661-.301.82-.41l-.683-1.913-.547.41-.547.136c-.426 0-.781-.263-.956-.683-.18-.432-.274-.94-.274-1.64v-20.089Zm87.87 0v23.915h2.322v-12.162l.41-.82.82-1.094a6.6 6.6 0 0 1 1.367-.957c.48-.261.967-.409 1.503-.409.54 0 .95.164 1.366.41s.826.563 1.094.956c.273.389.399.886.546 1.367.148.487.273.913.273 1.367v11.342h2.324v-11.616c0-.694-.05-1.366-.274-2.05-.224-.688-.656-1.366-1.093-1.913-.432-.535-.967-.907-1.64-1.23a5.2 5.2 0 0 0-2.323-.547c-.585 0-1.137.176-1.64.41-.503.241-.978.503-1.366.82a5.8 5.8 0 0 0-.957.957l-.41.683v-9.429Zm46.735 0v23.915h2.324v-12.162l.41-.82.82-1.094c.37-.344.743-.705 1.23-.956a3.6 3.6 0 0 1 1.64-.41c.54 0 .945.164 1.366.41.415.246.825.563 1.093.956.267.389.4.886.546 1.367.143.487.274.913.274 1.367v11.342h2.323v-11.616c0-.694-.18-1.366-.41-2.05-.219-.688-.514-1.366-.956-1.913-.438-.535-.968-.907-1.64-1.23a5.15 5.15 0 0 0-2.323-.547c-.58 0-1.137.176-1.64.41-.503.241-.979.503-1.367.82a5.8 5.8 0 0 0-.957.957l-.41.683v-9.429Zm35.258 0v20.225c0 .541.026 1.143.136 1.64.093.498.284.853.547 1.23.256.366.53.738.956.957.427.224.968.273 1.64.273.334 0 .82-.022 1.23-.136.4-.11.667-.301.82-.41l-.683-1.913-.547.41-.683.136c-.42 0-.645-.263-.82-.683-.186-.432-.273-.94-.273-1.64v-20.089Zm-223.16 1.503v22.412h5.33c1.394 0 2.635-.345 3.827-.957 1.191-.6 2.28-1.41 3.143-2.46a11.9 11.9 0 0 0 2.05-3.69c.492-1.426.683-3.005.683-4.645a12.4 12.4 0 0 0-.82-4.51c-.52-1.328-1.132-2.356-2.05-3.28-.918-.918-2.033-1.694-3.28-2.186s-2.662-.684-4.1-.684Zm88.827 0v22.412h2.46v-10.66h9.156v10.66h2.46v-22.412h-2.46v9.703h-9.156v-9.703Zm-86.366 1.914h2.733c.967 0 1.842.278 2.7Line truncated

After

Width:  |  Height:  |  Size: 24 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" id="svg4232" width="541.333" height="226.114"><g id="layer1" transform="translate(-129.334 -393.59)"><path id="path84" d="M271.089 441.054v36.897c0 1.836-1.58 4.865-3.416 6.696l-37.58 37.58c-.92.918-1.86 1.367-2.46 1.367-.602 0-.957-.449-.957-1.367v4.1c0 1.836 1.58 3.416 3.416 3.416h81.857c1.837 0 3.416-1.58 3.416-3.416v-81.994c0-1.836-1.58-3.28-3.416-3.28Zm-52.066 85.273c0 1.836-1.585 3.416-3.416 3.416h-40.86v44.55c0 .918.36 1.367.956 1.367s1.405-.449 2.323-1.367l37.58-37.58c1.832-1.837 3.417-4.86 3.417-6.696Z" style="fill:#fff;fill-opacity:.5;fill-rule:nonzero;stroke:none"/><path id="path88" d="M270.132 394.59c-.604 0-1.541.449-2.46 1.367l-37.58 37.717c-1.831 1.831-3.416 4.723-3.416 6.56v81.993c0 1.837 1.585 1.837 3.416.00014999999996234692l37.58-37.58c1.837-1.837 3.417-4.86 3.417-6.696v-81.994c0-.918-.353-1.366-.957-1.366M133.75 441.055c-1.837 0-3.416 1.443-3.416 3.28v81.993c0 1.836 1.58 3.416 3.416 3.416h81.857c1.836 0 3.416-1.58 3.416-3.416v-81.994c0-1.836-1.58-3.28-3.416-3.28Z" style="fill:#fff;fill-opacity:.5;fill-rule:nonzero;stroke:none"/><path id="path100" d="M230.092 441.054c-1.836 0-3.416 1.58-3.416 3.416v77.757c0 .918.355 1.367.956 1.367.602 0 1.542-.449 2.46-1.367l37.58-37.58c1.837-1.837 3.417-4.865 3.417-6.696v-36.897Zm-12.026 5.603c-.606 0-1.541.453-2.46 1.366l-37.58 37.717c-1.83 1.87-3.28 4.728-3.28 6.56v37.443h40.86c1.837 0 3.417-1.58 3.417-3.416v-78.304c0-.913-.355-1.366-.957-1.366" style="fill:#fff;fill-opacity:.5;fill-rule:nonzero;stroke:none"/><path id="path238" d="M523.082 441.19c-.629 0-1.203.148-1.64.547-.448.404-.683.88-.683 1.503v84.18c0 .618.235 1.192.683 1.64a2.34 2.34 0 0 0 1.64.683h20.225c4.149 0 8.172-.628 11.89-1.776 3.716-1.165 6.914-2.766 9.702-4.92 2.793-2.18 4.968-4.87 6.56-8.063 1.59-3.186 2.459-6.816 2.459-10.795 0-2.75-.476-5.357-1.503-7.79-1.017-2.438-2.378-4.57-4.1-6.423-1.733-1.864-3.755-3.312-6.013-4.51a24.2 24.2 0 0 0-7.106-2.46c1.771-.53 3.553-1.398 5.466-2.595 1.908-1.192 3.734-2.646 5.33-4.373s2.902-3.712 3.963-5.877c1.066-2.17 1.64-4.45 1.64-6.832 0-3.81-.716-7.063-2.187-9.84-1.47-2.793-3.427-5.154-5.876-6.97-2.438-1.808-5.242-3.213-8.473-4.099-3.236-.88-6.646-1.23-10.112-1.23Zm61.358 0c-.617 0-1.098.153-1.503.547-.388.4-.453.885-.273 1.503l16.945 84.18c.18.607.607 1.192 1.093 1.64.487.432 1.028.683 1.64.683h6.696c.624 0 1.154-.251 1.64-.683.476-.448.777-1.033.957-1.64l14.485-74.204 14.623 74.204c.174.607.47 1.192.956 1.64.487.432 1.028.683 1.64.683h6.696c.629 0 1.192-.251 1.64-.683a3.9 3.9 0 0 0 .957-1.64l16.945-84.18c.186-.618.076-1.104-.273-1.503-.367-.394-.755-.547-1.367-.547h-5.33c-1.328 0-2.246.634-2.596 2.05l-13.119 74.34-14.622-74.34c-.356-1.416-1.115-2.05-2.46-2.05h-6.696c-1.41 0-2.252.634-2.596 2.05l-14.486 74.34-13.256-74.34c-.36-1.416-1.322-2.05-2.733-2.05Zm-53.979 7.79h14.486c4.974 0 9.047 1.312 12.162 3.963 3.1 2.65 4.647 6.28 4.647 10.796 0 2.3-.536 4.345-1.504 6.286-.984 1.946-2.224 3.651-3.826 5.056-1.59 1.421-3.433 2.482-5.466 3.28a17.5 17.5 0 0 1-6.423 1.23h-14.076Zm0 38.263h14.486c5.592 0 10.15 1.542 13.666 4.51 3.492 2.963 5.192 6.887 5.192 12.026 0 2.662-.382 5.16-1.366 7.379a17.2 17.2 0 0 1-4.1 5.74c-1.727 1.596-3.706 2.804-6.013 3.69a20.4 20.4 0 0 1-7.38 1.367l-14.485-.001Zm-117.523 62.725v20.225c0 .541.03 1.143.136 1.64.098.498.29.853.547 1.23a3.2 3.2 0 0 0 1.093.957c.426.224.967.273 1.64.273a3.8 3.8 0 0 0 1.093-.136c.404-.11.661-.301.82-.41l-.683-1.913-.547.41-.547.136c-.426 0-.781-.263-.956-.683-.18-.432-.274-.94-.274-1.64v-20.089Zm87.87 0v23.915h2.322v-12.162l.41-.82.82-1.094a6.6 6.6 0 0 1 1.367-.957c.48-.261.967-.409 1.503-.409.54 0 .95.164 1.366.41s.826.563 1.094.956c.273.389.399.886.546 1.367.148.487.273.913.273 1.367v11.342h2.324v-11.616c0-.694-.05-1.366-.274-2.05-.224-.688-.656-1.366-1.093-1.913-.432-.535-.967-.907-1.64-1.23a5.2 5.2 0 0 0-2.323-.547c-.585 0-1.137.176-1.64.41-.503.241-.978.503-1.366.82a5.8 5.8 0 0 0-.957.957l-.41.683v-9.429Zm46.735 0v23.915h2.324v-12.162l.41-.82.82-1.094c.37-.344.743-.705 1.23-.956a3.6 3.6 0 0 1 1.64-.41c.54 0 .945.164 1.366.41.415.246.825.563 1.093.956.267.389.4.886.546 1.367.143.487.274.913.274 1.367v11.342h2.323v-11.616c0-.694-.18-1.366-.41-2.05-.219-.688-.514-1.366-.956-1.913-.438-.535-.968-.907-1.64-1.23a5.15 5.15 0 0 0-2.323-.547c-.58 0-1.137.176-1.64.41-.503.241-.979.503-1.367.82a5.8 5.8 0 0 0-.957.957l-.41.683v-9.429Zm35.258 0v20.225c0 .541.026 1.143.136 1.64.093.498.284.853.547 1.23.256.366.53.738.956.957.427.224.968.273 1.64.273.334 0 .82-.022 1.23-.136.4-.11.667-.301.82-.41l-.683-1.913-.547.41-.683.136c-.42 0-.645-.263-.82-.683-.186-.432-.273-.94-.273-1.64v-20.089Zm-223.16 1.503v22.412h5.33c1.394 0 2.635-.345 3.827-.957 1.191-.6 2.28-1.41 3.143-2.46a11.9 11.9 0 0 0 2.05-3.69c.492-1.426.683-3.005.683-4.645a12.4 12.4 0 0 0-.82-4.51c-.52-1.328-1.132-2.356-2.05-3.28-.918-.918-2.033-1.694-3.28-2.186s-2.662-.684-4.1-.684Zm88.827 0v22.412h2.46v-10.66h9.156v10.66h2.46v-22.412h-2.46v9.703h-9.156v-9.703Zm-86.366 1.914h2.733c.967 0 1.842.278 2.733.683.88Line truncated

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><defs><linearGradient id="a" x1="882.997" x2="638.955" y1="27.113" y2="866.902" gradientTransform="scale(.1)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#000014"/><stop offset="1" stop-color="#150426"/></linearGradient><linearGradient id="b" x1="1001.68" x2="790.326" y1="652.45" y2="1094.91" gradientTransform="scale(.1)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ff1639"/><stop offset="1" stop-color="#ff1639" stop-opacity="0"/></linearGradient></defs><path fill="url(#a)" d="M81.504 9.465c.973 1.207 1.469 2.836 2.457 6.09l21.656 71.136a90.079 90.079 0 0 0-25.89-8.765L65.629 30.28a1.833 1.833 0 0 0-3.52.004L48.18 77.902a90.104 90.104 0 0 0-26.003 8.778l21.758-71.14c.996-3.25 1.492-4.876 2.464-6.083a8.023 8.023 0 0 1 3.243-2.398c1.433-.575 3.136-.575 6.535-.575H71.72c3.402 0 5.105 0 6.543.579a7.988 7.988 0 0 1 3.242 2.402Zm0 0"/><path fill="#ff5d01" d="M84.094 90.074c-3.57 3.055-10.696 5.137-18.903 5.137-10.07 0-18.515-3.137-20.754-7.356-.8 2.418-.98 5.184-.98 6.954 0 0-.527 8.675 5.508 14.71a5.671 5.671 0 0 1 5.672-5.671c5.37 0 5.367 4.683 5.363 8.488v.336c0 5.773 3.527 10.719 8.543 12.805a11.62 11.62 0 0 1-1.172-5.098c0-5.508 3.23-7.555 6.988-9.938 2.989-1.894 6.309-4 8.594-8.222a15.513 15.513 0 0 0 1.875-7.41 15.55 15.55 0 0 0-.734-4.735Zm0 0"/><path fill="url(#b)" d="M84.094 90.074c-3.57 3.055-10.696 5.137-18.903 5.137-10.07 0-18.515-3.137-20.754-7.356-.8 2.418-.98 5.184-.98 6.954 0 0-.527 8.675 5.508 14.71a5.671 5.671 0 0 1 5.672-5.671c5.37 0 5.367 4.683 5.363 8.488v.336c0 5.773 3.527 10.719 8.543 12.805a11.62 11.62 0 0 1-1.172-5.098c0-5.508 3.23-7.555 6.988-9.938 2.989-1.894 6.309-4 8.594-8.222a15.513 15.513 0 0 0 1.875-7.41 15.55 15.55 0 0 0-.734-4.735Zm0 0"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" focusable="false">
<title>
Gatsby
</title>
<circle cx="14" cy="14" r="14" fill="#639"/>
<path fill="#fff" d="M6.2 21.8C4.1 19.7 3 16.9 3 14.2L13.9 25c-2.8-.1-5.6-1.1-7.7-3.2zm10.2 2.9L3.3 11.6C4.4 6.7 8.8 3 14 3c3.7 0 6.9 1.8 8.9 4.5l-1.5 1.3C19.7 6.5 17 5 14 5c-3.9 0-7.2 2.5-8.5 6L17 22.5c2.9-1 5.1-3.5 5.8-6.5H18v-2h7c0 5.2-3.7 9.6-8.6 10.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 427 B

@@ -0,0 +1,10 @@
<svg width="394" height="80" viewBox="0 0 394 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M261.919 0.0330722H330.547V12.7H303.323V79.339H289.71V12.7H261.919V0.0330722Z" fill="black"/>
<path d="M149.052 0.0330722V12.7H94.0421V33.0772H138.281V45.7441H94.0421V66.6721H149.052V79.339H80.43V12.7H80.4243V0.0330722H149.052Z" fill="black"/>
<path d="M183.32 0.0661486H165.506L229.312 79.3721H247.178L215.271 39.7464L247.127 0.126654L229.312 0.154184L206.352 28.6697L183.32 0.0661486Z" fill="black"/>
<path d="M201.6 56.7148L192.679 45.6229L165.455 79.4326H183.32L201.6 56.7148Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M80.907 79.339L17.0151 0H0V79.3059H13.6121V16.9516L63.8067 79.339H80.907Z" fill="black"/>
<path d="M333.607 78.8546C332.61 78.8546 331.762 78.5093 331.052 77.8186C330.342 77.1279 329.991 76.2917 330 75.3011C329.991 74.3377 330.342 73.5106 331.052 72.8199C331.762 72.1292 332.61 71.7838 333.607 71.7838C334.566 71.7838 335.405 72.1292 336.115 72.8199C336.835 73.5106 337.194 74.3377 337.204 75.3011C337.194 75.9554 337.028 76.5552 336.696 77.0914C336.355 77.6368 335.922 78.064 335.377 78.373C334.842 78.6911 334.252 78.8546 333.607 78.8546Z" fill="black"/>
<path d="M356.84 45.4453H362.872V68.6846C362.863 70.8204 362.401 72.6472 361.498 74.1832C360.585 75.7191 359.321 76.8914 357.698 77.7185C356.084 78.5364 354.193 78.9546 352.044 78.9546C350.079 78.9546 348.318 78.6001 346.75 77.9094C345.182 77.2187 343.937 76.1826 343.024 74.8193C342.101 73.456 341.649 71.7565 341.649 69.7207H347.691C347.7 70.6114 347.903 71.3838 348.29 72.0291C348.677 72.6744 349.212 73.1651 349.895 73.5105C350.586 73.8559 351.38 74.0286 352.274 74.0286C353.243 74.0286 354.073 73.8286 354.746 73.4196C355.419 73.0197 355.936 72.4199 356.296 71.6201C356.646 70.8295 356.831 69.8479 356.84 68.6846V45.4453Z" fill="black"/>
<path d="M387.691 54.5338C387.544 53.1251 386.898 52.0254 385.773 51.2438C384.638 50.4531 383.172 50.0623 381.373 50.0623C380.11 50.0623 379.022 50.2532 378.118 50.6258C377.214 51.0075 376.513 51.5164 376.033 52.1617C375.554 52.807 375.314 53.5432 375.295 54.3703C375.295 55.061 375.461 55.6608 375.784 56.1607C376.107 56.6696 376.54 57.0968 377.103 57.4422C377.656 57.7966 378.274 58.0874 378.948 58.3237C379.63 58.56 380.313 58.76 380.995 58.9236L384.14 59.6961C385.404 59.9869 386.631 60.3778 387.802 60.8776C388.973 61.3684 390.034 61.9955 390.965 62.7498C391.897 63.5042 392.635 64.413 393.179 65.4764C393.723 66.5397 394 67.7848 394 69.2208C394 71.1566 393.502 72.8562 392.496 74.3285C391.491 75.7917 390.043 76.9369 388.143 77.764C386.252 78.582 383.965 79 381.272 79C378.671 79 376.402 78.6002 374.493 77.8004C372.575 77.0097 371.08 75.8463 370.001 74.3194C368.922 72.7926 368.341 70.9294 368.258 68.7391H374.235C374.318 69.8842 374.687 70.8386 375.314 71.6111C375.95 72.3745 376.78 72.938 377.795 73.3197C378.819 73.6923 379.962 73.8832 381.226 73.8832C382.545 73.8832 383.707 73.6832 384.712 73.2924C385.708 72.9016 386.492 72.3564 387.055 71.6475C387.627 70.9476 387.913 70.1206 387.922 69.1754C387.913 68.312 387.654 67.5939 387.156 67.0304C386.649 66.467 385.948 65.9944 385.053 65.6127C384.15 65.231 383.098 64.8856 381.899 64.5857L378.081 63.6223C375.323 62.9225 373.137 61.8592 371.541 60.4323C369.937 59.0054 369.143 57.115 369.143 54.7429C369.143 52.798 369.678 51.0894 370.758 49.6261C371.827 48.1629 373.294 47.0268 375.148 46.2179C377.011 45.4 379.114 45 381.456 45C383.836 45 385.92 45.4 387.719 46.2179C389.517 47.0268 390.929 48.1538 391.952 49.5897C392.976 51.0257 393.511 52.6707 393.539 54.5338H387.691Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1 @@
<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><path d="M39.267 108.97l-.284-.567c-.567-1.135-.567-2.27-.283-3.689H8.059L53.454 24.14l19.009 34.33 6.241-4.54L59.695 19.6c-.283-.567-2.553-3.971-6.241-3.971-1.703 0-4.256.567-6.242 4.255L1.25 101.31c-.284.852-2.27 4.54-.568 7.66 1.135 1.703 2.838 3.405 6.81 3.405h38.585c-3.972 0-5.958-1.702-6.81-3.404z" fill="#00c58e"/><path d="M126.65 101.59L89.767 35.201c-.567-.567-2.553-4.256-6.242-4.256-1.702 0-4.255.851-6.241 4.256l-4.823 7.944v15.321l11.065-19.009 36.599 65.254h-13.902a6.525 6.525 0 01-.568 3.972l-.284.284c-1.702 3.12-5.958 3.404-6.525 3.404h21.562c.851 0 4.823-.283 6.809-3.404.851-1.419 1.419-3.972-.567-7.377z" fill="#108775"/><path d="M106.51 108.97v-.284l.284-.567c.283-1.135.567-2.27.283-3.405l-1.134-3.404-28.938-50.501-4.256-7.66h-.284l-4.256 7.66-28.938 50.5-1.134 3.405a6.81 6.81 0 00.567 4.256c1.135 1.702 2.837 3.405 6.809 3.405h53.906c.851 0 5.107-.284 7.093-3.405zM72.464 58.469l26.386 46.245H46.079z" fill="#2f495e"/></svg>

After

Width:  |  Height:  |  Size: 1014 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-11.5 -10.23174 23 20.46348">
<title>React Logo</title>
<circle cx="0" cy="0" r="2.05" fill="#61dafb"/>
<g stroke="#61dafb" stroke-width="1" fill="none">
<ellipse rx="11" ry="4.2"/>
<ellipse rx="11" ry="4.2" transform="rotate(60)"/>
<ellipse rx="11" ry="4.2" transform="rotate(120)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 366 B

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 98.1 118" style="enable-background:new 0 0 98.1 118;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FF3E00;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M91.8,15.6C80.9-0.1,59.2-4.7,43.6,5.2L16.1,22.8C8.6,27.5,3.4,35.2,1.9,43.9c-1.3,7.3-0.2,14.8,3.3,21.3
c-2.4,3.6-4,7.6-4.7,11.8c-1.6,8.9,0.5,18.1,5.7,25.4c11,15.7,32.6,20.3,48.2,10.4l27.5-17.5c7.5-4.7,12.7-12.4,14.2-21.1
c1.3-7.3,0.2-14.8-3.3-21.3c2.4-3.6,4-7.6,4.7-11.8C99.2,32.1,97.1,22.9,91.8,15.6"/>
<path class="st1" d="M40.9,103.9c-8.9,2.3-18.2-1.2-23.4-8.7c-3.2-4.4-4.4-9.9-3.5-15.3c0.2-0.9,0.4-1.7,0.6-2.6l0.5-1.6l1.4,1
c3.3,2.4,6.9,4.2,10.8,5.4l1,0.3l-0.1,1c-0.1,1.4,0.3,2.9,1.1,4.1c1.6,2.3,4.4,3.4,7.1,2.7c0.6-0.2,1.2-0.4,1.7-0.7L65.5,72
c1.4-0.9,2.3-2.2,2.6-3.8c0.3-1.6-0.1-3.3-1-4.6c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7l-10.5,6.7
c-1.7,1.1-3.6,1.9-5.6,2.4c-8.9,2.3-18.2-1.2-23.4-8.7c-3.1-4.4-4.4-9.9-3.4-15.3c0.9-5.2,4.1-9.9,8.6-12.7l27.5-17.5
c1.7-1.1,3.6-1.9,5.6-2.5c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.2,0.9-0.4,1.7-0.7,2.6l-0.5,1.6l-1.4-1
c-3.3-2.4-6.9-4.2-10.8-5.4l-1-0.3l0.1-1c0.1-1.4-0.3-2.9-1.1-4.1c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7L32.4,46.1
c-1.4,0.9-2.3,2.2-2.6,3.8s0.1,3.3,1,4.6c1.6,2.3,4.4,3.3,7.1,2.6c0.6-0.2,1.2-0.4,1.7-0.7l10.5-6.7c1.7-1.1,3.6-1.9,5.6-2.5
c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.9,5.2-4.1,9.9-8.6,12.7l-27.5,17.5C44.8,102.5,42.9,103.3,40.9,103.9"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,132 @@
<svg viewBox="0 0 23 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.7482 0H18.8887C21.641 3.93959 21.6571 10.0462 18.8887 14H20.7482C23.516 10.0462 23.4999 3.93959 20.7482 0Z" fill="white"/>
<path d="M2.07027 3.05176e-05C-0.682028 3.93963 -0.698142 10.0463 2.07027 14H3.92985C1.16208 10.0463 1.1782 3.93963 3.92985 3.05176e-05H2.07027Z" fill="white"/>
<path d="M12.0135 13.6771C11.815 13.9297 11.4089 13.7892 11.4089 13.4682V10.3853C11.4089 10.0114 11.106 9.70846 10.7321 9.70846H7.32818C7.05295 9.70846 6.89245 9.39713 7.05295 9.17347L9.29089 6.04023C9.61124 5.59225 9.29089 4.9696 8.73979 4.9696H4.62036C4.34513 4.9696 4.18463 4.65828 4.34512 4.43461L7.24632 0.372548C7.31013 0.283598 7.41262 0.230743 7.52155 0.230743H16.1671C16.4424 0.230743 16.6029 0.542069 16.4424 0.765734L14.2044 3.89897C13.8841 4.34695 14.2044 4.9696 14.7555 4.9696H18.1595C18.4418 4.9696 18.6004 5.29511 18.4257 5.51748L12.0142 13.6777L12.0135 13.6771Z" fill="#863BFF"/>
<mask id="mask0_127_17274" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="4" y="0" width="15" height="14">
<path d="M11.9823 13.677C11.7838 13.9296 11.3777 13.7891 11.3777 13.4681V10.3852C11.3777 10.0113 11.0747 9.70837 10.7009 9.70837H7.29693C7.0217 9.70837 6.8612 9.39704 7.0217 9.17338L9.25964 6.04014C9.57999 5.59216 9.25964 4.96951 8.70854 4.96951H4.58911C4.31388 4.96951 4.15338 4.65818 4.31387 4.43452L7.21507 0.372457C7.27888 0.283506 7.38137 0.230652 7.4903 0.230652H16.1359C16.4111 0.230652 16.5716 0.541978 16.4111 0.765643L14.1732 3.89888C13.8528 4.34686 14.1732 4.96951 14.7243 4.96951H18.1282C18.4106 4.96951 18.5691 5.29502 18.3944 5.51739L11.9829 13.6776L11.9823 13.677Z" fill="black"/>
</mask>
<g mask="url(#mask0_127_17274)">
<g filter="url(#filter0_f_127_17274)">
<ellipse cx="1.6481" cy="4.39979" rx="1.6481" ry="4.39979" transform="matrix(0.00324134 0.999995 0.999995 -0.00324134 2.91309 9.66077)" fill="#EDE6FF"/>
</g>
<g filter="url(#filter1_f_127_17274)">
<ellipse cx="3.11172" cy="8.9321" rx="3.11172" ry="8.9321" transform="matrix(0.00324134 0.999995 0.999995 -0.00324134 -7.51758 2.58936)" fill="#EDE6FF"/>
</g>
<g filter="url(#filter2_f_127_17274)">
<ellipse cx="1.6481" cy="9.12221" rx="1.6481" ry="9.12221" transform="matrix(0.00324134 0.999995 0.999995 -0.00324134 -7.8584 3.61816)" fill="#7E14FF"/>
</g>
<g filter="url(#filter3_f_127_17274)">
<ellipse cx="1.6481" cy="9.15566" rx="1.6481" ry="9.15566" transform="matrix(0.00324134 0.999995 0.999995 -0.00324134 -6.4834 9.00391)" fill="#7E14FF"/>
</g>
<g filter="url(#filter4_f_127_17274)">
<ellipse cx="1.6481" cy="9.15566" rx="1.6481" ry="9.15566" transform="matrix(0.00324134 0.999995 0.999995 -0.00324134 -6.02441 9.34766)" fill="#7E14FF"/>
</g>
<g filter="url(#filter5_f_127_17274)">
<ellipse cx="4.21045" cy="6.60625" rx="4.21045" ry="6.60625" transform="matrix(0.0584509 -0.99829 -0.99829 -0.0584509 26.4971 8.26871)" fill="#EDE6FF"/>
</g>
<g filter="url(#filter6_f_127_17274)">
<ellipse cx="1.03839" cy="6.43346" rx="1.03839" ry="6.43346" transform="matrix(-0.0172986 -0.99985 -0.99985 0.0172986 26.9297 5.63535)" fill="#7E14FF"/>
</g>
<g filter="url(#filter7_f_127_17274)">
<ellipse cx="1.03839" cy="6.43346" rx="1.03839" ry="6.43346" transform="matrix(-0.0172986 -0.99985 -0.99985 0.0172986 26.9297 5.63535)" fill="#7E14FF"/>
</g>
<g filter="url(#filter8_f_127_17274)">
<ellipse cx="4.36576" cy="2.91514" rx="1.31855" ry="8.70955" transform="rotate(39.5103 4.36576 2.91514)" fill="#7E14FF"/>
</g>
<g filter="url(#filter9_f_127_17274)">
<ellipse cx="18.4697" cy="-1.59207" rx="1.31855" ry="8.70955" transform="rotate(37.8923 18.4697 -1.59207)" fill="#7E14FF"/>
</g>
<g filter="url(#filter10_f_127_17274)">
<ellipse cx="16.6417" cy="2.12577" rx="1.78679" ry="2.89199" transform="rotate(37.8923 16.6417 2.12577)" fill="#47BFFF"/>
</g>
<g filter="url(#filter11_f_127_17274)">
<ellipse cx="3.68841" cy="11.7003" rx="1.31855" ry="8.70955" transform="rotate(37.8923 3.68841 11.7003)" fill="#7E14FF"/>
</g>
<g filter="url(#filter12_f_127_17274)">
<ellipse cx="3.68841" cy="11.7003" rx="1.31855" ry="8.70955" transform="rotate(37.8923 3.68841 11.7003)" fill="#7E14FF"/>
</g>
<g filter="url(#filter13_f_127_17274)">
<ellipse cx="14.9179" cy="9.17936" rx="1.31855" ry="8.70955" transform="rotate(37.8923 14.9179 9.17936)" fill="#7E14FF"/>
</g>
<g filter="url(#filter14_f_127_17274)">
<ellipse cx="15.7453" cy="9.92533" rx="1.78679" ry="4.57726" transform="rotate(37.8923 15.7453 9.92533)" fill="#47BFFF"/>
</g>
</g>
<defs>
<filter id="filter0_f_127_17274" x="-1.66562" y="5.06287" width="17.967" height="12.4635" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2.29179" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter1_f_127_17274" x="-12.0914" y="-2.02332" width="27.0314" height="15.3908" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2.29179" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter2_f_127_17274" x="-10.6037" y="0.838165" width="23.7454" height="8.79703" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter3_f_127_17274" x="-9.22867" y="6.22382" width="23.8118" height="8.79703" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter4_f_127_17274" x="-8.76968" y="6.56757" width="23.8118" height="8.79703" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter5_f_127_17274" x="8.96524" y="-5.12549" width="22.3664" height="17.6096" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2.29179" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter6_f_127_17274" x="11.2967" y="0.91397" width="18.3655" height="7.58884" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter7_f_127_17274" x="11.2967" y="0.91397" width="18.3655" height="7.58884" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter8_f_127_17274" x="-4.01871" y="-6.60739" width="16.7698" height="19.045" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter9_f_127_17274" x="10.2694" y="-11.2637" width="16.4007" height="19.3433" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter10_f_127_17274" x="11.6239" y="-3.15738" width="10.0355" height="10.5663" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter11_f_127_17274" x="-4.51187" y="2.02869" width="16.4007" height="19.3433" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter12_f_127_17274" x="-4.51187" y="2.02869" width="16.4007" height="19.3433" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter13_f_127_17274" x="6.71762" y="-0.49228" width="16.4007" height="19.3433" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
<filter id="filter14_f_127_17274" x="9.84946" y="3.39893" width="11.7913" height="13.0528" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="1.37508" result="effect1_foregroundBlur_127_17274"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 261.76 226.69" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(1.3333 0 0 -1.3333 -76.311 313.34)"><g transform="translate(178.06 235.01)"><path d="m0 0-22.669-39.264-22.669 39.264h-75.491l98.16-170.02 98.16 170.02z" fill="#41b883"/></g><g transform="translate(178.06 235.01)"><path d="m0 0-22.669-39.264-22.669 39.264h-36.227l58.896-102.01 58.896 102.01z" fill="#34495e"/></g></g></svg>

After

Width:  |  Height:  |  Size: 467 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3916 1524"><title>logo-on-white-bg</title><path fill="#FFF" d="M808 336l387 218.9v437.9l-387 218.9-387-218.9V554.9z"/><path fill="#8ED6FB" d="M1125.9 977.7l-305.1 172.6v-134.4l190.1-104.6 115 66.4zm20.9-18.9V597.9l-111.6 64.5v232l111.6 64.4zm-657.9 18.9L794 1150.3v-134.4L603.8 911.3l-114.9 66.4zM468 958.8V597.9l111.6 64.5v232L468 958.8zm13.1-384.3l312.9-177v129.9L593.5 637.7l-1.6.9-110.8-64.1zm652.6 0l-312.9-177v129.9l200.5 110.2 1.6.9 110.8-64z"/><path fill="#1C78C0" d="M794 985.3L606.4 882.1V677.8L794 786.1v199.2zm26.8 0l187.6-103.1V677.8L820.8 786.1v199.2zm-13.4-207zM619.1 654.2l188.3-103.5 188.3 103.5-188.3 108.7-188.3-108.7z"/><path fill="#1A1C1C" d="M1585.3 912.3h82.5l84.1-280.2h-80.4l-49.8 198.8-53.1-198.8H1499l-53.6 198.8-49.3-198.8h-80.4l83.6 280.2h82.5l52-179.5 51.5 179.5zM1756.2 773c0 84.1 57.3 146.3 147.4 146.3 69.7 0 107.2-41.8 117.9-61.6l-48.8-37c-8 11.8-30 34.3-68.1 34.3-41.3 0-71.3-26.8-72.9-64.3H2029c.5-5.4.5-10.7.5-16.1 0-91.6-49.3-149.5-136.1-149.5-79.9 0-137.2 63.2-137.2 147.9zm77.7-30.6c3.2-32.1 25.7-56.8 60.6-56.8 33.8 0 58.4 22.5 60 56.8h-120.6zm223.5 169.9h69.7v-28.9c7.5 9.1 35.4 35.9 83.1 35.9 80.4 0 137.2-60.5 137.2-146.8 0-86.8-52.5-147.3-132.9-147.3-48.2 0-76.1 26.8-83.1 36.4V524.9h-73.9v387.4zm71.8-139.3c0-52.5 31.1-82.5 71.8-82.5 42.9 0 71.8 33.8 71.8 82.5 0 49.8-30 80.9-71.8 80.9-45 0-71.8-36.5-71.8-80.9zm247 239.5h73.9V883.3c7 9.1 34.8 35.9 83.1 35.9 80.4 0 132.9-60.5 132.9-147.3 0-85.7-56.8-146.8-137.2-146.8-47.7 0-75.6 26.8-83.1 36.4V632h-69.7v380.5zm71.8-241.1c0-44.5 26.8-80.9 71.8-80.9 41.8 0 71.8 31.1 71.8 80.9 0 48.8-28.9 82.5-71.8 82.5-40.7 0-71.8-30-71.8-82.5zm231.5 54.1c0 58.9 48.2 93.8 105 93.8 32.2 0 53.6-9.6 68.1-25.2l4.8 18.2h65.4V734.9c0-62.7-26.8-109.8-116.8-109.8-42.9 0-85.2 16.1-110.4 33.2l27.9 50.4c20.9-10.7 46.6-19.8 74.5-19.8 32.7 0 50.9 16.6 50.9 41.3v18.2c-10.2-7-32.2-15.5-60.6-15.5-65.4-.1-108.8 37.4-108.8 92.6zm73.9-2.2c0-23 19.8-39.1 48.2-39.1s48.8 14.5 48.8 39.1c0 23.6-20.4 38.6-48.2 38.6s-48.8-15.5-48.8-38.6zm348.9 30.6c-46.6 0-79.8-33.8-79.8-81.4 0-45 29.5-82 77.2-82 31.6 0 53.1 15.5 65.4 26.8l20.9-62.2c-18.2-13.9-47.2-30-88.4-30-85.2 0-149 62.7-149 147.9s62.2 146.3 149.5 146.3c40.7 0 71.3-17.1 87.3-30l-19.8-60.5c-12.4 10.1-34.9 25.1-63.3 25.1zm110.9 58.4h73.9V767.6l93.8 144.7h86.8L3361.6 759l98.6-127h-83.1l-90 117.9v-225h-73.9v387.4z"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 675.012 183.973">
<defs>
<style>
.cls-1 {
fill: #0014eb;
}
.cls-2 {
fill: #0f1d49;
}
</style>
</defs>
<g>
<path class="cls-1" d="M83.783,96.187l1.194-2.545c.258-.551.516-1.102.775-1.653-3.815-9.283-6.973-18.834-9.493-28.557-3.775,4.027-6.94,8.648-9.325,13.723,3.334,9.124,7.925,17.758,13.666,25.635,1.092-2.197,2.15-4.405,3.183-6.601h0Z"/>
<path class="cls-1" d="M78.4,107.042c-5.629-7.417-10.255-15.511-13.803-24.066-1.771,5.321-2.744,11.014-2.744,16.941v7.112c3.316,3.89,6.348,8.026,9.068,12.389,2.76-3.905,5.214-8.086,7.479-12.375h0Z"/>
<path class="cls-1" d="M61.853,113.839v16.037c2.269-2.084,4.361-4.358,6.303-6.778-1.93-3.212-4.035-6.301-6.303-9.258h0Z"/>
<path class="cls-1" d="M88.258,86.704c1.642-3.407,3.344-6.8,5.177-10.114-.097-5.355-.501-10.547-.914-15.577-.242-2.949-.486-5.95-.67-9.016-4.391,2.098-8.441,4.803-12.058,8.004,2.251,9.074,5.072,18,8.467,26.705l-.002-.002Z"/>
<path class="cls-1" d="M96.776,60.642c.246,3.002.489,6.065.673,9.19,2.892-4.491,6.14-8.733,9.939-12.541,4.04-4.05,8.704-7.543,13.716-10.335h-7.04c-6.335,0-12.406,1.147-18.024,3.245.185,3.551.457,7.037.737,10.443h0Z"/>
</g>
<g>
<path class="cls-1" d="M110.373,60.373c-4.48,4.493-8.183,9.703-11.459,15.246,8.014-2.166,16.174-3.708,24.408-4.619v-20.35c-4.735,2.616-9.141,5.904-12.949,9.723Z"/>
<path class="cls-1" d="M95.934,80.957c-1.595,3.005-3.102,6.07-4.565,9.141,8.126-.279,16.163-.578,24.191-.878,2.32-.086,4.641-.173,6.963-.26.517-2.989.8-6.059.8-9.198v-4.395c-9.173,1.037-18.249,2.873-27.124,5.513l-.265.08v-.002Z"/>
<path class="cls-1" d="M72.847,124.067c-2.333,3.089-4.882,5.996-7.71,8.639h6c8.762,0,17.013-2.202,24.264-6.074-7.627-1.82-15.281-2.694-22.555-2.567h.001Z"/>
<path class="cls-1" d="M106.33,110.057c-8.522,0-16.678-.296-24.308-.671-1.881,3.546-3.895,7.026-6.104,10.357,8.016.16,16.384,1.432,24.63,3.765,5.256-3.645,9.816-8.243,13.452-13.543-2.582.057-5.146.093-7.67.093Z"/>
<path class="cls-1" d="M116.693,105.553c2.102-3.813,3.754-7.913,4.88-12.234-1.957.074-3.914.147-5.87.22-8.765.328-17.534.655-26.428.956-.156.333-.313.668-.468,1l-1.197,2.551c-1.11,2.361-2.248,4.736-3.428,7.099,10.083.47,21.057.763,32.513.407h-.001Z"/>
</g>
<path class="cls-2" d="M177.362,82.924c-.227-.144-.465-.287-.712-.432l-13.313-7.752c-.905-20.777-7.433-38.901-17.945-49.778-3.052-3.169-6.288-5.824-9.618-7.891l-4.12-2.566,9.053-3.31-3.902-11.196-32.089,8.172,17.576,29.751,9.693-6.675-5.823-8.703c3.881,2.557,8.848,6.623,8.871,6.637,11.384,9.112,17.014,25.23,17.717,50.721l.012.472,19.982,11.56-19.982,11.56-.012.471c-.955,34.671-10.778,52.131-32.773,58.363l-2.254.426.053,4.97-.057,5.046.974-.167c4.819-.825,8.19-1.825,8.305-1.861,6.062-1.601,12.595-5.801,18.39-11.82,10.511-10.666,17.042-28.791,17.95-49.781l20.629-11.301v-11.065l-6.603-3.852Z"/>
<path class="cls-2" d="M52.834,152.219l-.851.593,7.027,9.689c-4.238-2.748-8.916-6.59-9.888-7.431l-.037-.036-.06-.083-.092-.074c-11.384-9.112-17.013-25.229-17.717-50.721l-.013-.471-19.982-11.56,19.982-11.56.013-.472c.955-34.671,10.778-52.131,32.773-58.363l2.255-.425-.054-4.969.057-4.775-.925.101c-4.826.522-8.255,1.62-8.356,1.654-6.062,1.601-12.595,5.801-18.388,11.82-10.511,10.664-17.042,28.791-17.952,49.782L0,86.218v11.066l6.603,3.852c.227.144.463.288.711.432l13.313,7.752c.905,20.777,7.435,38.901,17.946,49.779,3.024,3.133,6.201,5.749,9.407,7.754l5.13,3.587-8.442,2.336,3.904,11.198,29.159-6.087-16.055-31.75-8.84,6.083h-.001Z"/>
<path class="cls-2" d="M662.144,57.197h12.867v109.694h-12.867v-38.28h-.322c-4.825,7.077-12.546,12.867-26.059,12.867-20.266,0-36.672-16.245-36.672-43.267s16.406-43.267,36.672-43.267c13.513,0,21.234,6.112,26.059,12.385h.322v-10.133ZM637.372,130.219c15.283,0,24.772-12.707,24.772-32.008s-9.49-32.008-24.772-32.008c-16.888,0-25.413,15.923-25.413,32.008s8.525,32.008,25.413,32.008Z"/>
<path class="cls-2" d="M561.725,54.945c14.958,0,27.504,8.203,27.504,27.021v57.26h-12.867v-54.043c0-10.616-4.986-18.979-17.854-18.979-14.476,0-24.126,9.168-24.126,21.875v51.148h-12.867V57.197h12.867v9.811h.322c4.343-5.79,13.028-12.063,27.021-12.063Z"/>
<rect class="cls-2" x="492.568" y="57.199" width="12.867" height="82.03"/>
<path class="cls-2" d="M468.997,86.792c-.643-9.007-6.755-20.588-23.161-20.588-19.462,0-26.861,16.567-26.861,32.008s7.399,32.008,26.861,32.008c16.567,0,22.518-11.42,23.161-21.231h12.867c-1.126,18.658-15.763,32.49-36.029,32.49-23.966,0-39.728-17.532-39.728-43.267s15.763-43.267,39.728-43.267c20.266,0,34.099,13.511,36.029,31.847h-12.867Z"/>
<path class="cls-2" d="M332.383,130.219c16.406,0,22.518-10.616,23.965-16.245h12.867c-4.182,15.28-15.923,27.504-36.35,27.504-24.931,0-40.05-17.532-40.05-43.267,0-27.343,15.441-43.267,39.406-43.267,25.735,0,38.28,17.532,38.28,46.644h-64.819c0,14.797,8.846,28.63,26.7,28.63ZM332.223,66.204c-15.441,0-26.539,10.937-26.539,24.126h51.952c0-13.189-9.972-24.126-25.413-24.126Z"/>
<path class="cls-2" d="M284.574,54.945c3.056,0,5.308.161,7.56.483v12.867h-.322c-17.854-2.895-30.238,9.651-30.238,27.182v43.749h-12.867V57.197h12.867v16.245h.322c4.343-10.776,10.937-18.497,22.679-18.497Z"/>
<circle class="cls-2" cx="499.241" cy="39.864" r="8.499"/>
<g>
<circle class="cls-2" cx="388.676" cy="77.162" r="7.895"/>
<circle class="cls-2" cx="388.676" cy="119.262" r="7.895"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB