Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a4b4536cd
|
||
|
|
102a6d53e5
|
||
|
|
983cdea235
|
||
|
|
2d926f153e
|
||
|
|
a1381dc717
|
||
|
|
c7045d96e4
|
||
|
|
b500e2f647
|
||
|
|
fdd4bbaa54
|
||
|
|
b8ca038153
|
||
|
|
c7811f4001
|
||
|
|
3ae2f6881a
|
||
|
|
a13440b519
|
||
|
|
800a7d6ff9
|
||
|
|
d01253d0d0
|
||
|
|
7885aee8fc
|
||
|
|
27199ee1cc
|
||
|
|
1dd983a453
|
||
|
|
4cd9975017
|
||
|
|
a64382d2de
|
||
|
|
45c554c875
|
||
|
|
841d8b5944
|
||
|
|
613f37983e
|
||
|
|
2658251bcb
|
||
|
|
3d6059c070
|
||
|
|
ed756461e6
|
||
|
|
028f5f0fb8
|
||
|
|
7ae2a9964a
|
||
|
|
abc8dece9c
|
||
|
|
a5f890ae24
|
||
|
|
56901d6068
|
||
|
|
2184870e0b
|
||
|
|
755aadbc6a
|
||
|
|
3554908246
|
||
|
|
2ac3303167
|
||
|
|
7970ba84d1
|
||
|
|
bb7c2730ca
|
||
|
|
4f4e989721
|
||
|
|
32a633188a
|
||
|
|
8ae050baad
|
||
|
|
19d05252ef
|
||
|
|
e1c75a1ba3
|
||
|
|
c21a073984
|
||
|
|
bd473fc890
|
||
|
|
95ff4a08d7
|
||
|
|
29403e0f78
|
||
|
|
0a7730ad9a
|
||
|
|
c4c6119b8a
|
||
|
|
a830480100
|
||
|
|
a56b487bd4
|
||
|
|
81f233b61a
|
||
|
|
0774b8652d
|
||
|
|
dde33f108f
|
||
|
|
82407679b5
|
||
|
|
c242e0f947
|
||
|
|
52312f1cb8
|
||
|
|
e2bab75e60
|
||
|
|
b242c7b4e6
|
||
|
|
46b297ada4
|
||
|
|
8dabc4e570
|
||
|
|
6b33a79d9c
|
||
|
|
a07ab0a3d1
|
||
|
|
5c25b14a59
|
||
|
|
79fbe046d9
|
||
|
|
4b267bcb8b
|
||
|
|
df52a8c6a5
|
||
|
|
6e41b8c3eb
|
||
|
|
96362e2959
|
||
|
|
73be35ccb8
|
||
|
|
3c938b4b1c
|
||
|
|
841a7ced76
|
||
|
|
5c419c9ed1
|
||
|
|
a955a7636a
|
||
|
|
c978842812
|
||
|
|
61468d2d3a
|
||
|
|
a3c3e259b4
|
||
|
|
ac39f2b746
|
||
|
|
19ca5ad3e0
|
||
|
|
aea96edd09
|
No files matched your search
@@ -34,3 +34,7 @@ hdm-internettechnik-slides/
|
||||
*.tmp
|
||||
*.bak
|
||||
.idea
|
||||
|
||||
# Interne Klausur-Previews – NIE committen/deployen
|
||||
slides/**/*-INTERN.md
|
||||
slides/*-INTERN.md
|
||||
@@ -1,11 +1,12 @@
|
||||
# CLAUDE.md - Agent Guidelines for HdM Slides
|
||||
# CLAUDE.md - Agent Guidelines for Uni Slides (DHBW + HdM)
|
||||
|
||||
This project builds presentation decks for Marp, supporting multiple courses.
|
||||
|
||||
## Courses
|
||||
|
||||
- **223015b** - Dateiformate, Schnittstellen, Speichermedien (6 Kapitel)
|
||||
- **223015c** - Internettechnologien (3 Kapitel)
|
||||
- **223015b** - Dateiformate, Schnittstellen, Speichermedien (HdM, 6 Kapitel + Klausur)
|
||||
- **223015c** - Internettechnologien (HdM, 3 Kapitel + Klausur)
|
||||
- **dhbw** - Technik I – Grundlagen IT (DHBW, 8 Kapitel)
|
||||
|
||||
## Agent Restrictions
|
||||
|
||||
@@ -25,8 +26,9 @@ Slide files in `slides/*/*.md` are main content files:
|
||||
|
||||
```
|
||||
slides/
|
||||
├── 223015b/ # Dateiformate course
|
||||
└── 223015c/ # Internettechnik course
|
||||
├── 223015b/ # HdM: Dateiformate
|
||||
├── 223015c/ # HdM: Internettechnik
|
||||
└── dhbw/ # DHBW: Technik I
|
||||
scripts/ # Shared scripts
|
||||
themes/ # Custom Marp themes
|
||||
build/ # Generated output (gitignored)
|
||||
@@ -34,19 +36,30 @@ build/ # Generated output (gitignored)
|
||||
|
||||
## Build Commands
|
||||
|
||||
Unified per-course pattern: `make <target>-<course>`. Group targets without
|
||||
suffix run for all courses. Single dev server serves all courses.
|
||||
|
||||
```bash
|
||||
make dev # Marp live server all courses (port 1312, HMR)
|
||||
make dev-b # Marp live server 223015b (port 1313, HMR)
|
||||
make dev-c # Marp live server 223015c (port 1314, HMR)
|
||||
make build # Build all courses
|
||||
make build-b # Build 223015b only
|
||||
make build-c # Build 223015c only
|
||||
make html # HTML only
|
||||
make pdf # PDF only
|
||||
make klausur # Extract klausur slides → slides/*/klausur.md
|
||||
make deploy # Deploy all (ASK FIRST!)
|
||||
# Dev (all courses, single port)
|
||||
make dev # Live server (HMR), port 1312
|
||||
|
||||
# Per-course build/deploy (replace <c> with course id: 223015b, 223015c, dhbw)
|
||||
make build-<c> # Build HTML + PDF
|
||||
make html-<c> # HTML only
|
||||
make pdf-<c> # PDF only
|
||||
make klausur-<c> # Extract klausur slides (HdM only)
|
||||
make deploy-<c> # Build + deploy single course (ASK FIRST!)
|
||||
|
||||
# All courses
|
||||
make build # Build everything
|
||||
make html / pdf # HTML / PDF only
|
||||
make klausur # Extract klausur (HdM courses only)
|
||||
make deploy # Deploy everything (ASK FIRST!)
|
||||
```
|
||||
|
||||
**Adding a new course:** add id to `COURSES` in Makefile + define `<id>_NAME`,
|
||||
`<id>_KAPITEL`, `<id>_DEPLOY`, `<id>_KLAUSUR`. No new targets needed.
|
||||
|
||||
## Nix Flake Commands
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,228 +1,187 @@
|
||||
# HdM Slides - Unified Makefile
|
||||
# Supports multiple courses: 223015b (Dateiformate) and 223015c (Internettechnik)
|
||||
# Uni Slides – Unified Makefile
|
||||
# Multi-course Marp build system (HdM + DHBW)
|
||||
|
||||
.PHONY: help dev dev-b dev-c build build-b build-c pdf html klausur clean install deploy qr optimize-images
|
||||
.PHONY: help dev build html pdf klausur deploy deploy-index build-index \
|
||||
clean install qr qr-slides optimize-images
|
||||
|
||||
# Course configuration
|
||||
COURSES = 223015b 223015c
|
||||
# -----------------------------------------------------------------------------
|
||||
# Course registry
|
||||
# -----------------------------------------------------------------------------
|
||||
COURSES = 223015b 223015c dhbw
|
||||
SLIDES_DIR = slides
|
||||
|
||||
# Course-specific settings
|
||||
223015b_NAME = Dateiformate, Schnittstellen, Speichermedien
|
||||
223015b_KAPITEL = 00-intro 01-grundlagen-text-audio 02-bild-audio-video 03-speichermedien-schnittstellen 04-distribution-apis-zukunft 05-vertiefung-offene-fragen klausurfolien klausurfragen
|
||||
223015b_DEPLOY_PATH = /home/tengo/html/hdm/223015b
|
||||
|
||||
223015c_NAME = Internettechnologien
|
||||
223015c_KAPITEL = 01-geschichte-grundlagen-html 02-netzwerke-protokolle-css 03-interaktivitaet-javascript klausurfolien klausurfragen
|
||||
223015c_DEPLOY_PATH = /home/tengo/html/hdm/223015c
|
||||
|
||||
DEPLOY_HOST = tengo@tuttle.uberspace.de
|
||||
|
||||
# Default target
|
||||
# Per-course settings: NAME, KAPITEL, DEPLOY, PORT, KLAUSUR (1 or empty)
|
||||
223015b_NAME = Dateiformate, Schnittstellen, Speichermedien
|
||||
223015b_KAPITEL = 00-intro 01-grundlagen-text-audio 02-bild-audio-video 03-speichermedien-schnittstellen 04-distribution-apis-zukunft 05-vertiefung-offene-fragen klausurfolien klausurfragen
|
||||
223015b_DEPLOY = /home/tengo/html/hdm/223015b
|
||||
223015b_KLAUSUR = 1
|
||||
|
||||
223015c_NAME = Internettechnologien
|
||||
223015c_KAPITEL = 01-geschichte-grundlagen-html 02-netzwerke-protokolle-css klausurfolien klausurfragen
|
||||
223015c_DEPLOY = /home/tengo/html/hdm/223015c
|
||||
223015c_KLAUSUR = 1
|
||||
|
||||
dhbw_NAME = Web Engineering
|
||||
dhbw_KAPITEL = 01-intro 02-html-css 03-js-frameworks 04-nodejs-basics 05-express 06-testing 07-typescript 08-docker 09-best-practices
|
||||
dhbw_DEPLOY = /home/tengo/html/dhbw
|
||||
dhbw_KLAUSUR =
|
||||
|
||||
# Courses with klausur extraction enabled
|
||||
KLAUSUR_COURSES = $(foreach c,$(COURSES),$(if $($(c)_KLAUSUR),$(c),))
|
||||
ROOT_DEPLOY = /home/tengo/html/hdm
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Help
|
||||
# -----------------------------------------------------------------------------
|
||||
help:
|
||||
@echo "HdM Slides - Unified Build System"
|
||||
@echo "Uni Slides – Unified Build System"
|
||||
@echo ""
|
||||
@echo "Courses:"
|
||||
@echo " 223015b - Dateiformate, Schnittstellen, Speichermedien"
|
||||
@echo " 223015c - Internettechnologien"
|
||||
@for c in $(COURSES); do echo " $$c"; done
|
||||
@echo ""
|
||||
@echo "Development:"
|
||||
@echo " make dev - Marp live server all courses (port 1312, HMR)"
|
||||
@echo " make dev-b - Marp live server 223015b (port 1313, HMR)"
|
||||
@echo " make dev-c - Marp live server 223015c (port 1314, HMR)"
|
||||
@echo "Dev:"
|
||||
@echo " make dev Live server, all courses (port 1312)"
|
||||
@echo ""
|
||||
@echo "Build:"
|
||||
@echo " make build - Build all courses"
|
||||
@echo " make build-b - Build 223015b only"
|
||||
@echo " make build-c - Build 223015c only"
|
||||
@echo " make pdf - Export all to PDF"
|
||||
@echo " make html - Export all to HTML"
|
||||
@echo " make klausur - Extract klausurfolien slides"
|
||||
@echo "Per-course build/deploy (replace <c> with course id):"
|
||||
@echo " make build-<c> Build HTML + PDF"
|
||||
@echo " make html-<c> Build HTML only"
|
||||
@echo " make pdf-<c> Build PDF only"
|
||||
@echo " make klausur-<c> Extract klausur slides"
|
||||
@echo " make deploy-<c> Build + deploy"
|
||||
@echo ""
|
||||
@echo "All courses:"
|
||||
@echo " make build Build everything"
|
||||
@echo " make html / pdf HTML / PDF only"
|
||||
@echo " make klausur Extract klausur (HdM courses)"
|
||||
@echo " make deploy Deploy everything"
|
||||
@echo ""
|
||||
@echo "Tools:"
|
||||
@echo " make qr URL=... - Generate QR code for URL"
|
||||
@echo " make optimize-images COURSE=223015b - Resize images"
|
||||
@echo ""
|
||||
@echo "Deploy:"
|
||||
@echo " make deploy - Deploy all courses"
|
||||
@echo " make deploy-b - Deploy 223015b only"
|
||||
@echo " make deploy-c - Deploy 223015c only"
|
||||
@echo ""
|
||||
@echo "Other:"
|
||||
@echo " make clean - Remove generated files"
|
||||
@echo " make install - Install dependencies"
|
||||
@echo " make qr URL=... QR code"
|
||||
@echo " make qr-slides COURSE=<c> QR for course URL"
|
||||
@echo " make optimize-images COURSE=<c> Resize images"
|
||||
@echo " make clean Remove generated files"
|
||||
@echo " make install npm install"
|
||||
|
||||
# Ensure build directories exist
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build infrastructure
|
||||
# -----------------------------------------------------------------------------
|
||||
build/.exists:
|
||||
@mkdir -p build/223015b build/223015c
|
||||
@mkdir -p $(addprefix build/,$(COURSES))
|
||||
@touch $@
|
||||
|
||||
# Development server (Marp live server with HMR)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Pattern rules: build-<c>, html-<c>, pdf-<c>, klausur-<c>, deploy-<c>
|
||||
# Per-course config read via $($*_VAR)
|
||||
# -----------------------------------------------------------------------------
|
||||
build-%: build/.exists
|
||||
@echo "Building $*..."
|
||||
@mkdir -p build/$*
|
||||
@cp -r $(SLIDES_DIR)/$*/assets build/$*/ 2>/dev/null || true
|
||||
@cp -r $(SLIDES_DIR)/$*/materials build/$*/ 2>/dev/null || true
|
||||
@for f in $($*_KAPITEL); do \
|
||||
if [ -f "$(SLIDES_DIR)/$*/$$f.md" ]; then \
|
||||
echo " $$f"; \
|
||||
npx @marp-team/marp-cli $(SLIDES_DIR)/$*/$$f.md -o build/$*/$$f.html; \
|
||||
npx @marp-team/marp-cli $(SLIDES_DIR)/$*/$$f.md --pdf --allow-local-files -o build/$*/$$f.pdf; \
|
||||
fi \
|
||||
done
|
||||
@./scripts/generate-index.sh $* build/$*
|
||||
|
||||
html-%: build/.exists
|
||||
@echo "Building HTML for $*..."
|
||||
@mkdir -p build/$*
|
||||
@cp -r $(SLIDES_DIR)/$*/assets build/$*/ 2>/dev/null || true
|
||||
@cp -r $(SLIDES_DIR)/$*/materials build/$*/ 2>/dev/null || true
|
||||
@for f in $($*_KAPITEL); do \
|
||||
if [ -f "$(SLIDES_DIR)/$*/$$f.md" ]; then \
|
||||
echo " $$f.html"; \
|
||||
npx @marp-team/marp-cli $(SLIDES_DIR)/$*/$$f.md -o build/$*/$$f.html; \
|
||||
fi \
|
||||
done
|
||||
@./scripts/generate-index.sh $* build/$*
|
||||
|
||||
pdf-%: build/.exists
|
||||
@echo "Building PDFs for $*..."
|
||||
@mkdir -p build/$*
|
||||
@for f in $($*_KAPITEL); do \
|
||||
if [ -f "$(SLIDES_DIR)/$*/$$f.md" ]; then \
|
||||
echo " $$f.pdf"; \
|
||||
npx @marp-team/marp-cli $(SLIDES_DIR)/$*/$$f.md --pdf --allow-local-files -o build/$*/$$f.pdf; \
|
||||
fi \
|
||||
done
|
||||
|
||||
klausur-%:
|
||||
@./scripts/extract-klausur.sh $*
|
||||
|
||||
deploy-%: build-%
|
||||
@echo "Deploying $*..."
|
||||
scp build/$*/*.html $(DEPLOY_HOST):$($*_DEPLOY)/
|
||||
scp build/$*/*.pdf $(DEPLOY_HOST):$($*_DEPLOY)/ 2>/dev/null || true
|
||||
scp build/$*/*.svg $(DEPLOY_HOST):$($*_DEPLOY)/ 2>/dev/null || true
|
||||
scp -r build/$*/assets/ $(DEPLOY_HOST):$($*_DEPLOY)/ 2>/dev/null || true
|
||||
scp -r build/$*/materials/ $(DEPLOY_HOST):$($*_DEPLOY)/ 2>/dev/null || true
|
||||
@echo "$* deployed."
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Group targets (all courses)
|
||||
# -----------------------------------------------------------------------------
|
||||
dev:
|
||||
@./scripts/dev-server.sh
|
||||
|
||||
dev-b:
|
||||
@./scripts/dev-server.sh 223015b 1313
|
||||
|
||||
dev-c:
|
||||
@./scripts/dev-server.sh 223015c 1314
|
||||
|
||||
dev-kill:
|
||||
@-fuser -k 1312/tcp 2>/dev/null || true
|
||||
@-fuser -k 1313/tcp 2>/dev/null || true
|
||||
@-fuser -k 1314/tcp 2>/dev/null || true
|
||||
|
||||
# Build functions
|
||||
define build_course
|
||||
@echo "Building $(1)..."
|
||||
@mkdir -p build/$(1)
|
||||
@cp -r $(SLIDES_DIR)/$(1)/assets build/$(1)/ 2>/dev/null || true
|
||||
@cp -r $(SLIDES_DIR)/$(1)/materials build/$(1)/ 2>/dev/null || true
|
||||
@for f in $($(1)_KAPITEL); do \
|
||||
if [ -f "$(SLIDES_DIR)/$(1)/$$f.md" ]; then \
|
||||
echo " Building $$f..."; \
|
||||
npx @marp-team/marp-cli $(SLIDES_DIR)/$(1)/$$f.md -o build/$(1)/$$f.html; \
|
||||
npx @marp-team/marp-cli $(SLIDES_DIR)/$(1)/$$f.md --pdf --allow-local-files -o build/$(1)/$$f.pdf; \
|
||||
fi \
|
||||
done
|
||||
@./scripts/generate-index.sh $(1) build/$(1)
|
||||
endef
|
||||
|
||||
build-b: build/.exists
|
||||
$(call build_course,223015b)
|
||||
|
||||
build-c: build/.exists
|
||||
$(call build_course,223015c)
|
||||
|
||||
build: build-b build-c
|
||||
@echo "All courses built!"
|
||||
|
||||
# HTML only builds
|
||||
define html_course
|
||||
@echo "Building HTML for $(1)..."
|
||||
@mkdir -p build/$(1)
|
||||
@cp -r $(SLIDES_DIR)/$(1)/assets build/$(1)/ 2>/dev/null || true
|
||||
@cp -r $(SLIDES_DIR)/$(1)/materials build/$(1)/ 2>/dev/null || true
|
||||
@for f in $($(1)_KAPITEL); do \
|
||||
if [ -f "$(SLIDES_DIR)/$(1)/$$f.md" ]; then \
|
||||
echo " $$f.html"; \
|
||||
npx @marp-team/marp-cli $(SLIDES_DIR)/$(1)/$$f.md -o build/$(1)/$$f.html; \
|
||||
fi \
|
||||
done
|
||||
@./scripts/generate-index.sh $(1) build/$(1)
|
||||
endef
|
||||
|
||||
html-b: build/.exists
|
||||
$(call html_course,223015b)
|
||||
|
||||
html-c: build/.exists
|
||||
$(call html_course,223015c)
|
||||
|
||||
html: html-b html-c
|
||||
|
||||
# PDF only builds
|
||||
define pdf_course
|
||||
@echo "Building PDFs for $(1)..."
|
||||
@mkdir -p build/$(1)
|
||||
@for f in $($(1)_KAPITEL); do \
|
||||
if [ -f "$(SLIDES_DIR)/$(1)/$$f.md" ]; then \
|
||||
echo " $$f.pdf"; \
|
||||
npx @marp-team/marp-cli $(SLIDES_DIR)/$(1)/$$f.md --pdf --allow-local-files -o build/$(1)/$$f.pdf; \
|
||||
fi \
|
||||
done
|
||||
endef
|
||||
|
||||
pdf-b: build/.exists
|
||||
$(call pdf_course,223015b)
|
||||
|
||||
pdf-c: build/.exists
|
||||
$(call pdf_course,223015c)
|
||||
|
||||
pdf: pdf-b pdf-c
|
||||
|
||||
# Klausur slide extraction
|
||||
klausur-b:
|
||||
@./scripts/extract-klausur.sh 223015b
|
||||
|
||||
klausur-c:
|
||||
@./scripts/extract-klausur.sh 223015c
|
||||
|
||||
klausur: klausur-b klausur-c
|
||||
@echo "Klausurfolien slides extracted for all courses!"
|
||||
|
||||
# QR Code generation (uses nix-shell)
|
||||
qr:
|
||||
ifndef URL
|
||||
@echo "Usage: make qr URL=https://example.com"
|
||||
@echo " make qr URL=https://example.com OUTPUT=my-qr.png"
|
||||
else
|
||||
@nix-shell -p qrencode --run 'qrencode -o $(or $(OUTPUT),qr-code.png) -s 10 "$(URL)"'
|
||||
@echo "Generated: $(or $(OUTPUT),qr-code.png)"
|
||||
endif
|
||||
|
||||
# QR code for a slide deck URL
|
||||
qr-slides:
|
||||
ifndef COURSE
|
||||
@echo "Usage: make qr-slides COURSE=223015b"
|
||||
else
|
||||
@nix-shell -p qrencode --run 'qrencode -o build/$(COURSE)/qr-$(COURSE).png -s 10 "https://librete.ch/hdm/$(COURSE)/"'
|
||||
@echo "Generated: build/$(COURSE)/qr-$(COURSE).png"
|
||||
endif
|
||||
|
||||
# Optimize images
|
||||
optimize-images:
|
||||
ifndef COURSE
|
||||
@echo "Usage: make optimize-images COURSE=223015b"
|
||||
else
|
||||
@echo "Optimizing images for $(COURSE)..."
|
||||
@mkdir -p $(SLIDES_DIR)/$(COURSE)/assets-original
|
||||
@nix-shell -p imagemagick --run 'for img in $(SLIDES_DIR)/$(COURSE)/assets/*.png $(SLIDES_DIR)/$(COURSE)/assets/*.jpg 2>/dev/null; do \
|
||||
[ -f "$$img" ] && cp "$$img" $(SLIDES_DIR)/$(COURSE)/assets-original/ && \
|
||||
magick "$$img" -resize "1920x>" -quality 85 "$$img" && echo " Optimized: $$(basename $$img)"; \
|
||||
done || true'
|
||||
@echo "Done! Originals in assets-original/"
|
||||
endif
|
||||
|
||||
# Root index for /hdm/
|
||||
HDM_DEPLOY_PATH = /home/tengo/html/hdm
|
||||
build: $(addprefix build-,$(COURSES))
|
||||
html: $(addprefix html-,$(COURSES))
|
||||
pdf: $(addprefix pdf-,$(COURSES))
|
||||
klausur: $(addprefix klausur-,$(KLAUSUR_COURSES))
|
||||
|
||||
build-index: build/.exists
|
||||
@echo "Building root index..."
|
||||
@mkdir -p build
|
||||
@./scripts/generate-root-index.sh
|
||||
|
||||
# Deploy
|
||||
define deploy_course
|
||||
@echo "Deploying $(1)..."
|
||||
scp build/$(1)/*.html $(DEPLOY_HOST):$($(1)_DEPLOY_PATH)/
|
||||
scp build/$(1)/*.pdf $(DEPLOY_HOST):$($(1)_DEPLOY_PATH)/ 2>/dev/null || true
|
||||
scp build/$(1)/*.svg $(DEPLOY_HOST):$($(1)_DEPLOY_PATH)/ 2>/dev/null || true
|
||||
scp -r build/$(1)/assets/ $(DEPLOY_HOST):$($(1)_DEPLOY_PATH)/
|
||||
scp -r build/$(1)/materials/ $(DEPLOY_HOST):$($(1)_DEPLOY_PATH)/ 2>/dev/null || true
|
||||
@echo "$(1) deployed!"
|
||||
endef
|
||||
|
||||
deploy-b: build-b
|
||||
$(call deploy_course,223015b)
|
||||
|
||||
deploy-c: build-c
|
||||
$(call deploy_course,223015c)
|
||||
|
||||
deploy-index: build-index
|
||||
@echo "Deploying root index..."
|
||||
scp build/index.html $(DEPLOY_HOST):$(HDM_DEPLOY_PATH)/
|
||||
scp build/qr-root.svg $(DEPLOY_HOST):$(HDM_DEPLOY_PATH)/ 2>/dev/null || true
|
||||
@echo "Root index deployed!"
|
||||
scp build/index.html $(DEPLOY_HOST):$(ROOT_DEPLOY)/ 2>/dev/null || true
|
||||
@echo "Root index deployed."
|
||||
|
||||
deploy: build-index deploy-b deploy-c deploy-index
|
||||
@echo "All courses deployed!"
|
||||
deploy: build-index $(addprefix deploy-,$(COURSES)) deploy-index
|
||||
@echo "All courses deployed."
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Tools
|
||||
# -----------------------------------------------------------------------------
|
||||
qr:
|
||||
ifndef URL
|
||||
@echo "Usage: make qr URL=https://example.com [OUTPUT=qr.png]"
|
||||
else
|
||||
@nix-shell -p qrencode --run 'qrencode -o $(or $(OUTPUT),qr-code.png) -s 10 "$(URL)"'
|
||||
@echo "Generated: $(or $(OUTPUT),qr-code.png)"
|
||||
endif
|
||||
|
||||
qr-slides:
|
||||
ifndef COURSE
|
||||
@echo "Usage: make qr-slides COURSE=<course>"
|
||||
else
|
||||
@nix-shell -p qrencode --run 'qrencode -o build/$(COURSE)/qr-$(COURSE).png -s 10 "https://librete.ch/hdm/$(COURSE)/"'
|
||||
@echo "Generated: build/$(COURSE)/qr-$(COURSE).png"
|
||||
endif
|
||||
|
||||
optimize-images:
|
||||
ifndef COURSE
|
||||
@echo "Usage: make optimize-images COURSE=<course>"
|
||||
else
|
||||
@echo "Optimizing images for $(COURSE)..."
|
||||
@mkdir -p $(SLIDES_DIR)/$(COURSE)/assets-original
|
||||
@nix-shell -p imagemagick --run 'for img in $(SLIDES_DIR)/$(COURSE)/assets/*.png $(SLIDES_DIR)/$(COURSE)/assets/*.jpg 2>/dev/null; do \
|
||||
[ -f "$$img" ] && cp "$$img" $(SLIDES_DIR)/$(COURSE)/assets-original/ && \
|
||||
magick "$$img" -resize "1920x>" -quality 85 "$$img" && echo " $$(basename $$img)"; \
|
||||
done || true'
|
||||
@echo "Done. Originals in assets-original/"
|
||||
endif
|
||||
|
||||
# Clean
|
||||
clean:
|
||||
@echo "Cleaning generated files..."
|
||||
@echo "Cleaning..."
|
||||
rm -rf build/ .dev-index/ *.pdf *.html qr-code.png
|
||||
|
||||
# Install dependencies
|
||||
install:
|
||||
@echo "Installing dependencies..."
|
||||
npm install
|
||||
@npm install
|
||||
@@ -1,25 +1,22 @@
|
||||
# HdM Slides
|
||||
# Uni Slides - DHBW + HdM
|
||||
|
||||
Presentation slides for HdM Stuttgart courses, built with [Marp](https://marp.app/).
|
||||
Combined presentation slides for DHBW and HdM Stuttgart courses, built with [Marp](https://marp.app/).
|
||||
|
||||
## Courses
|
||||
|
||||
| Code | Title | Kapitel |
|
||||
|------|-------|---------|
|
||||
| 223015b | Dateiformate, Schnittstellen, Speichermedien | 6 |
|
||||
| 223015c | Internettechnologien | 3 |
|
||||
| Code | Title | Origin |
|
||||
|------|-------|--------|
|
||||
| 223015b | Dateiformate, Schnittstellen, Speichermedien | HdM |
|
||||
| 223015c | Internettechnologien | HdM |
|
||||
| dhbw | Technik I – Grundlagen IT | DHBW |
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
slides/
|
||||
├── 223015b/ # Dateiformate course
|
||||
│ ├── 00-intro.md
|
||||
│ ├── 01-grundlagen-text-audio.md
|
||||
│ └── ...
|
||||
└── 223015c/ # Internettechnik course
|
||||
├── 01-geschichte-grundlagen-html.md
|
||||
└── ...
|
||||
├── 223015b/ # HdM: Dateiformate
|
||||
├── 223015c/ # HdM: Internettechnik
|
||||
├── dhbw/ # DHBW slides (TBD)
|
||||
scripts/ # Build scripts
|
||||
build/ # Generated output (gitignored)
|
||||
```
|
||||
@@ -27,54 +24,49 @@ build/ # Generated output (gitignored)
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Start development server (hot reload)
|
||||
make dev # Single server for all courses (port 3000)
|
||||
|
||||
# Access individual courses:
|
||||
# 223015b: http://localhost:3000/223015b/
|
||||
# 223015c: http://localhost:3000/223015c/
|
||||
make hdm-dev # Live server all HdM courses (port 1312, HMR)
|
||||
make hdm-dev-b # 223015b only (port 1313, HMR)
|
||||
make hdm-dev-c # 223015c only (port 1314, HMR)
|
||||
make dhbw-dev # DHBW (not yet configured)
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
make build # Build all (HTML + PDF)
|
||||
make build-b # Build 223015b only
|
||||
make build-c # Build 223015c only
|
||||
make html # HTML only
|
||||
make pdf # PDF only
|
||||
make hdm-build # Build all HdM courses (HTML + PDF)
|
||||
make hdm-build-b # 223015b only
|
||||
make hdm-build-c # 223015c only
|
||||
make hdm-html # HTML only all HdM
|
||||
make hdm-pdf # PDF only all HdM
|
||||
```
|
||||
|
||||
## Klausur Folien
|
||||
|
||||
Extract exam-relevant slides (marked with `<!-- _class: klausur -->`) into a single file:
|
||||
Extract exam slides (`<!-- _class: klausur -->`) into single file:
|
||||
|
||||
```bash
|
||||
make klausur # Extract all courses
|
||||
make klausur-b # 223015b only
|
||||
make klausur-c # 223015c only
|
||||
make hdm-klausur # All HdM courses
|
||||
make hdm-klausur-b # 223015b only
|
||||
make hdm-klausur-c # 223015c only
|
||||
```
|
||||
|
||||
Output: `slides/<course>/klausurfolien.md`
|
||||
|
||||
The generated file includes:
|
||||
- A title slide per kapitel for orientation
|
||||
- All slides marked with `<!-- _class: klausur -->`
|
||||
|
||||
## Tools
|
||||
|
||||
```bash
|
||||
make qr URL=https://example.com # Generate QR code
|
||||
make qr-slides COURSE=223015b # QR for course URL
|
||||
make optimize-images COURSE=223015b # Resize images
|
||||
make qr URL=https://example.com # Generate QR code
|
||||
make qr-slides COURSE=223015b # QR for course URL
|
||||
make optimize-images COURSE=223015b # Resize images
|
||||
```
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
make deploy # Deploy all courses
|
||||
make deploy-b # Deploy 223015b only
|
||||
make deploy-c # Deploy 223015c only
|
||||
make hdm-deploy # Deploy all HdM courses
|
||||
make hdm-deploy-b # 223015b only
|
||||
make hdm-deploy-c # 223015c only
|
||||
make deploy # Full deploy (all courses)
|
||||
```
|
||||
|
||||
## Marking Slides as Klausur-Relevant
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
description = "HdM Slides - Marp presentation builder";
|
||||
description = "Uni Slides - DHBW + HdM Marp presentation builder";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
@@ -19,7 +19,7 @@
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
echo "HdM Slides - run 'make' for available commands"
|
||||
echo "Uni Slides - run 'make' for available commands"
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,6 +13,15 @@ case "$COURSE" in
|
||||
SLIDES_URL="https://librete.ch/hdm/223015b"
|
||||
ACCENT_COLOR="#1e5f8a"
|
||||
ACCENT_LIGHT="#e8f4fc"
|
||||
INSTITUTION="HdM Stuttgart"
|
||||
HEADLINE="HdM Vorlesungen"
|
||||
KAPITEL_TERM="Kapitel"
|
||||
BREADCRUMB_URL="../"
|
||||
EXAM_URL=""
|
||||
EXAM_LABEL=""
|
||||
KONTAKT_URL="mailto:mail@librete.ch"
|
||||
KONTAKT_LABEL="Kontakt"
|
||||
QR_IMG="qr-index.svg"
|
||||
;;
|
||||
223015c)
|
||||
TITLE="Grundlagen IT- und Internettechnik"
|
||||
@@ -20,16 +29,72 @@ case "$COURSE" in
|
||||
SLIDES_URL="https://librete.ch/hdm/223015c"
|
||||
ACCENT_COLOR="#d63384"
|
||||
ACCENT_LIGHT="#fce4ec"
|
||||
INSTITUTION="HdM Stuttgart"
|
||||
HEADLINE="HdM Vorlesungen"
|
||||
KAPITEL_TERM="Kapitel"
|
||||
BREADCRUMB_URL="../"
|
||||
EXAM_URL=""
|
||||
EXAM_LABEL=""
|
||||
KONTAKT_URL="mailto:mail@librete.ch"
|
||||
KONTAKT_LABEL="Kontakt"
|
||||
QR_IMG="qr-index.svg"
|
||||
;;
|
||||
dhbw)
|
||||
TITLE="Web Engineering"
|
||||
SUBTITLE="Informatik / Wirtschaftsinformatik"
|
||||
SLIDES_URL="https://librete.ch/dhbw"
|
||||
ACCENT_COLOR="#a02060"
|
||||
ACCENT_LIGHT="#fce4ec"
|
||||
INSTITUTION="DHBW Stuttgart"
|
||||
HEADLINE="DHBW Vorlesungen"
|
||||
KAPITEL_TERM="Sitzung"
|
||||
BREADCRUMB_URL=""
|
||||
EXAM_URL="https://git.librete.ch/DHBW/pruefungsleistung"
|
||||
EXAM_LABEL="Prüfungsleistung"
|
||||
KONTAKT_URL="mailto:michael.czechowski@lehre.dhbw-stuttgart.de"
|
||||
KONTAKT_LABEL="Kontakt"
|
||||
QR_IMG="assets/slides-dhbw.png"
|
||||
;;
|
||||
*)
|
||||
TITLE="$COURSE - HdM Slides"
|
||||
TITLE="$COURSE - Slides"
|
||||
SUBTITLE="Lecture Slides"
|
||||
SLIDES_URL="https://librete.ch/hdm/$COURSE"
|
||||
SLIDES_URL="https://librete.ch/$COURSE"
|
||||
ACCENT_COLOR="#0066cc"
|
||||
ACCENT_LIGHT="#e8f4fc"
|
||||
INSTITUTION="Stuttgart"
|
||||
HEADLINE="Vorlesungen"
|
||||
KAPITEL_TERM="Kapitel"
|
||||
BREADCRUMB_URL=""
|
||||
EXAM_URL=""
|
||||
EXAM_LABEL=""
|
||||
KONTAKT_URL="mailto:mail@librete.ch"
|
||||
KONTAKT_LABEL="Kontakt"
|
||||
QR_IMG="qr-index.svg"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Optional breadcrumb HTML (empty if no parent overview)
|
||||
if [[ -n "$BREADCRUMB_URL" ]]; then
|
||||
BREADCRUMB_HTML="<p class=\"breadcrumb\"><a href=\"$BREADCRUMB_URL\">← Kursübersicht</a></p>"
|
||||
else
|
||||
BREADCRUMB_HTML=""
|
||||
fi
|
||||
|
||||
# Optional exam-link HTML
|
||||
if [[ -n "$EXAM_URL" ]]; then
|
||||
EXAM_HTML="<p class=\"exam-link\"><a href=\"$EXAM_URL\">$EXAM_LABEL →</a></p>"
|
||||
else
|
||||
EXAM_HTML=""
|
||||
fi
|
||||
|
||||
# Footer kursübersicht link only when breadcrumb parent exists
|
||||
if [[ -n "$BREADCRUMB_URL" ]]; then
|
||||
FOOTER_KURS_HTML=" ·
|
||||
<a href=\"$BREADCRUMB_URL\">Kursübersicht</a>"
|
||||
else
|
||||
FOOTER_KURS_HTML=""
|
||||
fi
|
||||
|
||||
# Topic mappings for nice German titles
|
||||
declare -A TOPIC_MAP
|
||||
TOPIC_MAP["intro"]="Einführung"
|
||||
@@ -41,6 +106,14 @@ TOPIC_MAP["vertiefung-offene-fragen"]="Vertiefung & Offene Fragen"
|
||||
TOPIC_MAP["geschichte-grundlagen-html"]="Geschichte, Grundlagen & HTML"
|
||||
TOPIC_MAP["netzwerke-protokolle-css"]="Netzwerke, Protokolle & CSS"
|
||||
TOPIC_MAP["interaktivitaet-javascript"]="Interaktivität & JavaScript"
|
||||
TOPIC_MAP["html-css"]="HTML & CSS"
|
||||
TOPIC_MAP["js-frameworks"]="JS, Frameworks & Build Tools"
|
||||
TOPIC_MAP["nodejs-basics"]="Node.js: Scripting, Running & Building"
|
||||
TOPIC_MAP["express"]="Express API, CRUD & Middlewares"
|
||||
TOPIC_MAP["testing"]="Testing"
|
||||
TOPIC_MAP["typescript"]="TypeScript"
|
||||
TOPIC_MAP["docker"]="Docker, Proxies & DBs"
|
||||
TOPIC_MAP["best-practices"]="Wrap-up & Best Practices"
|
||||
|
||||
# Configure which topics should appear disabled per course
|
||||
# Two modes: fully disabled (non-clickable card) and buttons-disabled (link remains clickable, buttons are disabled)
|
||||
@@ -48,8 +121,17 @@ declare -A DISABLED_FULL
|
||||
declare -A DISABLED_BUTTONS
|
||||
# For course B: chapters 3,4,5 should be fully disabled
|
||||
DISABLED_FULL["223015b"]="speichermedien-schnittstellen distribution-apis-zukunft vertiefung-offene-fragen"
|
||||
# For course C: chapter 3 should keep link clickable but buttons disabled
|
||||
DISABLED_BUTTONS["223015c"]="interaktivitaet-javascript"
|
||||
# For course C: all chapters fully active
|
||||
DISABLED_BUTTONS["223015c"]=""
|
||||
|
||||
# Courses where the "Klausurrelevante Folien" card should be active (clickable)
|
||||
declare -A KLAUSURFOLIEN_ACTIVE
|
||||
KLAUSURFOLIEN_ACTIVE["223015b"]=1
|
||||
|
||||
# Courses where the "Klausurfragen" card should be active (clickable)
|
||||
declare -A KLAUSURFRAGEN_ACTIVE
|
||||
KLAUSURFRAGEN_ACTIVE["223015b"]=1
|
||||
KLAUSURFRAGEN_ACTIVE["223015c"]=1
|
||||
|
||||
cat > "$BUILD_DIR/index.html" << HEADER
|
||||
<!DOCTYPE html>
|
||||
@@ -101,6 +183,16 @@ cat > "$BUILD_DIR/index.html" << HEADER
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
.exam-link {
|
||||
margin-top: 1rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.exam-link a {
|
||||
color: $ACCENT_COLOR;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
.exam-link a:hover { text-decoration: underline; }
|
||||
/* Use the .termine wrapper and add a gap between cards */
|
||||
.termine {
|
||||
margin-top: 1.5rem;
|
||||
@@ -226,11 +318,12 @@ cat > "$BUILD_DIR/index.html" << HEADER
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<p class="breadcrumb"><a href="../">← Kursübersicht</a></p>
|
||||
<h1>HdM Vorlesungen</h1>
|
||||
$BREADCRUMB_HTML
|
||||
<h1>$HEADLINE</h1>
|
||||
<h2 class="course-heading">$TITLE</h2>
|
||||
<p class="subtitle">$SUBTITLE</p>
|
||||
<p class="meta">HdM Stuttgart · Sommersemester 2026 · Michael Czechowski</p>
|
||||
<p class="meta">$INSTITUTION · Sommersemester 2026 · Michael Czechowski</p>
|
||||
$EXAM_HTML
|
||||
</header>
|
||||
<div class="termine">
|
||||
HEADER
|
||||
@@ -253,7 +346,7 @@ for html in $(ls "$BUILD_DIR"/[0-9][0-9]-*.html 2>/dev/null | sort); do
|
||||
if [[ "$kapitel_num" == "0" ]] || [[ -z "$kapitel_num" ]]; then
|
||||
kapitel_label="Einführung"
|
||||
else
|
||||
kapitel_label="Kapitel $kapitel_num"
|
||||
kapitel_label="$KAPITEL_TERM $kapitel_num"
|
||||
fi
|
||||
|
||||
pdf_filename="${filename%.html}.pdf"
|
||||
@@ -320,13 +413,29 @@ LINK
|
||||
|
||||
done
|
||||
|
||||
# Add klausurfolien entry if it exists (disabled — wird noch überarbeitet)
|
||||
# Add klausurfolien entry if it exists (active per KLAUSURFOLIEN_ACTIVE, else disabled)
|
||||
if [[ -f "$BUILD_DIR/klausurfolien.html" ]]; then
|
||||
pdf_disabled=""
|
||||
if [[ -f "$BUILD_DIR/klausurfolien.pdf" ]]; then
|
||||
pdf_disabled="<span class=\"btn btn-pdf btn-disabled\">PDF</span>"
|
||||
fi
|
||||
cat >> "$BUILD_DIR/index.html" << KLAUSUR
|
||||
if [[ "${KLAUSURFOLIEN_ACTIVE[$COURSE]}" == "1" ]]; then
|
||||
pdf_active=""
|
||||
if [[ -f "$BUILD_DIR/klausurfolien.pdf" ]]; then
|
||||
pdf_active="<a href=\"klausurfolien.pdf\" class=\"btn btn-pdf\">PDF</a>"
|
||||
fi
|
||||
cat >> "$BUILD_DIR/index.html" << KLAUSUR
|
||||
<div class="kapitel-card" style="border-left: 3px solid $ACCENT_COLOR;">
|
||||
<a href="klausurfolien.html" class="kapitel-link">
|
||||
<div class="kapitel-label">Prüfung</div>
|
||||
<div class="kapitel-title">Klausurrelevante Folien</div>
|
||||
</a>
|
||||
<a href="klausurfolien.html" class="btn btn-slides">Folien</a>
|
||||
$pdf_active
|
||||
</div>
|
||||
KLAUSUR
|
||||
else
|
||||
pdf_disabled=""
|
||||
if [[ -f "$BUILD_DIR/klausurfolien.pdf" ]]; then
|
||||
pdf_disabled="<span class=\"btn btn-pdf btn-disabled\">PDF</span>"
|
||||
fi
|
||||
cat >> "$BUILD_DIR/index.html" << KLAUSUROFF
|
||||
<div class="kapitel-card disabled" style="border-left: 3px solid $ACCENT_COLOR;">
|
||||
<div class="kapitel-link">
|
||||
<div class="kapitel-label">Prüfung</div>
|
||||
@@ -335,20 +444,41 @@ if [[ -f "$BUILD_DIR/klausurfolien.html" ]]; then
|
||||
<span class="btn btn-slides btn-disabled">Folien</span>
|
||||
$pdf_disabled
|
||||
</div>
|
||||
KLAUSUR
|
||||
KLAUSUROFF
|
||||
fi
|
||||
fi
|
||||
|
||||
# Add klausurfragen entry if it exists (disabled — wird noch überarbeitet)
|
||||
# Add klausurfragen entry if it exists (active per KLAUSURFRAGEN_ACTIVE, else disabled)
|
||||
if [[ -f "$BUILD_DIR/klausurfragen.html" ]] || [[ -f "$BUILD_DIR/klausurfragen.pdf" ]]; then
|
||||
pdf_disabled=""
|
||||
if [[ -f "$BUILD_DIR/klausurfragen.pdf" ]]; then
|
||||
pdf_disabled="<span class=\"btn btn-pdf btn-disabled\">PDF</span>"
|
||||
fi
|
||||
slides_disabled=""
|
||||
if [[ -f "$BUILD_DIR/klausurfragen.html" ]]; then
|
||||
slides_disabled="<span class=\"btn btn-slides btn-disabled\">Folien</span>"
|
||||
fi
|
||||
cat >> "$BUILD_DIR/index.html" << KLAUSURFRAGEN
|
||||
if [[ "${KLAUSURFRAGEN_ACTIVE[$COURSE]}" == "1" ]]; then
|
||||
pdf_active=""
|
||||
if [[ -f "$BUILD_DIR/klausurfragen.pdf" ]]; then
|
||||
pdf_active="<a href=\"klausurfragen.pdf\" class=\"btn btn-pdf\">PDF</a>"
|
||||
fi
|
||||
slides_active=""
|
||||
if [[ -f "$BUILD_DIR/klausurfragen.html" ]]; then
|
||||
slides_active="<a href=\"klausurfragen.html\" class=\"btn btn-slides\">Folien</a>"
|
||||
fi
|
||||
cat >> "$BUILD_DIR/index.html" << KLAUSURFRAGEN
|
||||
<div class="kapitel-card" style="border-left: 3px solid $ACCENT_COLOR;">
|
||||
<a href="klausurfragen.html" class="kapitel-link">
|
||||
<div class="kapitel-label">Prüfung</div>
|
||||
<div class="kapitel-title">Klausurfragen</div>
|
||||
</a>
|
||||
$slides_active
|
||||
$pdf_active
|
||||
</div>
|
||||
KLAUSURFRAGEN
|
||||
else
|
||||
pdf_disabled=""
|
||||
if [[ -f "$BUILD_DIR/klausurfragen.pdf" ]]; then
|
||||
pdf_disabled="<span class=\"btn btn-pdf btn-disabled\">PDF</span>"
|
||||
fi
|
||||
slides_disabled=""
|
||||
if [[ -f "$BUILD_DIR/klausurfragen.html" ]]; then
|
||||
slides_disabled="<span class=\"btn btn-slides btn-disabled\">Folien</span>"
|
||||
fi
|
||||
cat >> "$BUILD_DIR/index.html" << KLAUSURFRAGENOFF
|
||||
<div class="kapitel-card disabled" style="border-left: 3px solid $ACCENT_COLOR;">
|
||||
<div class="kapitel-link">
|
||||
<div class="kapitel-label">Prüfung</div>
|
||||
@@ -357,18 +487,18 @@ if [[ -f "$BUILD_DIR/klausurfragen.html" ]] || [[ -f "$BUILD_DIR/klausurfragen.p
|
||||
$slides_disabled
|
||||
$pdf_disabled
|
||||
</div>
|
||||
KLAUSURFRAGEN
|
||||
KLAUSURFRAGENOFF
|
||||
fi
|
||||
fi
|
||||
|
||||
cat >> "$BUILD_DIR/index.html" << FOOTER
|
||||
</div>
|
||||
<div class="qr-section">
|
||||
<img src="qr-index.svg" alt="QR Code" class="qr-code">
|
||||
<img src="$QR_IMG" alt="QR Code" class="qr-code">
|
||||
<p class="qr-url">$SLIDES_URL</p>
|
||||
</div>
|
||||
<footer>
|
||||
<a href="mailto:mail@librete.ch">Kontakt</a> ·
|
||||
<a href="../">Kursübersicht</a>
|
||||
<a href="$KONTAKT_URL">$KONTAKT_LABEL</a>$FOOTER_KURS_HTML
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,6 +9,7 @@ title: Dateiformate, Schnittstellen, Speichermedien & Distributionswege
|
||||
---
|
||||
|
||||
<style>
|
||||
|
||||
:root {
|
||||
--color-foreground: #1a1a2e;
|
||||
--color-highlight: #1e5f8a;
|
||||
@@ -75,11 +76,6 @@ section.erklaerung :not(footer)
|
||||
{
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
@media print {
|
||||
section.erklaerung {
|
||||
background: #e3f2fd !important;
|
||||
}
|
||||
}
|
||||
section.erklaerung h1 {
|
||||
font-size: 1.5rem;
|
||||
color: #1e5f8a;
|
||||
@@ -353,7 +349,7 @@ dann **150 Mbit/s ÷ 8** = **18,75 MB/s**
|
||||
**50 MB Upload bei 10 Mbit/s = ~40 Sekunden**
|
||||
|
||||
<!--
|
||||
- ADSL (Asymmetric Digital Subscriber Line, ITU = International Telecommunication Union, Norm G.992.1, 1999): Upload bewusst begrenzt — Annahme war, Nutzer konsumieren mehr als sie produzieren
|
||||
- ADSL (Asymmetric Digital Subscriber Line, ITU = International Telecommunication Union, Norm G.992.1, 1999): Upload bewusst begrenzt — Annahme war, Nutzende konsumieren mehr als sie produzieren
|
||||
- FTTH = Fiber To The Home (Glasfaser bis in die Wohnung)
|
||||
- Glasfaser (FTTH) symmetrisch: Upload = Download
|
||||
- FTTH-Verfügbarkeit Deutschland Ende 2024: ~40% der Haushalte (Bundesnetzagentur, Breitbandatlas)
|
||||
@@ -439,7 +435,7 @@ Jede Messung = ein Zahlenwert
|
||||
<!--
|
||||
- Dynamikumfang Formel: ~6 dB pro Bit
|
||||
- 16 Bit reicht für menschliches Hören unter realen Bedingungen (~80–90 dB)
|
||||
- 24 Bit im Studio: mehr Headroom für Bearbeitung, kein perzipieller Mehrwert für Endnutzer (Meyer & Moran, JAES 2007)
|
||||
- 24 Bit im Studio: mehr Headroom für Bearbeitung, kein perzipieller Mehrwert für Endnutzende (Meyer & Moran, JAES 2007)
|
||||
- dB = Dezibel: logarithmische Einheit für Lautstärke
|
||||
-->
|
||||
|
||||
@@ -561,7 +557,7 @@ RECHNUNG:
|
||||
|
||||
- CD (1982, Sony/Philips): erstes massenmarktfähiges digitales Distributionsmedium für unkomprimiertes PCM-Audio
|
||||
- PCM (Pulse-Code Modulation) existierte bereits vorher: NHK (Nippon Hōsō Kyōkai = japanische öffentlich-rechtliche Rundfunkanstalt)-Forschung ab 1960er, digitale Studiogeräte ab 1970er
|
||||
- Die CD war nicht das erste digitale Audio — aber das erste das in Konsumentenhänden landete
|
||||
- Die CD war nicht das erste digitale Audio — aber das erste das in KonsumentInnen-Hand landete
|
||||
-->
|
||||
|
||||
---
|
||||
@@ -600,11 +596,12 @@ RECHNUNG:
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: erklaerung
|
||||
<!--
|
||||
_class: erklaerung
|
||||
|
||||
# CD-Audio – Vertiefung
|
||||
|
||||
Die CD (1982) war nicht das erste digitale Audiomedium — PCM-Aufnahmen existierten seit den späten 1960ern in Tonstudios (u.a. NHK, Nippon Columbia). Die CD war das erste **massenmarktfähige** digitale Distributionsmedium für unkomprimiertes Audio in Konsumentenhänden.
|
||||
Die CD (1982) war nicht das erste digitale Audiomedium — PCM-Aufnahmen existierten seit den späten 1960ern in Tonstudios (u.a. NHK, Nippon Columbia). Die CD war das erste **massenmarktfähige** digitale Distributionsmedium für unkomprimiertes Audio in KonsumentInnen-Hand.
|
||||
|
||||
**Warum 44.100 Hz?**
|
||||
Kombination aus Nyquist-Limit (2× 20 kHz = 40 kHz Minimum) und Kompatibilität mit Videobandgeräten, die in frühen digitalen Tonstudios als Speichermedium genutzt wurden. NTSC: 3 Samples × 245 Zeilen × 60 Felder = 44.100. PAL: 3 Samples × 294 Zeilen × 50 Felder = 44.100.
|
||||
@@ -847,7 +844,7 @@ KLAUSURRELEVANT:
|
||||
<!--
|
||||
---
|
||||
|
||||
_class: erklaerung
|
||||
_class: erklaerung
|
||||
|
||||
# Kompression – Vertiefung
|
||||
|
||||
@@ -1226,6 +1223,58 @@ Sog. RGB Tuple (geordnete endliche Liste)
|
||||
|
||||
---
|
||||
|
||||
# Wo begegnet Ihnen Hex-Code?
|
||||
|
||||
| Kontext | Beispiel |
|
||||
|---------|----------|
|
||||
| CSS-Farben | `#FF5733` |
|
||||
| MAC-Adressen | `00:1A:2B:3C:4D:5E` |
|
||||
| Fehlercodes | `0x80070005` |
|
||||
| Speicheradressen | `0xA04F20` |
|
||||
| Unicode | `U+00E4` (ä) |
|
||||
| Datei-Signaturen | `89 50 4E 47` (PNG) |
|
||||
|
||||
<!--
|
||||
Präfixe:
|
||||
- 0x = "das ist Hexadezimal" (C, JavaScript, Python)
|
||||
- U+ = Unicode-Codepoint (Standard für Zeichenkodierung)
|
||||
- # = CSS-Konvention für Farben
|
||||
|
||||
ABKÜRZUNGEN:
|
||||
- MAC = Media Access Control (eindeutige Hardware-Adresse einer Netzwerkkarte)
|
||||
- Unicode = universeller Zeichensatz für alle Schriftsysteme
|
||||
|
||||
Speicheradressen erklärt:
|
||||
- 64-Bit-System → Adresse hat 64 Bit = 16 Hex-Ziffern
|
||||
- Beispiel vollständig: 0x0000000000A04F20 (führende Nullen weggelassen)
|
||||
- "0x" = Präfix, sagt nur "jetzt kommt Hex"
|
||||
|
||||
Fehlercodes: Windows zeigt diese bei Bluescreens
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
# Bit vs. Byte: Die Verwirrung
|
||||
|
||||
**1 Byte = 8 Bit** → Bit ÷ 8 = Byte
|
||||
|
||||
| Einheit (Bit) | Einheit (Byte) |
|
||||
|---------------|----------------|
|
||||
| 1 Kbit = 1.000 Bit | 1 KB = 1.000 Byte = 8.000 Bit |
|
||||
| 1 Mbit = 1.000.000 Bit | 1 MB = 1.000.000 Byte = 8 Mbit |
|
||||
| 1 Gbit = 1 Mrd. Bit | 1 GB = 1 Mrd. Byte = 8 Gbit |
|
||||
|
||||
100 Mbit/s Bandbreite = **12,5 MB/s** Downloadrate
|
||||
|
||||
<!--
|
||||
Bits = kleines b, Bytes = großes B
|
||||
Internetanbieter: Mbit/s (klingt größer!)
|
||||
100 Mbit/s ÷ 8 = 12,5 MB/s
|
||||
Marketing - 100 klingt besser als 12,5
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: lead -->
|
||||
|
||||
# ASCII
|
||||
@@ -1691,7 +1740,7 @@ Fangfrage: "Wie hoch ist die Sample Rate von Vinyls?" -> Vinyl has no sample rat
|
||||
<!--
|
||||
- Dynamikumfang Formel: ~6 dB pro Bit
|
||||
- 16 Bit reicht für menschliches Hören unter realen Bedingungen
|
||||
- 24 Bit im Studio: mehr Headroom für Bearbeitung, kein perzipieller Mehrwert für Endnutzer (Meyer & Moran, JAES 2007)
|
||||
- 24 Bit im Studio: mehr Headroom für Bearbeitung, kein perzipieller Mehrwert für Endnutzende (Meyer & Moran, JAES 2007)
|
||||
-->
|
||||
|
||||
---
|
||||
@@ -1896,7 +1945,7 @@ Ein Zusammenspiel aus vielen Faktoren:
|
||||
**P2P-Filesharing für MP3s**
|
||||
|
||||
- Shawn Fanning, 19 Jahre alt
|
||||
- 80 Millionen User in 2 Jahren
|
||||
- 80 Millionen Nutzende in 2 Jahren
|
||||
- Musikindustrie verklagt (2001)
|
||||
- Pandora's Box: Nicht mehr aufzuhalten
|
||||
|
||||
@@ -1913,7 +1962,7 @@ Ein Zusammenspiel aus vielen Faktoren:
|
||||
# Napster & Musikindustrie
|
||||
|
||||
**1999:** Napster startet
|
||||
**2001:** 80 Millionen User
|
||||
**2001:** 80 Millionen Nutzende
|
||||
|
||||
**Musikindustrie:**
|
||||
- CDs kosten $15–20
|
||||
@@ -2007,19 +2056,21 @@ Vollständige Lizenz: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
# Selbstlernen: HEX Files
|
||||
|
||||
1. Drei Dateien ohne Dateiendung:
|
||||
<a href="../materials/wtf1"/>`hex1`</a> <a href="../materials/wtf2"/>`hex2`</a> <a href="../materials/wtf3"/>`hex3`</a>
|
||||
3. Lies erste 16 Bytes aus und identifiziere Dateiformat (Magic Number)
|
||||
5. *Optional: Datei umbenennen und korrekte Dateiendung anhängen (bspw. `.jpg`)*
|
||||
1. Fünf Dateien ohne Dateiendung:
|
||||
<a href="https://librete.ch/hdm/223015b/materials/hex1">`hex1`</a> · <a href="https://librete.ch/hdm/223015b/materials/hex2">`hex2`</a> · <a href="https://librete.ch/hdm/223015b/materials/hex3">`hex3`</a> · <a href="https://librete.ch/hdm/223015b/materials/hex4">`hex4`</a> · <a href="https://librete.ch/hdm/223015b/materials/hex5">`hex5`</a>
|
||||
2. Lies erste 16 Byte aus und identifiziere Dateiformat (Magic Number)
|
||||
3. *Optional: Datei umbenennen und korrekte Dateiendung anhängen (bspw. `.jpg`)*
|
||||
|
||||
**Tools:**
|
||||
- Hex-Editor: [hexed.it](https://hexed.it)
|
||||
- Magic Numbers: [en.wikipedia.org/wiki/List_of_file_signatures](https://en.wikipedia.org/wiki/List_of_file_signatures)
|
||||
|
||||
<!--
|
||||
- wtf1: Plaintext (keine Magic Number)
|
||||
- wtf2: PNG (89 50 4E 47)
|
||||
- wtf3: JPEG (FF D8 FF)
|
||||
- hex1: Plaintext (keine Magic Number)
|
||||
- hex2: PNG (89 50 4E 47)
|
||||
- hex3: JPEG (FF D8 FF)
|
||||
- hex4: DOCX (50 4B 03 04 — ZIP-Container)
|
||||
- hex5: ZIP (50 4B 03 04)
|
||||
- Gruppenarbeit: 3-4 Personen
|
||||
- Ziel: Hex-Dump lesen lernen, Dateiformate verstehen
|
||||
-->
|
||||
-->
|
||||
@@ -72,18 +72,6 @@ section.aufgabe footer {
|
||||
}
|
||||
section.erklaerung {
|
||||
font-size: 1.1rem;
|
||||
background: repeating-linear-gradient(
|
||||
135deg,
|
||||
#e3f2fd,
|
||||
#e3f2fd 40px,
|
||||
#fff 40px,
|
||||
#fff 80px
|
||||
) !important;
|
||||
}
|
||||
@media print {
|
||||
section.erklaerung {
|
||||
background: #e3f2fd !important;
|
||||
}
|
||||
}
|
||||
section.erklaerung h1 {
|
||||
font-size: 1.5rem;
|
||||
@@ -232,7 +220,7 @@ Jeder Pixel speichert einen RGB-Farbwert (3 Bytes).
|
||||
**Aufbau:** Liste von Pixeln mit Farbwerten (2D-Array)
|
||||
|
||||
**Speicherbedarf (unkomprimiert):**
|
||||
Breite × Höhe × Farbtiefe (in Bytes)
|
||||
Breite × Höhe × Farbtiefe (in Byte)
|
||||
|
||||
**Beispiele:** JPEG, PNG, WebP
|
||||
|
||||
@@ -373,6 +361,15 @@ Vektorgrafiken speichern keine Pixel, sondern mathematische Beschreibungen: Koor
|
||||
- Frage: "Warum verschickt euch der Grafiker das Logo als .ai oder .svg?"
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: lead -->
|
||||
@@ -386,6 +383,14 @@ Vektorgrafiken speichern keine Pixel, sondern mathematische Beschreibungen: Koor
|
||||
- Wir nutzen seine Schwächen aus
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
<!-- _class: klausur -->
|
||||
@@ -510,6 +515,16 @@ Das Auge ist ein Tiefpassfilter: Hohe Frequenzen (feine Details) werden schwäch
|
||||
- Interaktive Demo: https://claude.ai/public/artifacts/452e62f1-8525-442d-aa88-bf3795cab2c5
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: klausur -->
|
||||
@@ -519,7 +534,7 @@ Das Auge ist ein Tiefpassfilter: Hohe Frequenzen (feine Details) werden schwäch
|
||||
|
||||

|
||||
|
||||
# JPEG Schritt 1: Farbraumkonversion
|
||||
# JPEG Schritt 1: Farbraumkonvertierung
|
||||
|
||||
**RGB → Y'CbCr**
|
||||
|
||||
@@ -542,11 +557,33 @@ KLAUSURRELEVANT:
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
<!-- _class: erklaerung -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||
# Farbraumkonversion – Vertiefung
|
||||
# Farbraumkonvertierung – Vertiefung
|
||||
|
||||
RGB→YCbCr nutzt die Biologie des menschlichen Auges: 120 Mio. Stäbchen (Helligkeit) vs. nur 6 Mio. Zapfen (Farbe) – ein 20:1-Verhältnis. Die Transformation erfolgt über eine lineare Matrix:
|
||||
|
||||
@@ -558,7 +595,7 @@ Cr = 0.500·R − 0.419·G − 0.081·B + 128
|
||||
|
||||
Die Gewichtung (G dominiert mit 59%) entspricht der spektralen Empfindlichkeit des Auges bei Tageslicht. Y enthält alle Schärfeinformation; Cb/Cr können reduziert werden.
|
||||
|
||||
**Chroma Subsampling – Notation J:a:b** (bezogen auf 4×2 Pixel):
|
||||
**Chroma Subsampling – Notation `J:a:b`** (bezogen auf 4×2 Pixel):
|
||||
|
||||
| Schema | Farbdaten | Einsatz |
|
||||
|--------|-----------|---------|
|
||||
@@ -725,11 +762,6 @@ KLAUSURRELEVANT:
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: klausur -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
<!-- _backgroundColor: #e3f2fd -->
|
||||
|
||||
# JPEG Schritt 6: Huffman-Coding
|
||||
|
||||
**Verlustfreie Kompression der Restwerte**
|
||||
@@ -782,37 +814,10 @@ JPEG verwendet zwei Huffman-Tabellen: eine für DC-Koeffizienten (Durchschnittsw
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: klausur -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
<!-- _backgroundColor: #e3f2fd -->
|
||||
<!--
|
||||
# Huffman-Coding: Beispiel
|
||||
|
||||
**Originaltext:** `ABRACADABRA` (11 Zeichen × 8 Bit = 88 Bit)
|
||||
|
||||
**Häufigkeitsanalyse:**
|
||||
A=5, B=2, R=2, C=1, D=1
|
||||
|
||||
**Huffman-Baum → Codes:**
|
||||
| Zeichen | Häufigkeit | Code |
|
||||
|---------|------------|------|
|
||||
| A | 5 | `0` |
|
||||
| B | 2 | `10` |
|
||||
| R | 2 | `110` |
|
||||
| C | 1 | `1110` |
|
||||
| D | 1 | `1111` |
|
||||
|
||||
**Codiert:** `0 10 110 0 1110 0 1111 0 10 110 0` = **23 Bit**
|
||||
**Kompression:** 88 → 23 Bit = **74% gespart**
|
||||
|
||||
|
||||
- Beispiel Schritt für Schritt durchrechnen
|
||||
- Warum funktioniert's? A kommt 5× vor, bekommt kürzesten Code
|
||||
- Präfix-Eigenschaft: Kein Code ist Anfang eines anderen → eindeutig dekodierbar
|
||||
- Frage: "Was passiert, wenn alle Zeichen gleich häufig sind?" → Keine Ersparnis
|
||||
- In JPEG: Nicht Buchstaben, sondern DCT-Koeffizienten werden so codiert
|
||||
-->
|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -836,8 +841,6 @@ A=5, B=2, R=2, C=1, D=1
|
||||
# Andere Bildformate
|
||||
## PNG, GIF, WebP, AVIF
|
||||
|
||||
15:33 Uhr weiter
|
||||
|
||||
<!--
|
||||
- JPEG ist nicht für alles geeignet
|
||||
- Jetzt: Alternativen und ihre Stärken
|
||||
@@ -960,7 +963,7 @@ KLAUSURRELEVANT:
|
||||
<!--
|
||||
- Kompatibilität schlägt oft Effizienz
|
||||
- TIFF für Archivierung: Unkomprimiert oder verlustfrei
|
||||
- RAW für Fotografen: Maximale Bearbeitungsfreiheit
|
||||
- RAW für FotografInnen: Maximale Bearbeitungsfreiheit
|
||||
- Frage: "Was passiert, wenn ihr ein PNG auf Instagram hochladet?"
|
||||
-->
|
||||
|
||||
@@ -996,8 +999,8 @@ KLAUSURRELEVANT:
|
||||
- Instagram ist keine Kunstgalerie, sondern Massenplattform
|
||||
- Optimiert für Geschwindigkeit, nicht Qualität
|
||||
- Facebook, Twitter, TikTok – alle machen das
|
||||
- Tipp für Fotografen: Portfolio auf eigener Website
|
||||
- Diskussion: Ist das fair? Trade-off Nutzer vs. Plattform
|
||||
- Tipp für FotografInnen: Portfolio auf eigener Website
|
||||
- Diskussion: Ist das fair? Trade-off Nutzende vs. Plattform
|
||||
-->
|
||||
|
||||
---
|
||||
@@ -1074,7 +1077,7 @@ KLAUSURRELEVANT:
|
||||
|
||||
**Codec** (Coder-Decoder) definiert den Kompressionsalgorithmus. Derselbe Container kann verschiedene Codecs enthalten – die Dateiendung verrät den Codec nicht.
|
||||
|
||||
| Container | Entwickler | Typische Codecs | Besonderheit |
|
||||
| Container | Entwicklung | Typische Codecs | Besonderheit |
|
||||
|-----------|------------|-----------------|--------------|
|
||||
| MP4 | ISO/MPEG | H.264, H.265, AAC | Web-Standard, DRM-fähig |
|
||||
| MKV | Matroska | Alle | Beliebig viele Streams, Kapitel |
|
||||
@@ -1129,29 +1132,10 @@ KLAUSURRELEVANT:
|
||||
|
||||
---
|
||||
|
||||
# Container + Codec = Video
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ Container (z.B. MP4) │
|
||||
│ ┌────────────────────────┐ │
|
||||
│ │ Video-Stream (H.264) │ │
|
||||
│ ├────────────────────────┤ │
|
||||
│ │ Audio-Stream (AAC) │ │
|
||||
│ ├────────────────────────┤ │
|
||||
│ │ Untertitel (SRT) │ │
|
||||
│ ├────────────────────────┤ │
|
||||
│ │ Metadaten │ │
|
||||
│ └────────────────────────┘ │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
<!--
|
||||
- Container = Verpackung
|
||||
- Codec = wie der Inhalt komprimiert ist
|
||||
- Ein MP4 mit H.264 und ein MP4 mit H.265 haben dieselbe Endung
|
||||
- Wichtig für Kompatibilität: Player muss Codec unterstützen
|
||||
-->
|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -1338,7 +1322,7 @@ H.264 (2003) ermöglichte erst YouTube (2005), Netflix-Streaming (2007) und Blu-
|
||||
|
||||
**Hardware-Ubiquität:** Seit 2010 hat jedes Smartphone, jede GPU, jeder Smart-TV einen H.264-Hardware-Decoder. Encoding in Echtzeit braucht keine CPU – das ermöglichte erst mobiles Video-Streaming und Videotelefonie.
|
||||
|
||||
**Patent-Pool (MPEG-LA):** ~2.000 Patente von 30+ Unternehmen. Endnutzer-Streaming ist lizenzfrei; Hardware-Hersteller zahlen ~$0,20/Gerät.
|
||||
**Patent-Pool (MPEG-LA):** ~2.000 Patente von 30+ Unternehmen. Endnutzungs-Streaming ist lizenzfrei; Hardware-Hersteller zahlen ~$0,20/Gerät.
|
||||
|
||||
---
|
||||
|
||||
@@ -1359,7 +1343,7 @@ H.264 (2003) ermöglichte erst YouTube (2005), Netflix-Streaming (2007) und Blu-
|
||||
<!--
|
||||
- MPEG-LA = Licensing Authority
|
||||
- Firefox weigerte sich lange, H.264 zu unterstützen
|
||||
- "Free to View" gilt nur für Endnutzer-Streaming
|
||||
- "Free to View" gilt nur für Endnutzungs-Streaming
|
||||
- Diskussion: Sollten Standards patent-frei sein?
|
||||
-->
|
||||
|
||||
|
||||
@@ -72,18 +72,6 @@ section.aufgabe footer {
|
||||
}
|
||||
section.erklaerung {
|
||||
font-size: 1.1rem;
|
||||
background: repeating-linear-gradient(
|
||||
135deg,
|
||||
#e3f2fd,
|
||||
#e3f2fd 40px,
|
||||
#fff 40px,
|
||||
#fff 80px
|
||||
) !important;
|
||||
}
|
||||
@media print {
|
||||
section.erklaerung {
|
||||
background: #e3f2fd !important;
|
||||
}
|
||||
}
|
||||
section.erklaerung h1 {
|
||||
font-size: 1.5rem;
|
||||
@@ -131,7 +119,7 @@ Hochschule der Medien Stuttgart
|
||||
|
||||
<!-- _class: lead -->
|
||||
|
||||
# Kapitel 3 – 23.01.2026
|
||||
# Kapitel 3
|
||||
## Speichermedien & Schnittstellen
|
||||
|
||||
---
|
||||
@@ -166,7 +154,7 @@ Hersteller: Dezimal (SI-Präfix) - klingt mehr!
|
||||
Betriebssysteme: Binär - aber zeigen "GB" statt "GiB"
|
||||
Diskrepanz bei 1 TB: 1.000.000.000.000 vs. 1.099.511.627.776 Bytes
|
||||
→ 10% Unterschied bei TB-Größen
|
||||
Verwirrung für Konsumenten seit Jahrzehnten
|
||||
Verwirrung für KonsumentInnen seit Jahrzehnten
|
||||
ISO/IEC 80000-13: Definiert KiB, MiB, GiB, TiB (2008)
|
||||
-->
|
||||
|
||||
@@ -381,8 +369,7 @@ Seitdem native Linux-Unterstützung.
|
||||
**Features:**
|
||||
- Journaling (Crash-Sicherheit)
|
||||
- Dateirechte (ACLs)
|
||||
- Kompression, Verschlüsselung
|
||||
- Große Dateien und Volumes
|
||||
- Kompression, Verschlüsselung, große Dateien und Volumes
|
||||
|
||||
**Nachteile:**
|
||||
- Nur Windows schreibt nativ
|
||||
@@ -604,7 +591,7 @@ Voll: Langsamstes Backup, schnellste Wiederherstellung
|
||||
|
||||
<!--
|
||||
Time Machine: Stündliche Snapshots, APFS-Integration
|
||||
Veeam: Enterprise-Level, kostenlose Endnutzer-Version
|
||||
Veeam: Enterprise-Level, kostenlose Privatversion
|
||||
rsync: Unix-Klassiker, extrem effizient
|
||||
Borg: Deduplizierung + Verschlüsselung
|
||||
-->
|
||||
@@ -641,11 +628,6 @@ Millionen von Websites und Spielen verloren.
|
||||
- 30 Jahre Haltbarkeit
|
||||
- Für Cold Storage ideal
|
||||
|
||||
**M-DISC:**
|
||||
- Spezielle DVD/Blu-ray
|
||||
- 1.000+ Jahre Haltbarkeit (Herstellerangabe)
|
||||
- Für kleine, wichtige Daten
|
||||
|
||||
**Cloud:**
|
||||
- Glacier, Backblaze B2
|
||||
- Günstig für Langzeit
|
||||
|
||||
@@ -160,7 +160,7 @@ AWS Snowmobile: 45-Fuß-Container auf LKW
|
||||
|
||||
**Prozess:**
|
||||
1. AWS schickt verschlüsseltes Gerät
|
||||
2. Kunde kopiert Daten lokal (schnell!)
|
||||
2. KundIn kopiert Daten lokal (schnell!)
|
||||
3. Gerät zurück an AWS
|
||||
4. AWS lädt in S3 hoch
|
||||
|
||||
@@ -220,7 +220,7 @@ Server unter Last (symbolisch)
|
||||
**Klassisches Modell:** Ein Server, viele Clients
|
||||
|
||||
**Problem:**
|
||||
1 Million User wollen 1 GB-Datei
|
||||
1 Million Nutzende wollen 1 GB-Datei
|
||||
→ Server braucht **1 PB Bandbreite!**
|
||||
→ Server überlastet → **"Hug of Death"**
|
||||
|
||||
@@ -252,7 +252,7 @@ Globale Verteilung
|
||||
**Funktionsweise:**
|
||||
1. **Origin Server** (Hauptquelle)
|
||||
2. **Edge Servers** (geografisch verteilt)
|
||||
3. User → nächster Edge Server
|
||||
3. Nutzende → nächster Edge Server
|
||||
4. Erste Anfrage: Edge holt von Origin, cached
|
||||
5. Weitere Anfragen: Direkt vom Edge (schnell!)
|
||||
|
||||
@@ -277,7 +277,7 @@ Große CDN-Anbieter: Cloudflare, Akamai, Amazon CloudFront, Fastly
|
||||
- Lange Cache-Zeit (TTL: Tage/Wochen)
|
||||
|
||||
**Dynamic Content:**
|
||||
- User-spezifisch (Profil)
|
||||
- Nutzungs-spezifisch (Profil)
|
||||
- Kurze TTL oder nicht cachebar
|
||||
|
||||
**Cache Invalidation:**
|
||||
@@ -349,14 +349,14 @@ Dezentral, jeder ist Client & Server
|
||||
- IPFS (InterPlanetary File System)
|
||||
- Blockchain (Bitcoin, Ethereum)
|
||||
|
||||
**Vorteil:** Skalierbar (mehr User = mehr Bandbreite!)
|
||||
**Vorteil:** Skalierbar (mehr Nutzende = mehr Bandbreite!)
|
||||
|
||||
**Nachteil:** Langsam bei wenigen Peers, oft für Piraterie missbraucht
|
||||
|
||||
<!--
|
||||
P2P vs. Client-Server: Kein zentraler Server
|
||||
Last verteilt sich auf alle Teilnehmer
|
||||
Je mehr User, desto schneller (umgekehrt zu Client-Server!)
|
||||
Last verteilt sich auf alle Teilnehmenden
|
||||
Je mehr Nutzende, desto schneller (umgekehrt zu Client-Server!)
|
||||
Zensur-resistent: Kein Single Point of Failure
|
||||
Rechtliche Grauzone: Technologie neutral, aber oft illegal genutzt
|
||||
-->
|
||||
@@ -1023,8 +1023,8 @@ Privacy-Albtraum
|
||||
|
||||
**Beispiele:**
|
||||
- **Foto:** Kamera, Datum, GPS, Belichtung
|
||||
- **MP3:** Künstler, Album, Jahr, Genre, Cover
|
||||
- **PDF:** Autor, Datum, Software
|
||||
- **MP3:** KünstlerInnen, Album, Jahr, Genre, Cover
|
||||
- **PDF:** AutorIn, Datum, Software
|
||||
- **E-Mail:** Absender, Empfänger, Zeitstempel
|
||||
|
||||
**Warum wichtig?**
|
||||
@@ -1180,7 +1180,7 @@ Wikipedia für Musik-Metadaten
|
||||
**Idee:** Wikipedia für Musik-Metadaten
|
||||
|
||||
**Community-gepflegt:**
|
||||
- Künstler, Alben, Tracks
|
||||
- KünstlerInnen, Alben, Tracks
|
||||
- Relationships (Band-Mitglieder, Label...)
|
||||
- Releases (verschiedene Editionen, Länder)
|
||||
|
||||
@@ -1239,7 +1239,7 @@ Versteckte Informationen sichtbar
|
||||
**PDF-Metadaten (XMP):**
|
||||
|
||||
**Gespeichert:**
|
||||
- Titel, Autor, Betreff, Keywords
|
||||
- Titel, AutorIn, Betreff, Keywords
|
||||
- Erstellungsdatum, Änderungsdatum
|
||||
- Software, **Company** (aus Office-Lizenz!)
|
||||
|
||||
@@ -1600,7 +1600,7 @@ QKD: Abhör-sichere Kommunikation (Quantenmechanik garantiert)
|
||||
|
||||
**Filecoin (2017):**
|
||||
- Blockchain-basiert
|
||||
- User vermieten Festplatten-Platz
|
||||
- Nutzende vermieten Festplatten-Platz
|
||||
- Bezahlung in FIL (Kryptowährung)
|
||||
|
||||
**Arweave (2018):**
|
||||
@@ -1638,7 +1638,7 @@ Legitime Use-Cases: NFT-Storage, Zensur-resistente Archivierung
|
||||
- 5G + Edge Computing nötig
|
||||
|
||||
**Cloud Gaming:**
|
||||
- Spiel im Rechenzentrum, Stream zu User
|
||||
- Spiel im Rechenzentrum, Stream zu Nutzenden
|
||||
- Input-Lag = Todfeind (<50ms)
|
||||
|
||||
**Problem:** Physik (Lichtgeschwindigkeit!)
|
||||
@@ -1647,7 +1647,7 @@ Legitime Use-Cases: NFT-Storage, Zensur-resistente Archivierung
|
||||
8K: YouTube unterstützt, aber wer hat 8K-Monitor?
|
||||
VR: Meta Quest, Apple Vision Pro
|
||||
Latenz: Lichtgeschwindigkeit ~300.000 km/s, aber Routing + Processing addiert sich
|
||||
Edge Computing: Server näher an User (5G-Masten)
|
||||
Edge Computing: Server näher an Nutzende (5G-Masten)
|
||||
Cloud Gaming: Stadia gescheitert (2023 †), GeForce Now, Xbox Cloud
|
||||
Input-Lag: Fiber ~5ms/1000km, aber Server-Processing + Encoding + Decoding
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# CLAUDE.md — 223015b lecture deck
|
||||
|
||||
Marp lecture slides + master question catalog for HdM Stuttgart course **223015b — „Dateiformate, Schnittstellen, Speichermedien & Distributionswege"**. Author: Michael Czechowski, SoSe 2026. This directory is the **source of truth**; the exam artifacts (Moodle XML) are derived downstream (see [Downstream](#downstream)).
|
||||
|
||||
## Files
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `00-intro.md` | Intro / organizational deck |
|
||||
| `01-grundlagen-text-audio.md` | Grundlagen: Text & Audio |
|
||||
| `02-bild-audio-video.md` | Bild, Audio, Video |
|
||||
| `03-speichermedien-schnittstellen.md` | Speichermedien & Schnittstellen |
|
||||
| `04-distribution-apis-zukunft.md` | Distribution, APIs, Zukunft |
|
||||
| `05-vertiefung-offene-fragen.md` | Vertiefung / offene Fragen |
|
||||
| `klausurfolien.md` | Exam-review slides |
|
||||
| `klausurfragen.md` | **Master question catalog — 122 Fragen** (`[MC]/[MM]/[MATCH]/[ORDER]/[ESSAY]/[SHORTANS]/[NUMERIC]/[CLOZE]`) |
|
||||
| `assets/`, `materials/` | Images and supporting material |
|
||||
|
||||
All decks are **Marp** Markdown (`marp: true`, theme `gaia`) with a shared inline `<style>` block. Highlight color for this course: `#1e5f8a` (blue). Sibling deck: `../223015c/` (Internettechnik, highlight `#d63384`).
|
||||
|
||||
## Conventions
|
||||
|
||||
- Each question in `klausurfragen.md` = `### <ID> – <title>` + `**Thema:**` + `**Punkte:**` + `**Typ:**` + body + `> **Feedback:**`/`> **Musterlösung:**`. Keep this shape — downstream tooling parses `**Thema:**` and `**Typ:**`.
|
||||
- Correct MC options marked `- [x] **…** ✅`; matching as a 2-column table; ordering as a numbered list.
|
||||
- `marp` CLI renders to HTML/PDF: `marp <file>.md` (see marp-team/marp-cli).
|
||||
|
||||
## Downstream
|
||||
|
||||
The **exam question bank + Moodle Quiz XML** live in a separate repo:
|
||||
|
||||
- Local: `/home/mwc/Documents/HDM/klausur/`
|
||||
- Git: `ssh://tengo@git.librete.ch:41240/libretech/2026-hdm-klausur.git`
|
||||
|
||||
There, `klausurfragen-b-course-dateiformate.md` is a **curated subset (81/122)** of this deck's `klausurfragen.md`, and the questions are exported as Moodle XML for import. **Edit questions here first**, then sync the subset + regenerate/patch the XML downstream (that repo's CLAUDE.md + `moodle-quiz-xml` skill document the XML format and workflow).
|
||||
|
After Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 284 KiB |
@@ -0,0 +1,205 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Aliasing — derselbe Fehler in zwei Sinnen</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--audio: #ea580c;
|
||||
--bild: #2563eb;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 30px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 24px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.pair {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 22px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.modality {
|
||||
border: 2px solid;
|
||||
border-radius: 12px;
|
||||
padding: 16px 18px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.modality.audio { border-color: var(--audio); }
|
||||
.modality.bild { border-color: var(--bild); }
|
||||
|
||||
.mod-head { display: flex; justify-content: space-between; align-items: baseline; }
|
||||
.mod-name { font-size: 1.25rem; font-weight: 700; }
|
||||
.modality.audio .mod-name { color: var(--audio); }
|
||||
.modality.bild .mod-name { color: var(--bild); }
|
||||
.mod-tag { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; color: var(--muted); }
|
||||
|
||||
.canvas {
|
||||
background: var(--soft);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.canvas svg { display: block; }
|
||||
|
||||
.scene-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||||
.scene { display: flex; flex-direction: column; gap: 4px; }
|
||||
.scene-label {
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.scene-cap {
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
line-height: 1.4;
|
||||
font-family: ui-monospace, monospace;
|
||||
}
|
||||
.scene-cap strong { color: var(--dark); }
|
||||
|
||||
.ear-eye {
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.5;
|
||||
color: var(--dark);
|
||||
}
|
||||
.ear-eye .arrow { color: var(--hl); font-weight: 700; }
|
||||
.ear-eye-headline {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.modality.audio .ear-eye-headline { color: var(--audio); }
|
||||
.modality.bild .ear-eye-headline { color: var(--bild); }
|
||||
|
||||
.verdict {
|
||||
margin-top: 22px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.1rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.93rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Aliasing — derselbe Fehler in zwei Sinnen</h1>
|
||||
<div class="sub">Was im Ohr <strong>Brummen</strong> macht, macht im Auge <strong>Moiré</strong>. Wurzel: Sampling zu grob.</div>
|
||||
|
||||
<div class="pair">
|
||||
|
||||
<div class="modality audio">
|
||||
<div class="mod-head">
|
||||
<span class="mod-name">Audio</span>
|
||||
<span class="mod-tag">Unterabtastung</span>
|
||||
</div>
|
||||
|
||||
<div class="scene-pair">
|
||||
<div class="scene">
|
||||
<div class="scene-label">Original (18 kHz)</div>
|
||||
<div class="canvas">
|
||||
<svg viewBox="0 0 280 80" width="100%" height="80" preserveAspectRatio="none">
|
||||
<line x1="0" y1="40" x2="280" y2="40" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3,4"/>
|
||||
<path d="M0,40 Q7,5 14,40 T28,40 T42,40 T56,40 T70,40 T84,40 T98,40 T112,40 T126,40 T140,40 T154,40 T168,40 T182,40 T196,40 T210,40 T224,40 T238,40 T252,40 T266,40 T280,40"
|
||||
stroke="#ea580c" stroke-width="2" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="scene-cap">20 Schwingungen<br>schnelle Welle</div>
|
||||
</div>
|
||||
<div class="scene">
|
||||
<div class="scene-label">Bei 22 kHz gesampelt</div>
|
||||
<div class="canvas">
|
||||
<svg viewBox="0 0 280 80" width="100%" height="80" preserveAspectRatio="none">
|
||||
<line x1="0" y1="40" x2="280" y2="40" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3,4"/>
|
||||
<path d="M0,40 Q7,5 14,40 T28,40 T42,40 T56,40 T70,40 T84,40 T98,40 T112,40 T126,40 T140,40 T154,40 T168,40 T182,40 T196,40 T210,40 T224,40 T238,40 T252,40 T266,40 T280,40"
|
||||
stroke="#fed7aa" stroke-width="1.2" fill="none"/>
|
||||
<path d="M0,40 Q70,10 140,40 T280,40"
|
||||
stroke="#ea580c" stroke-width="2.5" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="scene-cap"><strong>2 Schwingungen</strong><br>Brumm-Frequenz</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ear-eye">
|
||||
<div class="ear-eye-headline">Was das Ohr hört</div>
|
||||
Ein schriller 18-kHz-Ton wird zu einem dumpfen 4-kHz-Brumm. Das hohe ist <strong>nicht leiser</strong> — es ist <strong>verloren und falsch ersetzt</strong>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modality bild">
|
||||
<div class="mod-head">
|
||||
<span class="mod-name">Bild</span>
|
||||
<span class="mod-tag">Moiré-Effekt</span>
|
||||
</div>
|
||||
|
||||
<div class="scene-pair">
|
||||
<div class="scene">
|
||||
<div class="scene-label">Original (feine Streifen)</div>
|
||||
<div class="canvas">
|
||||
<svg viewBox="0 0 280 80" width="100%" height="80" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<pattern id="fine-real" x="0" y="0" width="3" height="80" patternUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="1.5" height="80" fill="#1e3a8a"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="280" height="80" fill="url(#fine-real)"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="scene-cap">93 Linien<br>volle Auflösung</div>
|
||||
</div>
|
||||
<div class="scene">
|
||||
<div class="scene-label">In niedriger DPI gerendert</div>
|
||||
<div class="canvas">
|
||||
<svg viewBox="0 0 280 80" width="100%" height="80" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id="moire-bands" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a8a"/>
|
||||
<stop offset="10%" stop-color="#bfdbfe"/>
|
||||
<stop offset="20%" stop-color="#1e3a8a"/>
|
||||
<stop offset="30%" stop-color="#bfdbfe"/>
|
||||
<stop offset="40%" stop-color="#1e3a8a"/>
|
||||
<stop offset="50%" stop-color="#bfdbfe"/>
|
||||
<stop offset="60%" stop-color="#1e3a8a"/>
|
||||
<stop offset="70%" stop-color="#bfdbfe"/>
|
||||
<stop offset="80%" stop-color="#1e3a8a"/>
|
||||
<stop offset="90%" stop-color="#bfdbfe"/>
|
||||
<stop offset="100%" stop-color="#1e3a8a"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="280" height="80" fill="url(#moire-bands)"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="scene-cap"><strong>5 breite Bänder</strong><br>Moiré-Frequenz</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ear-eye">
|
||||
<div class="ear-eye-headline">Was das Auge sieht</div>
|
||||
Feine Linien sind weg, dafür <strong>breite Streifen</strong>, die nie da waren. Aus 93 Linien werden 5 Bänder — das Bild lügt.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Aliasing kennt keine Modalität. Es ist ein <strong>mathematischer</strong> Fehler — kein technischer.</div>
|
||||
<div class="verdict-a">Egal ob Ton, Bild, Video, Radar, GPS — Sampling muss immer mindestens <strong>doppelt</strong> so dicht sein wie das Signal. Sonst tauscht die Realität Frequenzen aus.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 100 KiB |
@@ -0,0 +1,117 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Byte für Byte — drei Brillen</title>
|
||||
<style>
|
||||
:root {
|
||||
--hl: #d63384;
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--border: #cbd5e1;
|
||||
--bg-soft: #f8fafc;
|
||||
--bin-bg: #0f0f23;
|
||||
--bin-fg: #e5e7eb;
|
||||
--binary-mark: #ef4444;
|
||||
--ascii-ok: #16a34a;
|
||||
--ctrl: #9ca3af;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 28px 32px; }
|
||||
h1 { text-align: center; margin: 0 0 4px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 22px; font-size: 1rem; }
|
||||
.sub code { font-family: ui-monospace, "SF Mono", Menlo, monospace; }
|
||||
|
||||
.windows { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 18px; max-width: 1180px; margin: 0 auto; }
|
||||
|
||||
.window { border: 2px solid var(--border); border-radius: 10px; overflow: hidden; display: flex; flex-direction: column; }
|
||||
.window-header { background: var(--bg-soft); padding: 10px 14px; border-bottom: 2px solid var(--border); display: flex; flex-direction: column; gap: 2px; }
|
||||
.window-title { font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); }
|
||||
.window-name { font-size: 1.15rem; font-weight: 700; color: var(--dark); }
|
||||
|
||||
.window-body { padding: 14px 16px; flex: 1; display: flex; flex-direction: column; gap: 6px; font-family: ui-monospace, "SF Mono", Menlo, monospace; }
|
||||
|
||||
.window.bin .window-body { background: var(--bin-bg); }
|
||||
.window.bin .row { color: var(--bin-fg); font-size: 1.05rem; letter-spacing: 0.04em; }
|
||||
|
||||
.window.hex .row { color: var(--dark); font-size: 1.4rem; font-weight: 700; letter-spacing: 0.06em; }
|
||||
|
||||
.window.text .row { font-size: 1.4rem; font-weight: 700; display: flex; align-items: center; gap: 10px; }
|
||||
.window.text .glyph.printable { color: var(--ascii-ok); }
|
||||
.window.text .glyph.ctrl { color: var(--ctrl); font-size: 1.1rem; }
|
||||
.window.text .glyph.binary { color: var(--binary-mark); }
|
||||
.window.text .note { font-size: 0.78rem; font-weight: 400; color: var(--muted); margin-left: auto; }
|
||||
|
||||
.footnote { margin-top: 24px; max-width: 1100px; margin-left: auto; margin-right: auto; display: flex; gap: 32px; justify-content: center; font-size: 0.95rem; }
|
||||
.legend { display: flex; align-items: center; gap: 8px; }
|
||||
.swatch { display: inline-block; width: 16px; height: 16px; border-radius: 3px; }
|
||||
.swatch.binary { background: #fee2e2; border: 2px solid var(--binary-mark); }
|
||||
.swatch.ascii { background: #dcfce7; border: 2px solid var(--ascii-ok); }
|
||||
.verdict { margin-top: 14px; text-align: center; font-size: 1.05rem; color: var(--dark); }
|
||||
.verdict strong { color: var(--binary-mark); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Byte für Byte</h1>
|
||||
<div class="sub">Eine Datei — drei Brillen. Beispiel: erste 8 Byte einer PNG-Datei.</div>
|
||||
|
||||
<div class="windows">
|
||||
|
||||
<div class="window bin">
|
||||
<div class="window-header">
|
||||
<div class="window-title">Fenster 1</div>
|
||||
<div class="window-name">Bin · rohe 0 und 1</div>
|
||||
</div>
|
||||
<div class="window-body">
|
||||
<div class="row">10001001</div>
|
||||
<div class="row">01010000</div>
|
||||
<div class="row">01001110</div>
|
||||
<div class="row">01000111</div>
|
||||
<div class="row">00001101</div>
|
||||
<div class="row">00001010</div>
|
||||
<div class="row">00011010</div>
|
||||
<div class="row">00001010</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="window hex">
|
||||
<div class="window-header">
|
||||
<div class="window-title">Fenster 2</div>
|
||||
<div class="window-name">Hex · 2 Ziffern pro Byte</div>
|
||||
</div>
|
||||
<div class="window-body">
|
||||
<div class="row">89</div>
|
||||
<div class="row">50</div>
|
||||
<div class="row">4E</div>
|
||||
<div class="row">47</div>
|
||||
<div class="row">0D</div>
|
||||
<div class="row">0A</div>
|
||||
<div class="row">1A</div>
|
||||
<div class="row">0A</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="window text">
|
||||
<div class="window-header">
|
||||
<div class="window-title">Fenster 3</div>
|
||||
<div class="window-name">Text · ASCII oder ✗ (> 126)</div>
|
||||
</div>
|
||||
<div class="window-body">
|
||||
<div class="row"><span class="glyph binary">✗</span><span class="note">137 > 126</span></div>
|
||||
<div class="row"><span class="glyph printable">P</span><span class="note">80</span></div>
|
||||
<div class="row"><span class="glyph printable">N</span><span class="note">78</span></div>
|
||||
<div class="row"><span class="glyph printable">G</span><span class="note">71</span></div>
|
||||
<div class="row"><span class="glyph ctrl">CR</span><span class="note">13 · Steuerz.</span></div>
|
||||
<div class="row"><span class="glyph ctrl">LF</span><span class="note">10 · Steuerz.</span></div>
|
||||
<div class="row"><span class="glyph ctrl">SUB</span><span class="note">26 · Steuerz.</span></div>
|
||||
<div class="row"><span class="glyph ctrl">LF</span><span class="note">10 · Steuerz.</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
Mindestens ein Byte mit Wert > 126 → <strong>Binärdatei</strong>. Sonst: reine Textdatei.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1,172 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Container ≠ Codec</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--container: #2563eb;
|
||||
--video: #7c3aed;
|
||||
--audio: #ea580c;
|
||||
--subs: #16a34a;
|
||||
--meta: #94a3b8;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 28px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 22px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.scene {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 22px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container-box {
|
||||
border: 3px dashed var(--container);
|
||||
border-radius: 14px;
|
||||
padding: 14px 16px 18px;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
}
|
||||
.container-tag {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 18px;
|
||||
background: #fff;
|
||||
padding: 0 10px;
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
color: var(--container);
|
||||
}
|
||||
.container-label {
|
||||
text-align: center;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.track {
|
||||
display: grid;
|
||||
grid-template-columns: 90px 1fr;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
background: var(--soft);
|
||||
border-left: 4px solid;
|
||||
border-radius: 6px;
|
||||
padding: 9px 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.track.video { border-left-color: var(--video); }
|
||||
.track.audio { border-left-color: var(--audio); }
|
||||
.track.subs { border-left-color: var(--subs); }
|
||||
.track.meta { border-left-color: var(--meta); }
|
||||
|
||||
.track-label {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.track.video .track-label { color: var(--video); }
|
||||
.track.audio .track-label { color: var(--audio); }
|
||||
.track.subs .track-label { color: var(--subs); }
|
||||
.track.meta .track-label { color: var(--meta); }
|
||||
|
||||
.codec-pill {
|
||||
display: inline-block;
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.82rem;
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
padding: 2px 9px;
|
||||
border-radius: 4px;
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.verdict {
|
||||
margin-top: 22px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.1rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.93rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
.verdict-a code { font-family: ui-monospace, monospace; background: var(--soft); padding: 1px 5px; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Container ≠ Codec</h1>
|
||||
<div class="sub">Die <strong>Hülle</strong> sagt nichts darüber, <strong>wie</strong> der Inhalt komprimiert wurde.</div>
|
||||
|
||||
<div class="scene">
|
||||
|
||||
<div class="container-box">
|
||||
<span class="container-tag">.mp4</span>
|
||||
<div class="container-label">Datei A — älterer Stand</div>
|
||||
|
||||
<div class="track video">
|
||||
<span class="track-label">Video</span>
|
||||
<span><span class="codec-pill">H.264 / AVC</span> · 1080p · 8 Mbit/s</span>
|
||||
</div>
|
||||
<div class="track audio">
|
||||
<span class="track-label">Audio</span>
|
||||
<span><span class="codec-pill">AAC</span> · 192 kbit/s · Stereo</span>
|
||||
</div>
|
||||
<div class="track subs">
|
||||
<span class="track-label">Untertitel</span>
|
||||
<span><span class="codec-pill">TTML</span> · de + en</span>
|
||||
</div>
|
||||
<div class="track meta">
|
||||
<span class="track-label">Metadaten</span>
|
||||
<span>Titel · Dauer · Datum · Bitrate</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-box">
|
||||
<span class="container-tag">.mp4</span>
|
||||
<div class="container-label">Datei B — modern</div>
|
||||
|
||||
<div class="track video">
|
||||
<span class="track-label">Video</span>
|
||||
<span><span class="codec-pill">AV1</span> · 4K · 25 Mbit/s</span>
|
||||
</div>
|
||||
<div class="track audio">
|
||||
<span class="track-label">Audio</span>
|
||||
<span><span class="codec-pill">Opus</span> · 128 kbit/s · 5.1 Surround</span>
|
||||
</div>
|
||||
<div class="track subs">
|
||||
<span class="track-label">Untertitel</span>
|
||||
<span><span class="codec-pill">WebVTT</span> · de + en + ja</span>
|
||||
</div>
|
||||
<div class="track meta">
|
||||
<span class="track-label">Metadaten</span>
|
||||
<span>Titel · Dauer · Datum · HDR-Info</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Gleiche Endung. Anderes Innenleben.</div>
|
||||
<div class="verdict-a"><strong>Container</strong> = Verpackung (<code>.mp4</code>, <code>.mkv</code>, <code>.webm</code>). <strong>Codec</strong> = Komprimierer (H.264 / AV1 / AAC / Opus). <strong>Tool:</strong> <code>mediainfo <datei></code> zeigt beides.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,200 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Drei Dateien — dasselbe Innenleben</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--foto: #2563eb;
|
||||
--audio: #ea580c;
|
||||
--pdf: #7c3aed;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 32px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.85rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 28px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.files {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 22px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.file {
|
||||
border: 2px solid;
|
||||
border-radius: 12px;
|
||||
padding: 16px 18px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.file.foto { border-color: var(--foto); }
|
||||
.file.audio { border-color: var(--audio); }
|
||||
.file.pdf { border-color: var(--pdf); }
|
||||
|
||||
.file-head { display: flex; justify-content: space-between; align-items: baseline; }
|
||||
.file-name { font-size: 1.2rem; font-weight: 700; }
|
||||
.file.foto .file-name { color: var(--foto); }
|
||||
.file.audio .file-name { color: var(--audio); }
|
||||
.file.pdf .file-name { color: var(--pdf); }
|
||||
.file-ext { font-family: ui-monospace, monospace; font-size: 0.78rem; color: var(--muted); }
|
||||
|
||||
.preview {
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
height: 130px;
|
||||
background: var(--soft);
|
||||
}
|
||||
|
||||
.hex-strip {
|
||||
background: #0f0f23;
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.6;
|
||||
color: #cbd5e1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hex-strip .magic { color: #fff; font-weight: 700; padding: 0 2px; border-radius: 2px; }
|
||||
.hex-strip.foto-magic .magic { background: var(--foto); }
|
||||
.hex-strip.audio-magic .magic { background: var(--audio); }
|
||||
.hex-strip.pdf-magic .magic { background: var(--pdf); }
|
||||
.hex-label {
|
||||
font-size: 0.68rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--muted);
|
||||
font-weight: 700;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.magic-caption {
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
font-family: ui-monospace, monospace;
|
||||
}
|
||||
.magic-caption .magic-word {
|
||||
color: var(--dark);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.verdict {
|
||||
margin-top: 26px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.15rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.95rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Drei Dateien — dasselbe Innenleben</h1>
|
||||
<div class="sub">Außen <strong>völlig verschieden</strong>. Innen reden alle dieselbe Sprache: <strong>Byte für Byte</strong>.</div>
|
||||
|
||||
<div class="files">
|
||||
|
||||
<div class="file foto">
|
||||
<div class="file-head">
|
||||
<span class="file-name">Foto</span>
|
||||
<span class="file-ext">.png</span>
|
||||
</div>
|
||||
<svg class="preview" viewBox="0 0 220 130" preserveAspectRatio="xMidYMid slice">
|
||||
<defs>
|
||||
<linearGradient id="sky-f" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#fde68a"/>
|
||||
<stop offset="60%" stop-color="#fb923c"/>
|
||||
<stop offset="100%" stop-color="#7c3aed"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="220" height="130" fill="url(#sky-f)"/>
|
||||
<circle cx="160" cy="50" r="18" fill="#fef3c7" opacity="0.95"/>
|
||||
<path d="M0,95 L40,80 L80,90 L120,72 L160,86 L220,75 L220,130 L0,130 Z" fill="#1e1b4b"/>
|
||||
<path d="M0,110 L60,100 L120,108 L180,98 L220,105 L220,130 L0,130 Z" fill="#0f172a" opacity="0.7"/>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="hex-label">Erste Bytes der Datei</div>
|
||||
<div class="hex-strip foto-magic">
|
||||
<span class="magic">89 50 4E 47</span> 0D 0A 1A 0A 00 00 00 0D 49 48 44 52
|
||||
</div>
|
||||
</div>
|
||||
<div class="magic-caption"><span class="magic-word">50 4E 47</span> = <span class="magic-word">"PNG"</span></div>
|
||||
</div>
|
||||
|
||||
<div class="file audio">
|
||||
<div class="file-head">
|
||||
<span class="file-name">Sprachnachricht</span>
|
||||
<span class="file-ext">.mp3</span>
|
||||
</div>
|
||||
<svg class="preview" viewBox="0 0 220 130" preserveAspectRatio="none">
|
||||
<rect x="0" y="0" width="220" height="130" fill="#0f0f23"/>
|
||||
<line x1="0" y1="65" x2="220" y2="65" stroke="#374151" stroke-width="1"/>
|
||||
<path d="M0,65 L8,65 L12,55 L16,75 L20,40 L24,90 L28,30 L32,95 L36,35 L40,90
|
||||
L48,45 L56,82 L64,50 L72,78 L80,52 L88,75 L96,55 L104,72
|
||||
L116,58 L128,72 L140,60 L152,70 L168,62 L184,68 L200,64 L220,65
|
||||
L220,130 L0,130 Z" fill="#fb923c" stroke="#ea580c" stroke-width="1"/>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="hex-label">Erste Bytes der Datei</div>
|
||||
<div class="hex-strip audio-magic">
|
||||
<span class="magic">49 44 33</span> 03 00 00 00 00 03 76 54 49 54 32 00 00
|
||||
</div>
|
||||
</div>
|
||||
<div class="magic-caption"><span class="magic-word">49 44 33</span> = <span class="magic-word">"ID3"</span></div>
|
||||
</div>
|
||||
|
||||
<div class="file pdf">
|
||||
<div class="file-head">
|
||||
<span class="file-name">PDF-Dokument</span>
|
||||
<span class="file-ext">.pdf</span>
|
||||
</div>
|
||||
<svg class="preview" viewBox="0 0 220 130" preserveAspectRatio="xMidYMid meet">
|
||||
<rect x="0" y="0" width="220" height="130" fill="#f8fafc"/>
|
||||
<rect x="50" y="14" width="120" height="102" rx="4" fill="#fff" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<rect x="58" y="24" width="80" height="4" rx="1" fill="#7c3aed"/>
|
||||
<rect x="58" y="34" width="104" height="2" fill="#cbd5e1"/>
|
||||
<rect x="58" y="40" width="98" height="2" fill="#cbd5e1"/>
|
||||
<rect x="58" y="46" width="92" height="2" fill="#cbd5e1"/>
|
||||
<rect x="58" y="52" width="100" height="2" fill="#cbd5e1"/>
|
||||
<rect x="58" y="62" width="80" height="2" fill="#cbd5e1"/>
|
||||
<rect x="58" y="68" width="96" height="2" fill="#cbd5e1"/>
|
||||
<rect x="58" y="74" width="88" height="2" fill="#cbd5e1"/>
|
||||
<rect x="58" y="84" width="40" height="3" rx="1" fill="#7c3aed" opacity="0.7"/>
|
||||
<rect x="58" y="92" width="92" height="2" fill="#cbd5e1"/>
|
||||
<rect x="58" y="98" width="86" height="2" fill="#cbd5e1"/>
|
||||
<rect x="58" y="104" width="78" height="2" fill="#cbd5e1"/>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="hex-label">Erste Bytes der Datei</div>
|
||||
<div class="hex-strip pdf-magic">
|
||||
<span class="magic">25 50 44 46</span> 2D 31 2E 37 0A 25 E2 E3 CF D3 0A 0A
|
||||
</div>
|
||||
</div>
|
||||
<div class="magic-caption"><span class="magic-word">25 50 44 46</span> = <span class="magic-word">"%PDF"</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Drei verschiedene Dinge — eine Sprache.</div>
|
||||
<div class="verdict-a">Jede Datei beginnt mit ihrer <strong>Visitenkarte</strong> (Magic Number) und besteht ab da aus nichts als Byte. Foto, Audio, PDF — alle dieselben Bauteile, nur anders zusammengesetzt.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 77 KiB |
@@ -0,0 +1,206 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>JPEG-Pipeline — 6 Schritte vom Foto zur Datei</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--lossless-step: #2563eb;
|
||||
--lossy-step: #ea580c;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 28px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 26px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.pipeline {
|
||||
display: grid;
|
||||
grid-template-columns: 110px repeat(6, 1fr) 110px;
|
||||
gap: 4px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.endpoint {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
text-align: center;
|
||||
}
|
||||
.endpoint-art { width: 88px; height: 60px; display: block; }
|
||||
.endpoint-label {
|
||||
font-size: 0.92rem;
|
||||
font-weight: 700;
|
||||
color: var(--dark);
|
||||
}
|
||||
.endpoint-size {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.82rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.step {
|
||||
border: 2px solid;
|
||||
border-radius: 8px;
|
||||
padding: 8px 6px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
.step.lossless { border-color: var(--lossless-step); }
|
||||
.step.lossy { border-color: var(--lossy-step); }
|
||||
.step-num {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
color: var(--muted);
|
||||
}
|
||||
.step-name {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.step.lossless .step-name { color: var(--lossless-step); }
|
||||
.step.lossy .step-name { color: var(--lossy-step); }
|
||||
.step-desc {
|
||||
font-size: 0.66rem;
|
||||
color: var(--muted);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--muted);
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.legend {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
font-size: 0.84rem;
|
||||
color: var(--dark);
|
||||
}
|
||||
.legend-item { display: flex; align-items: center; gap: 6px; }
|
||||
.legend-swatch {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
.legend-swatch.lossless { background: var(--lossless-step); }
|
||||
.legend-swatch.lossy { background: var(--lossy-step); }
|
||||
|
||||
.verdict {
|
||||
margin-top: 22px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.1rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.93rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>JPEG-Pipeline — 6 Schritte vom Foto zur Datei</h1>
|
||||
<div class="sub">Nur <strong>einer</strong> dieser Schritte verliert Information. Die anderen fünf sind verlustfrei.</div>
|
||||
|
||||
<div class="pipeline">
|
||||
|
||||
<div class="endpoint">
|
||||
<svg class="endpoint-art" viewBox="0 0 88 60">
|
||||
<defs>
|
||||
<linearGradient id="photo-grad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#fde68a"/>
|
||||
<stop offset="55%" stop-color="#fb923c"/>
|
||||
<stop offset="100%" stop-color="#7c3aed"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="88" height="60" rx="3" fill="url(#photo-grad)"/>
|
||||
<circle cx="66" cy="22" r="9" fill="#fef3c7"/>
|
||||
<path d="M0,42 L18,34 L40,40 L60,30 L80,38 L88,33 L88,60 L0,60 Z" fill="#1e1b4b"/>
|
||||
</svg>
|
||||
<div class="endpoint-label">Foto (Roh)</div>
|
||||
<div class="endpoint-size">~36 MB</div>
|
||||
</div>
|
||||
|
||||
<div class="step lossless">
|
||||
<div class="step-num">1</div>
|
||||
<div class="step-name">Farbraum</div>
|
||||
<div class="step-desc">RGB → Y'CbCr<br>Helligkeit von Farbe trennen</div>
|
||||
</div>
|
||||
|
||||
<div class="step lossy">
|
||||
<div class="step-num">2</div>
|
||||
<div class="step-name">Chroma-Subsampling</div>
|
||||
<div class="step-desc">Cb & Cr auf <strong>¼</strong><br>Auge sieht Farbe grob</div>
|
||||
</div>
|
||||
|
||||
<div class="step lossless">
|
||||
<div class="step-num">3</div>
|
||||
<div class="step-name">8×8 Blöcke</div>
|
||||
<div class="step-desc">Bild in Kacheln<br>jeder Block einzeln</div>
|
||||
</div>
|
||||
|
||||
<div class="step lossless">
|
||||
<div class="step-num">4</div>
|
||||
<div class="step-name">DCT</div>
|
||||
<div class="step-desc">Frequenz-Zerlegung<br>häufige Muster</div>
|
||||
</div>
|
||||
|
||||
<div class="step lossy">
|
||||
<div class="step-num">5</div>
|
||||
<div class="step-name">Quantisierung</div>
|
||||
<div class="step-desc"><strong>Hier</strong> wird weggeworfen<br>hohe Frequenzen raus</div>
|
||||
</div>
|
||||
|
||||
<div class="step lossless">
|
||||
<div class="step-num">6</div>
|
||||
<div class="step-name">Huffman</div>
|
||||
<div class="step-desc">Verlustfrei kürzen<br>häufig → kurz</div>
|
||||
</div>
|
||||
|
||||
<div class="endpoint">
|
||||
<svg class="endpoint-art" viewBox="0 0 88 60">
|
||||
<rect x="0" y="0" width="88" height="60" rx="3" fill="url(#photo-grad)"/>
|
||||
<circle cx="66" cy="22" r="9" fill="#fef3c7"/>
|
||||
<path d="M0,42 L18,34 L40,40 L60,30 L80,38 L88,33 L88,60 L0,60 Z" fill="#1e1b4b"/>
|
||||
</svg>
|
||||
<div class="endpoint-label">JPEG</div>
|
||||
<div class="endpoint-size">~3,5 MB</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="legend">
|
||||
<span class="legend-item"><span class="legend-swatch lossless"></span>verlustfrei (umkehrbar)</span>
|
||||
<span class="legend-item"><span class="legend-swatch lossy"></span>verlustbehaftet (Information weg)</span>
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Der <strong>Quanten-Schritt</strong> ist das ganze Geheimnis von JPEG.</div>
|
||||
<div class="verdict-a">Schritte 1, 3, 4, 6 verlieren nichts. Schritt 2 wirft Farb-Detail weg (Auge merkt nicht). Schritt 5 entscheidet — je nach Qualitäts-Stufe — wie viel <strong>Frequenz-Detail</strong> verloren geht. Niedrige Quality = aggressivere Schritt-5-Quantisierung.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 71 KiB |
@@ -0,0 +1,158 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Dieselbe Aufnahme, zwei Formate</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--lossless: #16a34a;
|
||||
--lossy: #ef4444;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 36px 48px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.85rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 28px; font-size: 1.05rem; }
|
||||
|
||||
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 26px; max-width: 1200px; margin: 0 auto; }
|
||||
.panel { border: 2px solid var(--border); border-radius: 12px; padding: 22px 24px; display: flex; flex-direction: column; gap: 14px; background: #fff; }
|
||||
.panel.left { border-color: var(--lossless); }
|
||||
.panel.right { border-color: var(--lossy); }
|
||||
|
||||
.panel-head { display: flex; justify-content: space-between; align-items: baseline; }
|
||||
.panel-tag {
|
||||
font-size: 0.74rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.panel-name { font-size: 1.5rem; font-weight: 700; }
|
||||
.panel.left .panel-name { color: var(--lossless); }
|
||||
.panel.right .panel-name { color: var(--lossy); }
|
||||
.panel-format {
|
||||
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
||||
font-size: 0.85rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.waveform {
|
||||
background: #0f0f23;
|
||||
border-radius: 8px;
|
||||
padding: 6px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||||
.stat { background: var(--soft); border: 1px solid var(--border); border-radius: 7px; padding: 8px 12px; }
|
||||
.stat .label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); font-weight: 700; }
|
||||
.stat .value { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 1rem; font-weight: 700; color: var(--dark); margin-top: 2px; }
|
||||
|
||||
.verdict {
|
||||
margin-top: 26px;
|
||||
border: 2px solid var(--hl);
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px 22px;
|
||||
text-align: center;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.verdict-q { font-size: 1.2rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 1rem; color: var(--dark); margin-top: 6px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Dieselbe Aufnahme, zwei Formate</h1>
|
||||
<div class="sub">3 Sekunden Snare-Hit mit Cymbal-Tail · 44,1 kHz Sample-Rate · Stereo</div>
|
||||
|
||||
<div class="grid">
|
||||
|
||||
<div class="panel left">
|
||||
<div class="panel-head">
|
||||
<span class="panel-name">Lossless</span>
|
||||
<span class="panel-tag">FLAC</span>
|
||||
</div>
|
||||
<div class="panel-format">verlustfrei · alle Frequenzen 0–22 kHz erhalten</div>
|
||||
|
||||
<svg class="waveform" viewBox="0 0 800 160" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id="green-grad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#34d399"/>
|
||||
<stop offset="100%" stop-color="#16a34a"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<line x1="0" y1="80" x2="800" y2="80" stroke="#374151" stroke-width="1"/>
|
||||
<!-- detailed waveform: transient peak + cymbal decay with high-freq noise -->
|
||||
<path d="M0,80
|
||||
L40,80 L42,78 L44,82 L46,74 L48,88
|
||||
L60,30 L62,135 L64,18 L66,142 L68,28 L70,128 L72,38 L74,118 L76,48 L78,108
|
||||
L90,55 L92,108 L94,52 L96,112 L98,58 L100,105 L102,62 L104,98 L106,68 L108,92
|
||||
L130,72 L132,92 L134,68 L136,94 L138,72 L140,90 L142,74 L144,88
|
||||
L170,76 L172,86 L174,74 L176,87 L178,76 L180,85
|
||||
L220,77 L222,84 L224,76 L226,85 L228,77 L230,83
|
||||
L280,78 L282,83 L284,77 L286,84 L288,78 L290,82
|
||||
L340,79 L342,82 L344,78 L346,82 L348,79 L350,81
|
||||
L400,79 L402,81 L404,79 L406,81 L408,79 L410,81
|
||||
L460,80 L462,81 L464,79 L466,81 L468,80 L470,80
|
||||
L520,80 L522,81 L524,79 L526,80
|
||||
L580,80 L582,80 L584,80
|
||||
L640,80 L700,80 L800,80 Z"
|
||||
fill="url(#green-grad)" stroke="#16a34a" stroke-width="1" opacity="0.95"/>
|
||||
</svg>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="label">Dateigröße</div><div class="value">5,3 MB</div></div>
|
||||
<div class="stat"><div class="label">Bitrate</div><div class="value">1.411 kbit/s</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel right">
|
||||
<div class="panel-head">
|
||||
<span class="panel-name">Lossy</span>
|
||||
<span class="panel-tag">MP3 128 kbit/s</span>
|
||||
</div>
|
||||
<div class="panel-format">verlustbehaftet · hohe Frequenzen weggeworfen, Transienten geglättet</div>
|
||||
|
||||
<svg class="waveform" viewBox="0 0 800 160" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id="red-grad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#fca5a5"/>
|
||||
<stop offset="100%" stop-color="#ef4444"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<line x1="0" y1="80" x2="800" y2="80" stroke="#374151" stroke-width="1"/>
|
||||
<!-- smoothed waveform: same overall shape, no fine detail in transient peak -->
|
||||
<path d="M0,80
|
||||
L40,80 L48,80
|
||||
L60,48 L65,120 L70,52 L75,115 L80,58 L85,108
|
||||
L100,68 L108,92 L114,70 L120,90
|
||||
L150,75 L160,86 L168,76 L174,85
|
||||
L200,77 L210,84 L220,77 L230,83
|
||||
L260,78 L280,82 L300,78 L320,82
|
||||
L360,79 L380,81 L400,79 L420,81
|
||||
L460,80 L480,80 L520,80
|
||||
L580,80 L640,80 L700,80 L800,80 Z"
|
||||
fill="url(#red-grad)" stroke="#ef4444" stroke-width="1" opacity="0.95"/>
|
||||
</svg>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="label">Dateigröße</div><div class="value">0,48 MB</div></div>
|
||||
<div class="stat"><div class="label">Bitrate</div><div class="value">128 kbit/s</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Hört man den Unterschied?</div>
|
||||
<div class="verdict-a">Auf dem Smartphone-Lautsprecher: meist nicht. Im Studio-Monitor mit Kopfhörer: oft schon. — MP3 wirft <strong>91 % der Daten weg</strong>, die der Mensch im Alltag nicht vermisst.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 72 KiB |
@@ -0,0 +1,119 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Drei Modalitäten, ein Hebel</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--bild: #2563eb;
|
||||
--audio: #ea580c;
|
||||
--video: #7c3aed;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 32px 48px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.85rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 26px; font-size: 1.05rem; }
|
||||
|
||||
.table { display: grid; grid-template-columns: 140px 1fr 1fr 1fr; gap: 12px; max-width: 1180px; margin: 0 auto; align-items: stretch; }
|
||||
.col-head { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 700; color: var(--muted); padding: 0 8px 4px; }
|
||||
|
||||
.row-label { font-size: 1.15rem; font-weight: 700; padding: 16px 12px; display: flex; flex-direction: column; gap: 4px; justify-content: center; border-radius: 10px; }
|
||||
.row-label.bild { background: #dbeafe; color: var(--bild); }
|
||||
.row-label.audio { background: #ffedd5; color: var(--audio); }
|
||||
.row-label.video { background: #ede9fe; color: var(--video); }
|
||||
.row-label small { font-size: 0.78rem; font-weight: 400; color: var(--muted); text-transform: none; letter-spacing: 0; }
|
||||
|
||||
.cell { border: 1.5px solid var(--border); border-radius: 10px; padding: 12px 14px; background: #fff; display: flex; flex-direction: column; gap: 8px; }
|
||||
.cell-format { font-family: ui-monospace, monospace; font-size: 0.85rem; color: var(--muted); }
|
||||
.cell-size { font-size: 1.7rem; font-weight: 700; color: var(--dark); font-family: ui-monospace, monospace; }
|
||||
.cell-bar { height: 8px; border-radius: 4px; background: var(--soft); position: relative; overflow: hidden; }
|
||||
.cell-bar .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 4px; }
|
||||
|
||||
.verdict {
|
||||
margin-top: 22px;
|
||||
max-width: 1180px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
text-align: center;
|
||||
}
|
||||
.verdict-q { font-size: 1.05rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.95rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Drei Modalitäten — derselbe Hebel</h1>
|
||||
<div class="sub">Bild, Audio, Video schrumpfen alle dramatisch. Warum? Weil Wahrnehmung Lücken hat.</div>
|
||||
|
||||
<div class="table">
|
||||
<div></div>
|
||||
<div class="col-head">Roh</div>
|
||||
<div class="col-head">Lossy (modern)</div>
|
||||
<div class="col-head">Lossy (aggressiv)</div>
|
||||
|
||||
<div class="row-label bild">Bild<small>12-MP-Foto</small></div>
|
||||
<div class="cell">
|
||||
<div class="cell-format">RAW</div>
|
||||
<div class="cell-size">36 MB</div>
|
||||
<div class="cell-bar"><div class="fill" style="width:100%;background:#2563eb"></div></div>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<div class="cell-format">JPEG · Q 90</div>
|
||||
<div class="cell-size">4 MB</div>
|
||||
<div class="cell-bar"><div class="fill" style="width:11%;background:#2563eb"></div></div>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<div class="cell-format">JPEG · Q 50</div>
|
||||
<div class="cell-size">800 KB</div>
|
||||
<div class="cell-bar"><div class="fill" style="width:2.2%;background:#2563eb"></div></div>
|
||||
</div>
|
||||
|
||||
<div class="row-label audio">Audio<small>30 s Stereo</small></div>
|
||||
<div class="cell">
|
||||
<div class="cell-format">WAV · 44,1 kHz · 16 bit</div>
|
||||
<div class="cell-size">5,3 MB</div>
|
||||
<div class="cell-bar"><div class="fill" style="width:100%;background:#ea580c"></div></div>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<div class="cell-format">MP3 · 192 kbit/s</div>
|
||||
<div class="cell-size">720 KB</div>
|
||||
<div class="cell-bar"><div class="fill" style="width:14%;background:#ea580c"></div></div>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<div class="cell-format">MP3 · 64 kbit/s</div>
|
||||
<div class="cell-size">240 KB</div>
|
||||
<div class="cell-bar"><div class="fill" style="width:4.5%;background:#ea580c"></div></div>
|
||||
</div>
|
||||
|
||||
<div class="row-label video">Video<small>30 s · 4K · 30 fps</small></div>
|
||||
<div class="cell">
|
||||
<div class="cell-format">Roh · 8 bit</div>
|
||||
<div class="cell-size">12 GB</div>
|
||||
<div class="cell-bar"><div class="fill" style="width:100%;background:#7c3aed"></div></div>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<div class="cell-format">H.264 · 25 Mbit/s</div>
|
||||
<div class="cell-size">94 MB</div>
|
||||
<div class="cell-bar"><div class="fill" style="width:0.8%;background:#7c3aed"></div></div>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<div class="cell-format">H.265 · 12 Mbit/s</div>
|
||||
<div class="cell-size">45 MB</div>
|
||||
<div class="cell-bar"><div class="fill" style="width:0.4%;background:#7c3aed"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Bilder schrumpfen ×45. Audio ×22. Video ×270.</div>
|
||||
<div class="verdict-a">Jede Reduktion nutzt eine andere Wahrnehmungs-Lücke: <strong>Bild</strong> die geringere Farbschärfe, <strong>Audio</strong> die Maskierung, <strong>Video</strong> die Frame-zu-Frame-Ähnlichkeit.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 75 KiB |
@@ -0,0 +1,182 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Die Reise einer Datei</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--slot1: #0891b2;
|
||||
--slot2: #2563eb;
|
||||
--slot3: #7c3aed;
|
||||
--slot4: #be185d;
|
||||
--slot5: #ea580c;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 32px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.85rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 30px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.journey {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr auto 1fr;
|
||||
gap: 6px;
|
||||
align-items: stretch;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.station {
|
||||
border: 2px solid;
|
||||
border-radius: 10px;
|
||||
padding: 14px 12px 12px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.station.s1 { border-color: var(--slot1); }
|
||||
.station.s2 { border-color: var(--slot2); }
|
||||
.station.s3 { border-color: var(--slot3); }
|
||||
.station.s4 { border-color: var(--slot4); }
|
||||
.station.s5 { border-color: var(--slot5); }
|
||||
|
||||
.station-tag { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; color: var(--muted); }
|
||||
.station-name { font-size: 1.05rem; font-weight: 700; text-align: center; line-height: 1.2; }
|
||||
.station.s1 .station-name { color: var(--slot1); }
|
||||
.station.s2 .station-name { color: var(--slot2); }
|
||||
.station.s3 .station-name { color: var(--slot3); }
|
||||
.station.s4 .station-name { color: var(--slot4); }
|
||||
.station.s5 .station-name { color: var(--slot5); }
|
||||
.station-detail { font-size: 0.78rem; color: var(--muted); text-align: center; }
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 0 4px;
|
||||
justify-content: center;
|
||||
}
|
||||
.link-rate {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
color: var(--dark);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.link-protocol {
|
||||
font-size: 0.7rem;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.link-arrow svg { width: 32px; height: 14px; }
|
||||
|
||||
.scenario {
|
||||
margin-top: 28px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background: var(--soft);
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 16px 22px;
|
||||
}
|
||||
.scenario-head { font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); margin-bottom: 6px; }
|
||||
.scenario-body { font-size: 0.98rem; line-height: 1.55; }
|
||||
.scenario-body code { font-family: ui-monospace, monospace; background: #fff; padding: 1px 6px; border-radius: 4px; border: 1px solid var(--border); }
|
||||
|
||||
.verdict {
|
||||
margin-top: 18px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.1rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.92rem; color: var(--dark); margin-top: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Die Reise einer Datei</h1>
|
||||
<div class="sub">Ein 4K-Trailer (<strong>1,2 GB</strong>) reist von der externen SSD auf den Monitor — jede Etappe ist ein eigener Engpass.</div>
|
||||
|
||||
<div class="journey">
|
||||
<div class="station s1">
|
||||
<div class="station-tag">Quelle</div>
|
||||
<div class="station-name">SSD<br>extern</div>
|
||||
<div class="station-detail">NVMe-Stick</div>
|
||||
</div>
|
||||
|
||||
<div class="link">
|
||||
<div class="link-rate">3 200 MB/s</div>
|
||||
<svg class="link-arrow" viewBox="0 0 32 14"><path d="M2,7 L26,7 M22,2 L28,7 L22,12" stroke="#d63384" stroke-width="2" fill="none"/></svg>
|
||||
<div class="link-protocol">USB 4 / TB4</div>
|
||||
</div>
|
||||
|
||||
<div class="station s2">
|
||||
<div class="station-tag">Aufnahme</div>
|
||||
<div class="station-name">Laptop</div>
|
||||
<div class="station-detail">RAM & CPU</div>
|
||||
</div>
|
||||
|
||||
<div class="link">
|
||||
<div class="link-rate">~7 000 MB/s</div>
|
||||
<svg class="link-arrow" viewBox="0 0 32 14"><path d="M2,7 L26,7 M22,2 L28,7 L22,12" stroke="#d63384" stroke-width="2" fill="none"/></svg>
|
||||
<div class="link-protocol">PCIe 4.0</div>
|
||||
</div>
|
||||
|
||||
<div class="station s3">
|
||||
<div class="station-tag">Decoder</div>
|
||||
<div class="station-name">GPU</div>
|
||||
<div class="station-detail">H.264-Hardware</div>
|
||||
</div>
|
||||
|
||||
<div class="link">
|
||||
<div class="link-rate">2 200 MB/s</div>
|
||||
<svg class="link-arrow" viewBox="0 0 32 14"><path d="M2,7 L26,7 M22,2 L28,7 L22,12" stroke="#d63384" stroke-width="2" fill="none"/></svg>
|
||||
<div class="link-protocol">HDMI 2.1</div>
|
||||
</div>
|
||||
|
||||
<div class="station s4">
|
||||
<div class="station-tag">Display</div>
|
||||
<div class="station-name">4K-Monitor</div>
|
||||
<div class="station-detail">3840 × 2160</div>
|
||||
</div>
|
||||
|
||||
<div class="link">
|
||||
<div class="link-rate">visuell</div>
|
||||
<svg class="link-arrow" viewBox="0 0 32 14"><path d="M2,7 L26,7 M22,2 L28,7 L22,12" stroke="#d63384" stroke-width="2" fill="none"/></svg>
|
||||
<div class="link-protocol">Licht</div>
|
||||
</div>
|
||||
|
||||
<div class="station s5">
|
||||
<div class="station-tag">Ziel</div>
|
||||
<div class="station-name">Auge</div>
|
||||
<div class="station-detail">Bewusstsein</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="scenario">
|
||||
<div class="scenario-head">Was hier passiert</div>
|
||||
<div class="scenario-body">
|
||||
Die <code>1,2 GB</code> liegen verschlüsselt komprimiert auf dem Stick. Sie reisen über <strong>USB-C</strong> in den RAM, werden vom GPU <strong>dekodiert</strong> (H.264 → roher RGB-Strom), und über <strong>HDMI</strong> als unkomprimiertes Bild an den Monitor geschickt. Dort entstehen pro Sekunde 60 Bilder mit je rund <code>24 MB</code> — also <strong>1,4 GB/s an rohem Pixelstrom</strong>, der niemals als Datei vorliegt.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Speicher und Schnittstelle sind keine zwei Themen — sie sind die zwei Seiten desselben Engpasses.</div>
|
||||
<div class="verdict-a">Wo Bytes wohnen entscheidet, wie schnell sie reisen können. Wo sie reisen entscheidet, wo sie wohnen müssen.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 89 KiB |
@@ -0,0 +1,254 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Eine Datei reist — und erzählt</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--visible: #2563eb;
|
||||
--hidden: #7c3aed;
|
||||
--transit: #ea580c;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 28px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.85rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 22px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.scene {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.2fr;
|
||||
gap: 22px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.panel {
|
||||
border: 2px solid;
|
||||
border-radius: 12px;
|
||||
padding: 16px 18px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.panel.visible { border-color: var(--visible); }
|
||||
.panel.hidden { border-color: var(--hidden); }
|
||||
|
||||
.panel-head { display: flex; justify-content: space-between; align-items: baseline; }
|
||||
.panel-tag {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.panel-name { font-size: 1.25rem; font-weight: 700; }
|
||||
.panel.visible .panel-name { color: var(--visible); }
|
||||
.panel.hidden .panel-name { color: var(--hidden); }
|
||||
|
||||
.photo {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.photo-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.exif-list {
|
||||
display: grid;
|
||||
grid-template-columns: 130px 1fr;
|
||||
gap: 4px 12px;
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.exif-key {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--muted);
|
||||
padding-top: 3px;
|
||||
}
|
||||
.exif-val { font-family: ui-monospace, monospace; color: var(--dark); }
|
||||
.exif-val.gps { color: var(--hl); font-weight: 700; }
|
||||
|
||||
.transit-strip {
|
||||
max-width: 1200px;
|
||||
margin: 18px auto 0;
|
||||
border: 2px solid var(--transit);
|
||||
border-radius: 12px;
|
||||
padding: 14px 18px;
|
||||
background: #fff;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
.transit-tag {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--transit);
|
||||
writing-mode: vertical-rl;
|
||||
transform: rotate(180deg);
|
||||
text-align: center;
|
||||
}
|
||||
.transit-body { display: flex; flex-direction: column; gap: 8px; }
|
||||
.transit-route {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.92rem;
|
||||
font-weight: 700;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.hop {
|
||||
background: var(--soft);
|
||||
border: 1.5px solid var(--border);
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.82rem;
|
||||
color: var(--dark);
|
||||
}
|
||||
.hop.origin { border-color: var(--visible); color: var(--visible); }
|
||||
.hop.dest { border-color: var(--visible); color: var(--visible); }
|
||||
.hop.edge { border-color: var(--transit); color: var(--transit); }
|
||||
.arrow { color: var(--muted); font-family: ui-monospace, monospace; }
|
||||
.transit-headers {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.transit-headers strong { color: var(--dark); }
|
||||
|
||||
.verdict {
|
||||
margin-top: 18px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.1rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.93rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Eine Datei reist — und erzählt</h1>
|
||||
<div class="sub">Ein Smartphone-Foto geht von <strong>Stuttgart</strong> nach <strong>Tokyo</strong>. Was sieht der Empfänger — und was bekommt er noch dazu?</div>
|
||||
|
||||
<div class="scene">
|
||||
|
||||
<div class="panel visible">
|
||||
<div class="panel-head">
|
||||
<span class="panel-name">Inhalt</span>
|
||||
<span class="panel-tag">Was du siehst</span>
|
||||
</div>
|
||||
|
||||
<svg class="photo" viewBox="0 0 420 280" preserveAspectRatio="xMidYMid slice">
|
||||
<defs>
|
||||
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#fde68a"/>
|
||||
<stop offset="55%" stop-color="#fda4af"/>
|
||||
<stop offset="100%" stop-color="#9333ea"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="hills" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#312e81"/>
|
||||
<stop offset="100%" stop-color="#0f172a"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="420" height="280" fill="url(#sky)"/>
|
||||
<circle cx="320" cy="105" r="32" fill="#fff7ed" opacity="0.95"/>
|
||||
<path d="M0,200 L60,170 L110,185 L160,160 L210,178 L260,150 L310,172 L360,148 L420,168 L420,280 L0,280 Z" fill="url(#hills)"/>
|
||||
<path d="M0,225 L50,210 L100,222 L150,205 L200,220 L250,202 L300,218 L350,200 L420,215 L420,280 L0,280 Z" fill="#1e1b4b" opacity="0.7"/>
|
||||
<rect x="80" y="225" width="14" height="42" fill="#0f172a"/>
|
||||
<rect x="180" y="232" width="10" height="35" fill="#0f172a"/>
|
||||
<rect x="240" y="220" width="18" height="48" fill="#0f172a"/>
|
||||
<rect x="300" y="234" width="11" height="34" fill="#0f172a"/>
|
||||
<rect x="86" y="232" width="3" height="3" fill="#fbbf24"/>
|
||||
<rect x="188" y="240" width="2" height="2" fill="#fbbf24"/>
|
||||
<rect x="248" y="228" width="3" height="3" fill="#fbbf24"/>
|
||||
<rect x="307" y="240" width="2" height="2" fill="#fbbf24"/>
|
||||
</svg>
|
||||
|
||||
<div class="photo-meta">
|
||||
<span>HEIC · 4032 × 3024</span>
|
||||
<span>3,2 MB</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel hidden">
|
||||
<div class="panel-head">
|
||||
<span class="panel-name">Gepäck</span>
|
||||
<span class="panel-tag">Was die Datei über sich weiß</span>
|
||||
</div>
|
||||
|
||||
<div class="exif-list">
|
||||
<div class="exif-key">Kamera</div>
|
||||
<div class="exif-val">Apple iPhone 15 Pro · 24 mm · ƒ/1.78</div>
|
||||
|
||||
<div class="exif-key">Belichtung</div>
|
||||
<div class="exif-val">1/250 s · ISO 100 · EV 0</div>
|
||||
|
||||
<div class="exif-key">Aufgenommen</div>
|
||||
<div class="exif-val">2026-04-12 · 18:47:12 · +02:00</div>
|
||||
|
||||
<div class="exif-key">GPS</div>
|
||||
<div class="exif-val gps">48.7847° N · 9.1825° E · 248 m<br><span style="font-weight:400">(Stuttgart, Königstraße 28)</span></div>
|
||||
|
||||
<div class="exif-key">Software</div>
|
||||
<div class="exif-val">iOS 18.4.1 · HDR · Live Photo</div>
|
||||
|
||||
<div class="exif-key">Farbprofil</div>
|
||||
<div class="exif-val">Display P3</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="transit-strip">
|
||||
<div class="transit-tag">Weg</div>
|
||||
<div class="transit-body">
|
||||
<div class="transit-route">
|
||||
<span class="hop origin">Stuttgart</span>
|
||||
<span class="arrow">→</span>
|
||||
<span class="hop">DNS</span>
|
||||
<span class="arrow">→</span>
|
||||
<span class="hop">TLS 1.3</span>
|
||||
<span class="arrow">→</span>
|
||||
<span class="hop edge">CDN-Edge Tokyo</span>
|
||||
<span class="arrow">→</span>
|
||||
<span class="hop dest">Tokyo</span>
|
||||
</div>
|
||||
<div class="transit-headers">
|
||||
<strong>HTTP/3 200 OK</strong> · content-type: image/heic · content-length: 3 220 480 · <strong>RTT 47 ms</strong> · cache: HIT (Edge tok-7) · age: 312 s
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Eine Datei = Inhalt + Gepäck + Weg.</div>
|
||||
<div class="verdict-a"><strong>Inhalt</strong> sieht der Empfänger. <strong>Gepäck</strong> verrät, wer sie gemacht hat — und wo. <strong>Weg</strong> entscheidet, wie schnell sie ankommt. Drei Schichten, eine Datei.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 96 KiB |
@@ -0,0 +1,194 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>1 TB verkauft, 931 GB angezeigt — wo gehen die 69 GB hin?</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--marketing: #2563eb;
|
||||
--os: #ea580c;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 32px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 24px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.compare {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 80px 1fr;
|
||||
gap: 18px;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.side {
|
||||
border: 2px solid;
|
||||
border-radius: 12px;
|
||||
padding: 18px 22px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.side.marketing { border-color: var(--marketing); }
|
||||
.side.os { border-color: var(--os); }
|
||||
|
||||
.side-tag {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.side.marketing .side-name { color: var(--marketing); }
|
||||
.side.os .side-name { color: var(--os); }
|
||||
.side-name { font-size: 1.15rem; font-weight: 700; }
|
||||
|
||||
.label-mock {
|
||||
background: var(--soft);
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 14px 22px;
|
||||
min-width: 240px;
|
||||
text-align: center;
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.85rem;
|
||||
color: var(--muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.label-mock .big {
|
||||
font-family: system-ui, sans-serif;
|
||||
font-size: 2.2rem;
|
||||
font-weight: 900;
|
||||
display: block;
|
||||
color: var(--dark);
|
||||
line-height: 1.05;
|
||||
}
|
||||
.side.marketing .label-mock .big { color: var(--marketing); }
|
||||
.side.os .label-mock .big { color: var(--os); }
|
||||
.label-mock .small-line { font-size: 0.78rem; }
|
||||
|
||||
.formel {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.85rem;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.formel strong { color: var(--dark); }
|
||||
|
||||
.arrow-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.arrow-col svg { display: block; }
|
||||
.arrow-col .why {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
color: var(--hl);
|
||||
text-align: center;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.math-box {
|
||||
margin-top: 22px;
|
||||
max-width: 1100px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background: var(--soft);
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 16px 22px;
|
||||
}
|
||||
.math-box-head { font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); margin-bottom: 8px; }
|
||||
.math-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 22px; font-family: ui-monospace, monospace; font-size: 0.9rem; line-height: 1.6; }
|
||||
.math-grid .key { color: var(--muted); }
|
||||
.math-grid .val { color: var(--dark); font-weight: 700; }
|
||||
.math-grid .val.hl { color: var(--hl); }
|
||||
|
||||
.verdict {
|
||||
margin-top: 18px;
|
||||
max-width: 1100px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.05rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.93rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a code { font-family: ui-monospace, monospace; background: #fff; padding: 1px 5px; border-radius: 4px; border: 1px solid var(--border); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>„1 TB" gekauft — 931 GB im Finder. Wo sind die 69 GB hin?</h1>
|
||||
<div class="sub">Nirgendwo. Es sind <strong>dieselben Byte</strong>. Nur zwei Welten zählen anders.</div>
|
||||
|
||||
<div class="compare">
|
||||
|
||||
<div class="side marketing">
|
||||
<div class="side-tag">Auf der Verpackung</div>
|
||||
<div class="side-name">Marketing</div>
|
||||
<div class="label-mock">
|
||||
<span class="big">1 TB</span>
|
||||
<span class="small-line">externe SSD</span>
|
||||
</div>
|
||||
<div class="formel">
|
||||
1 TB = <strong>10<sup>12</sup></strong> Byte<br>
|
||||
= 1 000 000 000 000
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow-col">
|
||||
<svg viewBox="0 0 60 30" width="56" height="30"><path d="M5,15 L48,15 M42,7 L52,15 L42,23" stroke="#d63384" stroke-width="2.5" fill="none"/></svg>
|
||||
<div class="why">geteilt durch<br>2<sup>30</sup></div>
|
||||
</div>
|
||||
|
||||
<div class="side os">
|
||||
<div class="side-tag">Im Finder · Explorer</div>
|
||||
<div class="side-name">Betriebssystem</div>
|
||||
<div class="label-mock">
|
||||
<span class="big">931 GB</span>
|
||||
<span class="small-line">eigentlich: 931 GiB</span>
|
||||
</div>
|
||||
<div class="formel">
|
||||
1 GiB = <strong>2<sup>30</sup></strong> Byte<br>
|
||||
= 1 073 741 824
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="math-box">
|
||||
<div class="math-box-head">Die Rechnung</div>
|
||||
<div class="math-grid">
|
||||
<span class="key">Marketing schreibt</span>
|
||||
<span class="val">1 TB = 1 000 000 000 000 Byte (Basis 10)</span>
|
||||
<span class="key">Betriebssystem rechnet</span>
|
||||
<span class="val">1 TiB = 1 099 511 627 776 Byte (Basis 2)</span>
|
||||
<span class="key">1 TB in GiB umrechnen</span>
|
||||
<span class="val hl">10<sup>12</sup> ÷ 2<sup>30</sup> ≈ 931,3 GiB — der Finder zeigt also „931 GB"</span>
|
||||
<span class="key">Was die Studis sehen</span>
|
||||
<span class="val">Etikett: GB · Anzeige: GB · Bedeutung: zwei verschiedene Dinge</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Es fehlt nichts. Es wird nur in zwei Sprachen gezählt.</div>
|
||||
<div class="verdict-a">Korrekt wären <code>GiB</code> (binär, 1024) und <code>GB</code> (dezimal, 1000). In der Realität verwenden beide Welten <code>GB</code> und meinen verschiedene Zahlen.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 91 KiB |
@@ -0,0 +1,232 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Nyquist-Shannon — wie oft muss man messen?</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--bad: #ef4444;
|
||||
--warn: #f59e0b;
|
||||
--good: #16a34a;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 28px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 22px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.cases {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.case {
|
||||
display: grid;
|
||||
grid-template-columns: 200px 1fr;
|
||||
gap: 18px;
|
||||
align-items: center;
|
||||
border: 2px solid;
|
||||
border-radius: 10px;
|
||||
padding: 10px 14px;
|
||||
background: #fff;
|
||||
}
|
||||
.case.bad { border-color: var(--bad); }
|
||||
.case.warn { border-color: var(--warn); }
|
||||
.case.good { border-color: var(--good); }
|
||||
|
||||
.case-meta { display: flex; flex-direction: column; gap: 4px; }
|
||||
.case-tag {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.case-name { font-size: 1.05rem; font-weight: 700; }
|
||||
.case.bad .case-name { color: var(--bad); }
|
||||
.case.warn .case-name { color: var(--warn); }
|
||||
.case.good .case-name { color: var(--good); }
|
||||
.case-rate { font-family: ui-monospace, monospace; font-size: 0.82rem; color: var(--dark); }
|
||||
.case-note { font-size: 0.78rem; color: var(--muted); line-height: 1.35; }
|
||||
|
||||
.plot { background: var(--soft); border-radius: 8px; }
|
||||
.plot svg { display: block; }
|
||||
|
||||
.verdict {
|
||||
margin-top: 20px;
|
||||
max-width: 1180px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.05rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.92rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
.verdict-a code { font-family: ui-monospace, monospace; background: var(--soft); padding: 1px 5px; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Nyquist-Shannon — wie oft müssen wir messen?</h1>
|
||||
<div class="sub">Dieselbe Welle, drei Sample-Raten. Eine fällt durch, eine reicht knapp, eine ist sicher.</div>
|
||||
|
||||
<div class="cases">
|
||||
|
||||
<div class="case bad">
|
||||
<div class="case-meta">
|
||||
<div class="case-tag">Zu wenig</div>
|
||||
<div class="case-name">Aliasing</div>
|
||||
<div class="case-rate">f<sub>s</sub> = 0,9 · f</div>
|
||||
<div class="case-note">Die Sample-Punkte sitzen auf der Welle — aber verbunden sehen sie aus wie eine <strong>ganz andere, langsamere</strong> Welle.</div>
|
||||
</div>
|
||||
<div class="plot">
|
||||
<svg viewBox="0 0 880 140" width="100%" height="140" preserveAspectRatio="none">
|
||||
<line x1="20" y1="70" x2="860" y2="70" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3,4"/>
|
||||
<path d="M 20,70 Q 46.25,-20 72.5,70 T 125,70 T 177.5,70 T 230,70 T 282.5,70 T 335,70 T 387.5,70 T 440,70 T 492.5,70 T 545,70 T 597.5,70 T 650,70 T 702.5,70 T 755,70 T 807.5,70 T 860,70"
|
||||
stroke="#cbd5e1" stroke-width="1.5" fill="none"/>
|
||||
<polyline points="20,70 136.7,41 253.4,25.7 370.1,31.2 486.8,54.9 603.5,85.8 720.2,109.2 836.9,114.2"
|
||||
stroke="#ef4444" stroke-width="2.5" fill="none"/>
|
||||
<circle cx="20" cy="70" r="5" fill="#ef4444"/>
|
||||
<circle cx="136.7" cy="41" r="5" fill="#ef4444"/>
|
||||
<circle cx="253.4" cy="25.7" r="5" fill="#ef4444"/>
|
||||
<circle cx="370.1" cy="31.2" r="5" fill="#ef4444"/>
|
||||
<circle cx="486.8" cy="54.9" r="5" fill="#ef4444"/>
|
||||
<circle cx="603.5" cy="85.8" r="5" fill="#ef4444"/>
|
||||
<circle cx="720.2" cy="109.2" r="5" fill="#ef4444"/>
|
||||
<circle cx="836.9" cy="114.2" r="5" fill="#ef4444"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case warn">
|
||||
<div class="case-meta">
|
||||
<div class="case-tag">Genau</div>
|
||||
<div class="case-name">Nyquist-Grenze</div>
|
||||
<div class="case-rate">f<sub>s</sub> = 2 · f</div>
|
||||
<div class="case-note">Sample exakt auf <strong>Peak und Tal</strong>. Mathematisch reicht das — visuell sieht es kaputt aus.</div>
|
||||
</div>
|
||||
<div class="plot">
|
||||
<svg viewBox="0 0 880 140" width="100%" height="140" preserveAspectRatio="none">
|
||||
<line x1="20" y1="70" x2="860" y2="70" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3,4"/>
|
||||
<path d="M 20,70 Q 46.25,-20 72.5,70 T 125,70 T 177.5,70 T 230,70 T 282.5,70 T 335,70 T 387.5,70 T 440,70 T 492.5,70 T 545,70 T 597.5,70 T 650,70 T 702.5,70 T 755,70 T 807.5,70 T 860,70"
|
||||
stroke="#cbd5e1" stroke-width="1.5" fill="none"/>
|
||||
<polyline points="46.25,25 98.75,115 151.25,25 203.75,115 256.25,25 308.75,115 361.25,25 413.75,115 466.25,25 518.75,115 571.25,25 623.75,115 676.25,25 728.75,115 781.25,25 833.75,115"
|
||||
stroke="#f59e0b" stroke-width="2" fill="none" opacity="0.5"/>
|
||||
<circle cx="46.25" cy="25" r="5" fill="#f59e0b"/>
|
||||
<circle cx="98.75" cy="115" r="5" fill="#f59e0b"/>
|
||||
<circle cx="151.25" cy="25" r="5" fill="#f59e0b"/>
|
||||
<circle cx="203.75" cy="115" r="5" fill="#f59e0b"/>
|
||||
<circle cx="256.25" cy="25" r="5" fill="#f59e0b"/>
|
||||
<circle cx="308.75" cy="115" r="5" fill="#f59e0b"/>
|
||||
<circle cx="361.25" cy="25" r="5" fill="#f59e0b"/>
|
||||
<circle cx="413.75" cy="115" r="5" fill="#f59e0b"/>
|
||||
<circle cx="466.25" cy="25" r="5" fill="#f59e0b"/>
|
||||
<circle cx="518.75" cy="115" r="5" fill="#f59e0b"/>
|
||||
<circle cx="571.25" cy="25" r="5" fill="#f59e0b"/>
|
||||
<circle cx="623.75" cy="115" r="5" fill="#f59e0b"/>
|
||||
<circle cx="676.25" cy="25" r="5" fill="#f59e0b"/>
|
||||
<circle cx="728.75" cy="115" r="5" fill="#f59e0b"/>
|
||||
<circle cx="781.25" cy="25" r="5" fill="#f59e0b"/>
|
||||
<circle cx="833.75" cy="115" r="5" fill="#f59e0b"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="case good">
|
||||
<div class="case-meta">
|
||||
<div class="case-tag">Sicher</div>
|
||||
<div class="case-name">Überabtastung</div>
|
||||
<div class="case-rate">f<sub>s</sub> = 8 · f</div>
|
||||
<div class="case-note">Genug Punkte — die Welle wird Punkt für Punkt nachgezeichnet. So macht es eine CD.</div>
|
||||
</div>
|
||||
<div class="plot">
|
||||
<svg viewBox="0 0 880 140" width="100%" height="140" preserveAspectRatio="none">
|
||||
<line x1="20" y1="70" x2="860" y2="70" stroke="#cbd5e1" stroke-width="1" stroke-dasharray="3,4"/>
|
||||
<path d="M 20,70 Q 46.25,-20 72.5,70 T 125,70 T 177.5,70 T 230,70 T 282.5,70 T 335,70 T 387.5,70 T 440,70 T 492.5,70 T 545,70 T 597.5,70 T 650,70 T 702.5,70 T 755,70 T 807.5,70 T 860,70"
|
||||
stroke="#16a34a" stroke-width="2.5" fill="none" opacity="0.9"/>
|
||||
<circle cx="20" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="33.1" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="46.2" cy="25" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="59.4" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="72.5" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="85.6" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="98.8" cy="115" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="111.9" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="125" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="138.1" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="151.2" cy="25" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="164.4" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="177.5" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="190.6" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="203.8" cy="115" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="216.9" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="230" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="243.1" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="256.2" cy="25" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="269.4" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="282.5" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="295.6" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="308.8" cy="115" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="321.9" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="335" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="348.1" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="361.2" cy="25" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="374.4" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="387.5" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="400.6" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="413.8" cy="115" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="426.9" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="440" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="453.1" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="466.2" cy="25" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="479.4" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="492.5" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="505.6" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="518.8" cy="115" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="531.9" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="545" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="558.1" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="571.2" cy="25" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="584.4" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="597.5" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="610.6" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="623.8" cy="115" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="636.9" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="650" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="663.1" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="676.2" cy="25" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="689.4" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="702.5" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="715.6" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="728.8" cy="115" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="741.9" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="755" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="768.1" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="781.2" cy="25" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="794.4" cy="38.2" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="807.5" cy="70" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="820.6" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="833.8" cy="115" r="2.5" fill="#16a34a"/>
|
||||
<circle cx="846.9" cy="101.8" r="2.5" fill="#16a34a"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">f<sub>s</sub> ≥ 2 · f<sub>max</sub>. So einfach. So endgültig.</div>
|
||||
<div class="verdict-a">Das Ohr hört bis <strong>20 kHz</strong>. 2 × 20 = 40 kHz. Die CD nimmt <code>44,1 kHz</code> — die 4,1 sind Sicherheits-Marge.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 140 KiB |
@@ -0,0 +1,173 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Wir haben gelernt zu lesen — jetzt: wie kommt es rein?</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--done: #94a3b8;
|
||||
--next: #d63384;
|
||||
--bytes: #0891b2;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 32px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.7rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 36px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.pivot {
|
||||
display: grid;
|
||||
grid-template-columns: 200px 1fr 240px 1fr 200px;
|
||||
gap: 8px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.station {
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 18px 14px;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.station.bytes { border-color: var(--bytes); background: #0f0f23; color: #fff; }
|
||||
.station-tag {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.station.bytes .station-tag { color: #94a3b8; }
|
||||
.station-name { font-size: 1.3rem; font-weight: 700; }
|
||||
.station.bytes .station-name { color: #67e8f9; }
|
||||
.station-detail { font-size: 0.85rem; color: var(--muted); }
|
||||
.station.bytes .station-detail { color: #94a3b8; font-family: ui-monospace, monospace; font-size: 0.78rem; line-height: 1.4; }
|
||||
|
||||
.station-art { display: flex; align-items: center; justify-content: center; height: 70px; }
|
||||
.station-art svg { width: 80%; height: 100%; }
|
||||
|
||||
.strang {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
.strang-arrow svg { display: block; width: 100%; height: 28px; }
|
||||
.strang-label {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.strang.next .strang-label { color: var(--next); }
|
||||
.strang.done .strang-label { color: var(--done); text-decoration: line-through; text-decoration-color: var(--done); text-decoration-thickness: 1.5px; }
|
||||
.strang-tag {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
text-align: center;
|
||||
}
|
||||
.strang.next .strang-tag { color: var(--next); }
|
||||
.strang.done .strang-tag { color: var(--done); }
|
||||
|
||||
.verdict {
|
||||
margin-top: 28px;
|
||||
max-width: 1100px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.1rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.95rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Wir haben gelernt zu <strong>lesen</strong>. Jetzt: wie kommt es <strong>rein</strong>?</h1>
|
||||
<div class="sub">Die rechte Seite haben wir abgearbeitet. Die linke ist die nächste Stunde.</div>
|
||||
|
||||
<div class="pivot">
|
||||
|
||||
<div class="station">
|
||||
<div class="station-tag">Quelle</div>
|
||||
<div class="station-name">Welt</div>
|
||||
<div class="station-art">
|
||||
<svg viewBox="0 0 120 70" preserveAspectRatio="xMidYMid meet">
|
||||
<path d="M5,35 Q15,10 25,35 T45,35 T65,35 T85,35 T105,35 T115,35"
|
||||
stroke="#d63384" stroke-width="2.5" fill="none"/>
|
||||
<circle cx="115" cy="35" r="3" fill="#d63384"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="station-detail">Schallwelle · Lichtwelle<br>kontinuierlich · analog</div>
|
||||
</div>
|
||||
|
||||
<div class="strang next">
|
||||
<div class="strang-tag">jetzt dran</div>
|
||||
<div class="strang-arrow">
|
||||
<svg viewBox="0 0 220 28" preserveAspectRatio="none">
|
||||
<line x1="6" y1="14" x2="200" y2="14" stroke="#d63384" stroke-width="2.5"/>
|
||||
<path d="M195,7 L208,14 L195,21" stroke="#d63384" stroke-width="2.5" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="strang-label">Sampling + Quantisierung</div>
|
||||
</div>
|
||||
|
||||
<div class="station bytes">
|
||||
<div class="station-tag">Mitte</div>
|
||||
<div class="station-name">Datei</div>
|
||||
<div class="station-detail">
|
||||
89 50 4E 47<br>
|
||||
0D 0A 1A 0A<br>
|
||||
00 00 00 0D
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="strang done">
|
||||
<div class="strang-tag">schon durch</div>
|
||||
<div class="strang-arrow">
|
||||
<svg viewBox="0 0 220 28" preserveAspectRatio="none">
|
||||
<line x1="6" y1="14" x2="200" y2="14" stroke="#94a3b8" stroke-width="2"/>
|
||||
<path d="M195,7 L208,14 L195,21" stroke="#94a3b8" stroke-width="2" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="strang-label">Notation · Encoding · Format</div>
|
||||
</div>
|
||||
|
||||
<div class="station">
|
||||
<div class="station-tag">Ziel</div>
|
||||
<div class="station-name">Mensch</div>
|
||||
<div class="station-art">
|
||||
<svg viewBox="0 0 120 70" preserveAspectRatio="xMidYMid meet">
|
||||
<rect x="14" y="14" width="92" height="14" rx="2" fill="#1a1a2e"/>
|
||||
<rect x="14" y="34" width="68" height="6" rx="1" fill="#cbd5e1"/>
|
||||
<rect x="14" y="44" width="78" height="6" rx="1" fill="#cbd5e1"/>
|
||||
<rect x="14" y="54" width="56" height="6" rx="1" fill="#cbd5e1"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="station-detail">Text · Bild · Klang<br>Bedeutung</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Die Datei ist die Mitte. Beide Wege führen durch sie hindurch.</div>
|
||||
<div class="verdict-a"><strong>Rechts:</strong> aus Byte wird Bedeutung — gerade gelernt. <strong>Links:</strong> aus Welt wird Byte — jetzt.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 61 KiB |
@@ -0,0 +1,159 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Psychoakustik — was MP3 weglässt</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--kept: #16a34a;
|
||||
--tossed: #94a3b8;
|
||||
--masker: #2563eb;
|
||||
--threshold: #ea580c;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 30px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 22px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.chart-wrap {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.chart-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.chart-title { font-size: 1rem; font-weight: 700; }
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.legend-item { display: flex; align-items: center; gap: 4px; }
|
||||
.legend-swatch {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
.legend-swatch.kept { background: var(--kept); }
|
||||
.legend-swatch.tossed { background: var(--tossed); }
|
||||
.legend-swatch.masker { background: var(--masker); }
|
||||
.legend-swatch.threshold {
|
||||
background: transparent;
|
||||
border-top: 2px dashed var(--threshold);
|
||||
height: 8px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.verdict {
|
||||
margin-top: 22px;
|
||||
max-width: 1180px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.1rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.93rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Psychoakustik — was MP3 wegwirft</h1>
|
||||
<div class="sub">Ein lauter Ton macht leise Nachbarn <strong>unhörbar</strong>. Was nicht hörbar ist, muss man nicht speichern.</div>
|
||||
|
||||
<div class="chart-wrap">
|
||||
<div class="chart-head">
|
||||
<div class="chart-title">Lautstärke (dB SPL) über Frequenz (Hz, logarithmisch)</div>
|
||||
<div class="legend">
|
||||
<span class="legend-item"><span class="legend-swatch masker"></span>lauter Ton (Masker)</span>
|
||||
<span class="legend-item"><span class="legend-swatch kept"></span>hörbar · bleibt</span>
|
||||
<span class="legend-item"><span class="legend-swatch tossed"></span>maskiert · raus</span>
|
||||
<span class="legend-item"><span class="legend-swatch threshold"></span>Hörschwelle</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svg viewBox="0 0 1100 380" width="100%" height="380">
|
||||
<line x1="80" y1="340" x2="1060" y2="340" stroke="#1a1a2e" stroke-width="1.5"/>
|
||||
<line x1="80" y1="20" x2="80" y2="340" stroke="#1a1a2e" stroke-width="1.5"/>
|
||||
|
||||
<text x="20" y="30" font-size="11" fill="#6b7280">dB</text>
|
||||
<text x="40" y="50" font-size="10" fill="#6b7280" text-anchor="end">100</text>
|
||||
<text x="40" y="125" font-size="10" fill="#6b7280" text-anchor="end">75</text>
|
||||
<text x="40" y="200" font-size="10" fill="#6b7280" text-anchor="end">50</text>
|
||||
<text x="40" y="275" font-size="10" fill="#6b7280" text-anchor="end">25</text>
|
||||
<text x="40" y="345" font-size="10" fill="#6b7280" text-anchor="end">0</text>
|
||||
|
||||
<line x1="75" y1="50" x2="80" y2="50" stroke="#1a1a2e"/>
|
||||
<line x1="75" y1="125" x2="80" y2="125" stroke="#1a1a2e"/>
|
||||
<line x1="75" y1="200" x2="80" y2="200" stroke="#1a1a2e"/>
|
||||
<line x1="75" y1="275" x2="80" y2="275" stroke="#1a1a2e"/>
|
||||
|
||||
<text x="100" y="360" font-size="10" fill="#6b7280">20</text>
|
||||
<text x="260" y="360" font-size="10" fill="#6b7280">100</text>
|
||||
<text x="420" y="360" font-size="10" fill="#6b7280">500</text>
|
||||
<text x="580" y="360" font-size="10" fill="#6b7280">1k</text>
|
||||
<text x="740" y="360" font-size="10" fill="#6b7280">5k</text>
|
||||
<text x="900" y="360" font-size="10" fill="#6b7280">10k</text>
|
||||
<text x="1040" y="360" font-size="10" fill="#6b7280">20k</text>
|
||||
<text x="540" y="378" font-size="11" fill="#6b7280" text-anchor="middle">Frequenz [Hz]</text>
|
||||
|
||||
<path d="M100,80 Q170,130 260,200 Q350,290 420,310 Q500,335 580,330 Q670,310 740,295 Q820,280 900,250 Q970,200 1040,80"
|
||||
stroke="#ea580c" stroke-width="2" stroke-dasharray="5,5" fill="none"/>
|
||||
|
||||
<path d="M460,340 L460,210 Q480,165 580,80 Q680,165 700,210 L700,340 Z"
|
||||
fill="#2563eb" opacity="0.15"/>
|
||||
<path d="M460,340 L460,210 Q480,165 580,80 Q680,165 700,210 L700,340"
|
||||
stroke="#2563eb" stroke-width="2" fill="none" stroke-dasharray="2,3"/>
|
||||
|
||||
<rect x="572" y="80" width="16" height="260" fill="#2563eb"/>
|
||||
|
||||
<circle cx="240" cy="270" r="6" fill="#16a34a"/>
|
||||
<text x="240" y="260" font-size="10" fill="#16a34a" text-anchor="middle" font-weight="700">bleibt</text>
|
||||
|
||||
<circle cx="420" cy="240" r="6" fill="#16a34a"/>
|
||||
<text x="420" y="230" font-size="10" fill="#16a34a" text-anchor="middle" font-weight="700">bleibt</text>
|
||||
|
||||
<circle cx="510" cy="180" r="6" fill="#94a3b8"/>
|
||||
<text x="510" y="170" font-size="10" fill="#94a3b8" text-anchor="middle">raus</text>
|
||||
|
||||
<circle cx="660" cy="200" r="6" fill="#94a3b8"/>
|
||||
<text x="660" y="190" font-size="10" fill="#94a3b8" text-anchor="middle">raus</text>
|
||||
|
||||
<circle cx="640" cy="155" r="6" fill="#94a3b8"/>
|
||||
<text x="640" y="145" font-size="10" fill="#94a3b8" text-anchor="middle">raus</text>
|
||||
|
||||
<circle cx="860" cy="220" r="6" fill="#16a34a"/>
|
||||
<text x="860" y="210" font-size="10" fill="#16a34a" text-anchor="middle" font-weight="700">bleibt</text>
|
||||
|
||||
<circle cx="960" cy="170" r="6" fill="#16a34a"/>
|
||||
<text x="960" y="160" font-size="10" fill="#16a34a" text-anchor="middle" font-weight="700">bleibt</text>
|
||||
|
||||
<text x="595" y="76" font-size="11" fill="#2563eb" font-weight="700">1 kHz · 90 dB · "Masker"</text>
|
||||
<text x="780" y="105" font-size="11" fill="#ea580c" font-weight="700">Hörschwelle (in Ruhe)</text>
|
||||
<text x="540" y="160" font-size="11" fill="#2563eb" font-weight="700" text-anchor="middle">Maskierungs-Bereich</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">MP3 kodiert nur, was über der <strong>kombinierten Schwelle</strong> liegt.</div>
|
||||
<div class="verdict-a">Alles unterhalb wird weggeworfen — das Ohr hört es ohnehin nicht. So spart MP3 typischerweise <strong>~90 % der Daten</strong>, ohne dass der Mensch im Alltag etwas vermisst.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 77 KiB |
@@ -0,0 +1,222 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Psychovisualität — Luminanz vor Chrominanz</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--full: #2563eb;
|
||||
--reduced: #7c3aed;
|
||||
--y: #1a1a2e;
|
||||
--cb: #2563eb;
|
||||
--cr: #ef4444;
|
||||
--bar-bg: #e2e8f0;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 30px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 24px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.compare {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 22px;
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.col {
|
||||
border: 2px solid;
|
||||
border-radius: 12px;
|
||||
padding: 16px 20px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.col.full { border-color: var(--full); }
|
||||
.col.reduced { border-color: var(--reduced); }
|
||||
|
||||
.col-head { display: flex; justify-content: space-between; align-items: baseline; }
|
||||
.col-tag {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.col-name { font-size: 1.25rem; font-weight: 700; }
|
||||
.col.full .col-name { color: var(--full); }
|
||||
.col.reduced .col-name { color: var(--reduced); }
|
||||
.col-format {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.82rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.preview {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.preview svg { display: block; }
|
||||
|
||||
.channels {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.channel { display: grid; grid-template-columns: 90px 1fr 60px; gap: 8px; align-items: center; font-size: 0.85rem; }
|
||||
.channel-label {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-weight: 700;
|
||||
}
|
||||
.channel-bar { height: 14px; background: var(--bar-bg); border-radius: 3px; overflow: hidden; }
|
||||
.channel-bar .fill { height: 100%; border-radius: 3px; }
|
||||
.channel-bar .fill.y { background: var(--y); }
|
||||
.channel-bar .fill.cb { background: var(--cb); }
|
||||
.channel-bar .fill.cr { background: var(--cr); }
|
||||
.channel-pct {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.summary-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: var(--soft);
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.85rem;
|
||||
color: var(--dark);
|
||||
}
|
||||
.summary-line strong { color: var(--hl); }
|
||||
|
||||
.verdict {
|
||||
margin-top: 22px;
|
||||
max-width: 1180px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.1rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.93rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Psychovisualität — Luminanz vor Chrominanz</h1>
|
||||
<div class="sub">Das Auge sieht <strong>Helligkeit</strong> scharf. <strong>Farbe</strong> darf grob bleiben — und das Bild sieht trotzdem identisch aus.</div>
|
||||
|
||||
<div class="compare">
|
||||
|
||||
<div class="col full">
|
||||
<div class="col-head">
|
||||
<span class="col-name">Ohne Subsampling</span>
|
||||
<span class="col-tag">4:4:4</span>
|
||||
</div>
|
||||
<div class="col-format">jedes Pixel speichert Y · Cb · Cr in voller Auflösung</div>
|
||||
|
||||
<svg class="preview" viewBox="0 0 320 130" preserveAspectRatio="xMidYMid slice">
|
||||
<defs>
|
||||
<linearGradient id="full-sky" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#fde68a"/>
|
||||
<stop offset="55%" stop-color="#fb923c"/>
|
||||
<stop offset="100%" stop-color="#7c3aed"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="320" height="130" fill="url(#full-sky)"/>
|
||||
<circle cx="230" cy="50" r="22" fill="#fef3c7"/>
|
||||
<path d="M0,90 L40,75 L90,87 L140,68 L190,82 L240,66 L290,80 L320,72 L320,130 L0,130 Z" fill="#1e1b4b"/>
|
||||
<path d="M0,108 L60,100 L120,108 L180,98 L240,106 L300,98 L320,103 L320,130 L0,130 Z" fill="#0f172a" opacity="0.7"/>
|
||||
</svg>
|
||||
|
||||
<div class="channels">
|
||||
<div class="channel">
|
||||
<span class="channel-label">Y</span>
|
||||
<div class="channel-bar"><div class="fill y" style="width:100%"></div></div>
|
||||
<span class="channel-pct">100 %</span>
|
||||
</div>
|
||||
<div class="channel">
|
||||
<span class="channel-label">Cb</span>
|
||||
<div class="channel-bar"><div class="fill cb" style="width:100%"></div></div>
|
||||
<span class="channel-pct">100 %</span>
|
||||
</div>
|
||||
<div class="channel">
|
||||
<span class="channel-label">Cr</span>
|
||||
<div class="channel-bar"><div class="fill cr" style="width:100%"></div></div>
|
||||
<span class="channel-pct">100 %</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-line">
|
||||
<span>Datenmenge:</span>
|
||||
<span><strong>100 %</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col reduced">
|
||||
<div class="col-head">
|
||||
<span class="col-name">JPEG · H.264 Standard</span>
|
||||
<span class="col-tag">4:2:0</span>
|
||||
</div>
|
||||
<div class="col-format">Y voll · Cb & Cr nur in 2×2-Pixel-Blöcken</div>
|
||||
|
||||
<svg class="preview" viewBox="0 0 320 130" preserveAspectRatio="xMidYMid slice">
|
||||
<defs>
|
||||
<linearGradient id="red-sky" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#fde68a"/>
|
||||
<stop offset="55%" stop-color="#fb923c"/>
|
||||
<stop offset="100%" stop-color="#7c3aed"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="320" height="130" fill="url(#red-sky)"/>
|
||||
<circle cx="230" cy="50" r="22" fill="#fef3c7"/>
|
||||
<path d="M0,90 L40,75 L90,87 L140,68 L190,82 L240,66 L290,80 L320,72 L320,130 L0,130 Z" fill="#1e1b4b"/>
|
||||
<path d="M0,108 L60,100 L120,108 L180,98 L240,106 L300,98 L320,103 L320,130 L0,130 Z" fill="#0f172a" opacity="0.7"/>
|
||||
</svg>
|
||||
|
||||
<div class="channels">
|
||||
<div class="channel">
|
||||
<span class="channel-label">Y</span>
|
||||
<div class="channel-bar"><div class="fill y" style="width:100%"></div></div>
|
||||
<span class="channel-pct">100 %</span>
|
||||
</div>
|
||||
<div class="channel">
|
||||
<span class="channel-label">Cb</span>
|
||||
<div class="channel-bar"><div class="fill cb" style="width:25%"></div></div>
|
||||
<span class="channel-pct">25 %</span>
|
||||
</div>
|
||||
<div class="channel">
|
||||
<span class="channel-label">Cr</span>
|
||||
<div class="channel-bar"><div class="fill cr" style="width:25%"></div></div>
|
||||
<span class="channel-pct">25 %</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-line">
|
||||
<span>Datenmenge:</span>
|
||||
<span><strong>50 %</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Halbe Datenmenge. Identisches Bild — für dein Auge.</div>
|
||||
<div class="verdict-a">JPEG, H.264, H.265, WebP — alle reduzieren die Farb-Auflösung auf <strong>ein Viertel</strong>. Das ist Psychovisualität: was wir nicht scharf sehen, muss man nicht scharf speichern.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,212 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Raster vs. Vektor — Pixel oder Anweisung?</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--raster: #ea580c;
|
||||
--vektor: #2563eb;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 30px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 24px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.pair {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 22px;
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.col {
|
||||
border: 2px solid;
|
||||
border-radius: 12px;
|
||||
padding: 16px 18px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.col.raster { border-color: var(--raster); }
|
||||
.col.vektor { border-color: var(--vektor); }
|
||||
|
||||
.col-head { display: flex; justify-content: space-between; align-items: baseline; }
|
||||
.col-name { font-size: 1.3rem; font-weight: 700; }
|
||||
.col.raster .col-name { color: var(--raster); }
|
||||
.col.vektor .col-name { color: var(--vektor); }
|
||||
.col-tag {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.col-desc { font-size: 0.85rem; color: var(--muted); line-height: 1.4; }
|
||||
|
||||
.views {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
.view { display: flex; flex-direction: column; gap: 4px; }
|
||||
.view-label {
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
}
|
||||
.view-canvas {
|
||||
background: var(--soft);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
aspect-ratio: 1.4;
|
||||
overflow: hidden;
|
||||
}
|
||||
.view-canvas svg { display: block; width: 100%; height: 100%; }
|
||||
|
||||
.stats {
|
||||
background: var(--soft);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.82rem;
|
||||
color: var(--dark);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.stats .key { color: var(--muted); }
|
||||
.stats .val { font-weight: 700; }
|
||||
|
||||
.verdict {
|
||||
margin-top: 22px;
|
||||
max-width: 1180px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.1rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.93rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Raster vs. Vektor — Pixel oder Anweisung?</h1>
|
||||
<div class="sub">Beide zeigen ein Häkchen. Wenn man <strong>reinzoomt</strong>, kommt die Wahrheit raus.</div>
|
||||
|
||||
<div class="pair">
|
||||
|
||||
<div class="col raster">
|
||||
<div class="col-head">
|
||||
<span class="col-name">Raster</span>
|
||||
<span class="col-tag">PNG · JPEG · GIF</span>
|
||||
</div>
|
||||
<div class="col-desc">Ein Gitter aus Pixel-Farbwerten. Jede Position ist festgelegt.</div>
|
||||
|
||||
<div class="views">
|
||||
<div class="view">
|
||||
<div class="view-label">Original (32 × 22 px)</div>
|
||||
<div class="view-canvas">
|
||||
<svg viewBox="0 0 320 220" preserveAspectRatio="xMidYMid meet">
|
||||
<rect x="0" y="0" width="320" height="220" fill="#f8fafc"/>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect x="20" y="120" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="40" y="140" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="60" y="160" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="80" y="180" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="100" y="160" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="120" y="140" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="140" y="120" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="160" y="100" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="180" y="80" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="200" y="60" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="220" y="40" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="240" y="20" width="20" height="20" fill="#ea580c"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view">
|
||||
<div class="view-label">4× Zoom</div>
|
||||
<div class="view-canvas">
|
||||
<svg viewBox="80 60 160 160" preserveAspectRatio="xMidYMid meet">
|
||||
<rect x="0" y="0" width="320" height="220" fill="#f8fafc"/>
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect x="20" y="120" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="40" y="140" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="60" y="160" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="80" y="180" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="100" y="160" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="120" y="140" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="140" y="120" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="160" y="100" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="180" y="80" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="200" y="60" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="220" y="40" width="20" height="20" fill="#ea580c"/>
|
||||
<rect x="240" y="20" width="20" height="20" fill="#ea580c"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div><span class="key">Speicher:</span> <span class="val">32 × 22 × 3 Byte = 2 112 Byte</span></div>
|
||||
<div><span class="key">Beim Zoom:</span> <span class="val">pixelig — Information ist erschöpft</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col vektor">
|
||||
<div class="col-head">
|
||||
<span class="col-name">Vektor</span>
|
||||
<span class="col-tag">SVG · PDF · Schriftarten</span>
|
||||
</div>
|
||||
<div class="col-desc">Mathematische Anweisungen: <code style="background:#0f0f23;color:#5fb3e4;padding:0 4px;border-radius:3px;">polyline 30,110 → 90,170 → 250,30</code></div>
|
||||
|
||||
<div class="views">
|
||||
<div class="view">
|
||||
<div class="view-label">Original</div>
|
||||
<div class="view-canvas">
|
||||
<svg viewBox="0 0 320 220" preserveAspectRatio="xMidYMid meet">
|
||||
<rect x="0" y="0" width="320" height="220" fill="#f8fafc"/>
|
||||
<polyline points="30,130 90,190 270,30" stroke="#2563eb" stroke-width="14" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view">
|
||||
<div class="view-label">4× Zoom</div>
|
||||
<div class="view-canvas">
|
||||
<svg viewBox="80 60 160 160" preserveAspectRatio="xMidYMid meet">
|
||||
<rect x="0" y="0" width="320" height="220" fill="#f8fafc"/>
|
||||
<polyline points="30,130 90,190 270,30" stroke="#2563eb" stroke-width="14" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div><span class="key">Speicher:</span> <span class="val">3 Punkte + Strichbreite = ~80 Byte</span></div>
|
||||
<div><span class="key">Beim Zoom:</span> <span class="val">scharf — wird neu berechnet</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Pixel sind festgenagelt. Vektoren werden bei jedem Zoom <strong>neu berechnet</strong>.</div>
|
||||
<div class="verdict-a">Faustregel: <strong>Foto → Raster</strong> (kann nicht „neu berechnet" werden). <strong>Logo, Icon, Schrift → Vektor</strong> (skalierbar ohne Verlust).</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,189 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Was wir über Kompression gelernt haben</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--lossless: #16a34a;
|
||||
--lossy: #ef4444;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 30px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 26px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.compare {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 22px;
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.col {
|
||||
border: 2px solid;
|
||||
border-radius: 12px;
|
||||
padding: 18px 22px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.col.lossless { border-color: var(--lossless); }
|
||||
.col.lossy { border-color: var(--lossy); }
|
||||
|
||||
.col-head { display: flex; justify-content: space-between; align-items: baseline; }
|
||||
.col-name { font-size: 1.4rem; font-weight: 700; }
|
||||
.col.lossless .col-name { color: var(--lossless); }
|
||||
.col.lossy .col-name { color: var(--lossy); }
|
||||
.col-tag {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.row { display: grid; grid-template-columns: 120px 1fr; gap: 10px 14px; }
|
||||
.row-key {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--muted);
|
||||
padding-top: 3px;
|
||||
}
|
||||
.row-val { font-size: 0.92rem; line-height: 1.5; color: var(--dark); }
|
||||
.row-val strong {
|
||||
font-family: ui-monospace, monospace;
|
||||
background: var(--soft);
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.matrix {
|
||||
margin-top: 22px;
|
||||
max-width: 1180px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background: var(--soft);
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.matrix-head {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.matrix-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
gap: 8px 16px;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
.matrix-grid .scenario { font-weight: 700; color: var(--dark); }
|
||||
.matrix-grid .answer {
|
||||
font-family: ui-monospace, monospace;
|
||||
color: var(--dark);
|
||||
}
|
||||
.matrix-grid .answer.lossless { color: var(--lossless); font-weight: 700; }
|
||||
.matrix-grid .answer.lossy { color: var(--lossy); font-weight: 700; }
|
||||
|
||||
.verdict {
|
||||
margin-top: 18px;
|
||||
max-width: 1180px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.1rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.93rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Was wir über Kompression gelernt haben</h1>
|
||||
<div class="sub">Zwei fundamental verschiedene Wege, dasselbe Ziel: <strong>Daten kleiner machen.</strong></div>
|
||||
|
||||
<div class="compare">
|
||||
|
||||
<div class="col lossless">
|
||||
<div class="col-head">
|
||||
<span class="col-name">Verlustfrei</span>
|
||||
<span class="col-tag">Lossless</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row-key">Was passiert</div>
|
||||
<div class="row-val">Redundanz raus — Wiederholungen kürzer schreiben</div>
|
||||
<div class="row-key">Umkehrbar</div>
|
||||
<div class="row-val">Ja. Aus der komprimierten Datei wird das <strong>exakte</strong> Original wieder.</div>
|
||||
<div class="row-key">Verfahren</div>
|
||||
<div class="row-val">RLE, Huffman, Deflate, LZ77/LZ78</div>
|
||||
<div class="row-key">Formate</div>
|
||||
<div class="row-val">ZIP · PNG · FLAC · TIFF · RAW</div>
|
||||
<div class="row-key">Faktor</div>
|
||||
<div class="row-val">2× – 5× (typisch)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col lossy">
|
||||
<div class="col-head">
|
||||
<span class="col-name">Verlustbehaftet</span>
|
||||
<span class="col-tag">Lossy</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row-key">Was passiert</div>
|
||||
<div class="row-val">Irrelevanz raus — was Mensch nicht wahrnimmt, wird weggeworfen</div>
|
||||
<div class="row-key">Umkehrbar</div>
|
||||
<div class="row-val">Nein. Einmal weg, für immer weg.</div>
|
||||
<div class="row-key">Trick</div>
|
||||
<div class="row-val">Psychoakustik (MP3) · Psychovisualität (JPEG)</div>
|
||||
<div class="row-key">Formate</div>
|
||||
<div class="row-val">MP3 · AAC · JPEG · WebP · H.264 · H.265</div>
|
||||
<div class="row-key">Faktor</div>
|
||||
<div class="row-val">10× – 100× (typisch)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="matrix">
|
||||
<div class="matrix-head">Entscheidungsmatrix — wann nehme ich welches?</div>
|
||||
<div class="matrix-grid">
|
||||
<div class="scenario">Foto-Archiv (RAW)</div>
|
||||
<div class="answer lossless">Lossless</div>
|
||||
<div class="scenario">Spotify-Stream</div>
|
||||
<div class="answer lossy">Lossy</div>
|
||||
|
||||
<div class="scenario">Code-Repository</div>
|
||||
<div class="answer lossless">Lossless (ZIP)</div>
|
||||
<div class="scenario">Instagram-Foto</div>
|
||||
<div class="answer lossy">Lossy (JPEG)</div>
|
||||
|
||||
<div class="scenario">Studio-Master-Track</div>
|
||||
<div class="answer lossless">Lossless (FLAC)</div>
|
||||
<div class="scenario">YouTube-Video</div>
|
||||
<div class="answer lossy">Lossy (H.264)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Lossless für Archiv und Code. Lossy für Streaming und Anzeige.</div>
|
||||
<div class="verdict-a">Faustregel: <strong>Werkzeug</strong> (du arbeitest noch dran) → Lossless. <strong>Konsum</strong> (du schaust/hörst nur) → Lossy.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 108 KiB |
@@ -0,0 +1,188 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Was wir in 90 Minuten gelernt haben</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--left: #d63384;
|
||||
--right: #0891b2;
|
||||
--bytes: #0891b2;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 30px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 28px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.recap {
|
||||
display: grid;
|
||||
grid-template-columns: 160px 1fr 200px 1fr 160px;
|
||||
gap: 10px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.station {
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 18px 12px;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.station.bytes { border-color: var(--bytes); background: #0f0f23; color: #fff; }
|
||||
.station-tag {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.station.bytes .station-tag { color: #94a3b8; }
|
||||
.station-name { font-size: 1.3rem; font-weight: 700; }
|
||||
.station.bytes .station-name { color: #67e8f9; }
|
||||
.station-detail { font-size: 0.78rem; color: var(--muted); line-height: 1.35; }
|
||||
.station.bytes .station-detail { color: #94a3b8; font-family: ui-monospace, monospace; font-size: 0.76rem; line-height: 1.35; }
|
||||
|
||||
.strang {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
.strang-arrow { text-align: center; }
|
||||
.strang-arrow svg { width: 100%; height: 18px; }
|
||||
.strang-title {
|
||||
font-size: 0.92rem;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.strang.left .strang-title { color: var(--left); }
|
||||
.strang.right .strang-title { color: var(--right); }
|
||||
.strang-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--soft);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.strang-list li {
|
||||
font-size: 0.82rem;
|
||||
color: var(--dark);
|
||||
padding-left: 12px;
|
||||
position: relative;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.strang-list li::before {
|
||||
content: "·";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
.strang.left .strang-list li::before { color: var(--left); }
|
||||
.strang.right .strang-list li::before { color: var(--right); }
|
||||
|
||||
.verdict {
|
||||
margin-top: 22px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.1rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.95rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Was wir in 90 Minuten gelernt haben</h1>
|
||||
<div class="sub">Beide Wege gehen durch <strong>dieselbe Mitte</strong>. Du kennst jetzt alle Stationen.</div>
|
||||
|
||||
<div class="recap">
|
||||
|
||||
<div class="station">
|
||||
<div class="station-tag">Quelle</div>
|
||||
<div class="station-name">Welt</div>
|
||||
<div class="station-detail">Schallwelle<br>Lichtwelle<br>kontinuierlich · analog</div>
|
||||
</div>
|
||||
|
||||
<div class="strang left">
|
||||
<div class="strang-arrow">
|
||||
<svg viewBox="0 0 200 18" preserveAspectRatio="none">
|
||||
<line x1="6" y1="9" x2="180" y2="9" stroke="#d63384" stroke-width="2"/>
|
||||
<path d="M174,3 L188,9 L174,15" stroke="#d63384" stroke-width="2" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="strang-title">Welt → Byte</div>
|
||||
<ul class="strang-list">
|
||||
<li>Sampling-Rate</li>
|
||||
<li>Bittiefe · Quantisierung</li>
|
||||
<li>Datenrate-Formel</li>
|
||||
<li>Nyquist · 2·f<sub>max</sub></li>
|
||||
<li>Aliasing & Moiré</li>
|
||||
<li>44,1 kHz = 2·20 kHz + Marge</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="station bytes">
|
||||
<div class="station-tag">Mitte</div>
|
||||
<div class="station-name">Datei</div>
|
||||
<div class="station-detail">
|
||||
Byte-Strom<br>
|
||||
89 50 4E 47<br>
|
||||
nichts als Zahlen
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="strang right">
|
||||
<div class="strang-arrow">
|
||||
<svg viewBox="0 0 200 18" preserveAspectRatio="none">
|
||||
<line x1="6" y1="9" x2="180" y2="9" stroke="#0891b2" stroke-width="2"/>
|
||||
<path d="M174,3 L188,9 L174,15" stroke="#0891b2" stroke-width="2" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="strang-title">Byte → Bedeutung</div>
|
||||
<ul class="strang-list">
|
||||
<li>Bit · Byte · Hex</li>
|
||||
<li>8 Bit = 256 Zustände</li>
|
||||
<li>ASCII · UTF-8</li>
|
||||
<li>Magic Number</li>
|
||||
<li>KB vs KiB</li>
|
||||
<li>Hex im Alltag (CSS, MAC)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="station">
|
||||
<div class="station-tag">Ziel</div>
|
||||
<div class="station-name">Mensch</div>
|
||||
<div class="station-detail">Text · Bild · Klang<br>Bedeutung<br>verstehen</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Eine Datei ist kein Mysterium. Es ist eine <strong>Reihe von Zahlen</strong> mit zwei Geschichten dran.</div>
|
||||
<div class="verdict-a">Eine Geschichte erzählt, <strong>woher</strong> die Zahlen kommen. Die andere, <strong>was</strong> sie bedeuten. Beide kennst du jetzt.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,217 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>USB-C — drei Achsen, eine Verwirrung</title>
|
||||
<style>
|
||||
:root {
|
||||
--dark: #1a1a2e;
|
||||
--muted: #6b7280;
|
||||
--soft: #f8fafc;
|
||||
--border: #cbd5e1;
|
||||
--hl: #d63384;
|
||||
--stecker: #2563eb;
|
||||
--kabel: #ea580c;
|
||||
--protokoll: #7c3aed;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, -apple-system, sans-serif; color: var(--dark); }
|
||||
body { padding: 28px 40px; }
|
||||
h1 { text-align: center; margin: 0 0 6px; font-size: 1.75rem; }
|
||||
.sub { text-align: center; color: var(--muted); margin-bottom: 22px; font-size: 1.05rem; }
|
||||
.sub strong { color: var(--dark); }
|
||||
|
||||
.axes {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 18px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.axis {
|
||||
border: 2px solid;
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.axis.stecker { border-color: var(--stecker); }
|
||||
.axis.kabel { border-color: var(--kabel); }
|
||||
.axis.protokoll { border-color: var(--protokoll); }
|
||||
|
||||
.axis-head { display: flex; justify-content: space-between; align-items: baseline; }
|
||||
.axis-num {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
color: var(--muted);
|
||||
}
|
||||
.axis-name { font-size: 1.15rem; font-weight: 700; }
|
||||
.axis.stecker .axis-name { color: var(--stecker); }
|
||||
.axis.kabel .axis-name { color: var(--kabel); }
|
||||
.axis.protokoll .axis-name { color: var(--protokoll); }
|
||||
.axis-tagline {
|
||||
font-size: 0.82rem;
|
||||
color: var(--muted);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.axis-tagline strong { color: var(--dark); }
|
||||
|
||||
.icon {
|
||||
background: var(--soft);
|
||||
border-radius: 6px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.icon svg { display: block; }
|
||||
|
||||
.options {
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.6;
|
||||
color: var(--dark);
|
||||
background: var(--soft);
|
||||
border-radius: 6px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
.options strong {
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 0.78rem;
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.axis.stecker .options strong { color: var(--stecker); }
|
||||
.axis.kabel .options strong { color: var(--kabel); }
|
||||
.axis.protokoll .options strong { color: var(--protokoll); }
|
||||
|
||||
.scenario {
|
||||
margin-top: 22px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background: var(--soft);
|
||||
border: 1.5px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.scenario-head {
|
||||
font-size: 0.74rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--muted);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.scenario-body {
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.scenario-body code {
|
||||
font-family: ui-monospace, monospace;
|
||||
background: #fff;
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.verdict {
|
||||
margin-top: 16px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
border: 2px solid var(--hl);
|
||||
border-radius: 12px;
|
||||
padding: 14px 22px;
|
||||
}
|
||||
.verdict-q { font-size: 1.05rem; font-weight: 700; color: var(--hl); }
|
||||
.verdict-a { font-size: 0.92rem; color: var(--dark); margin-top: 4px; }
|
||||
.verdict-a strong { color: var(--hl); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>USB-C — drei Achsen, eine Verwirrung</h1>
|
||||
<div class="sub">Derselbe Stecker. <strong>Aber Kabel ≠ Kabel.</strong> <strong>Protokoll ≠ Protokoll.</strong></div>
|
||||
|
||||
<div class="axes">
|
||||
|
||||
<div class="axis stecker">
|
||||
<div class="axis-head">
|
||||
<span class="axis-name">Stecker</span>
|
||||
<span class="axis-num">1 · MECHANIK</span>
|
||||
</div>
|
||||
<div class="axis-tagline">Die <strong>Form</strong>. Was reinsteckt, oval, beidseitig.</div>
|
||||
<div class="icon">
|
||||
<svg viewBox="0 0 120 30" width="140" height="40">
|
||||
<rect x="6" y="9" width="108" height="12" rx="6" fill="#2563eb" opacity="0.18" stroke="#2563eb" stroke-width="2"/>
|
||||
<rect x="22" y="13" width="76" height="4" rx="2" fill="#2563eb"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="options">
|
||||
Nur eine Sorte: <strong>USB-C</strong> (auch USB Type-C). 24 Pins. Beidseitig steckbar. Andere Stecker-Typen: USB-A, USB-B, Micro-USB — nicht mit C kompatibel.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="axis kabel">
|
||||
<div class="axis-head">
|
||||
<span class="axis-name">Kabel</span>
|
||||
<span class="axis-num">2 · WAS DURCH-PASST</span>
|
||||
</div>
|
||||
<div class="axis-tagline">Die <strong>Adern</strong>. Was elektrisch transportiert werden kann.</div>
|
||||
<div class="icon">
|
||||
<svg viewBox="0 0 160 30" width="180" height="40">
|
||||
<path d="M10,15 L150,15" stroke="#ea580c" stroke-width="3"/>
|
||||
<rect x="2" y="8" width="14" height="14" rx="3" fill="#ea580c" opacity="0.4" stroke="#ea580c"/>
|
||||
<rect x="144" y="8" width="14" height="14" rx="3" fill="#ea580c" opacity="0.4" stroke="#ea580c"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="options">
|
||||
<strong>Charge-only</strong> · billig, nur Strom · 5 W<br>
|
||||
<strong>USB 2.0</strong> · 480 Mbit/s · 60 W (3 A)<br>
|
||||
<strong>USB 3.2 Gen 2</strong> · 10 Gbit/s · 100 W<br>
|
||||
<strong>Thunderbolt 4</strong> · 40 Gbit/s · 240 W
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="axis protokoll">
|
||||
<div class="axis-head">
|
||||
<span class="axis-name">Protokoll</span>
|
||||
<span class="axis-num">3 · WAS LÄUFT</span>
|
||||
</div>
|
||||
<div class="axis-tagline">Die <strong>Sprache</strong>. Was die zwei Geräte miteinander reden.</div>
|
||||
<div class="icon">
|
||||
<svg viewBox="0 0 160 30" width="180" height="40">
|
||||
<circle cx="20" cy="15" r="6" fill="#7c3aed"/>
|
||||
<path d="M30,15 L130,15" stroke="#7c3aed" stroke-width="2" stroke-dasharray="6,4"/>
|
||||
<circle cx="140" cy="15" r="6" fill="#7c3aed"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="options">
|
||||
<strong>USB-PD</strong> · Power Delivery (Laden)<br>
|
||||
<strong>USB 2/3/4</strong> · Datenübertragung<br>
|
||||
<strong>Thunderbolt 3/4</strong> · Daten + Display<br>
|
||||
<strong>DisplayPort Alt</strong> · Monitor anschließen<br>
|
||||
<strong>HDMI Alt</strong> · weiter HDMI rausführen
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="scenario">
|
||||
<div class="scenario-head">Konkret: warum lädt der Laptop nicht?</div>
|
||||
<div class="scenario-body">
|
||||
USB-C-Port am Laptop ist da (<strong>Stecker</strong> stimmt). Aber der Aldi-Kabel ist <code>Charge-only</code> mit <code>5 W</code> (<strong>Kabel</strong> kann nicht genug Strom liefern). Oder der Port am Laptop unterstützt nur <code>USB-PD</code> mit 100 W, das Netzteil meldet aber nur 65 W (<strong>Protokoll</strong>-Handshake scheitert). <em>Eine der drei Achsen passt nicht.</em>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="verdict">
|
||||
<div class="verdict-q">Drei Achsen. Drei unabhängige Fragen. Drei Gründe, warum's nicht funktioniert.</div>
|
||||
<div class="verdict-a">Beim nächsten „USB-C-Kabel funktioniert nicht": <strong>Stecker passt → Kabel-Spec checken → Protokoll-Support prüfen.</strong> In dieser Reihenfolge.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 251 KiB |
@@ -97,7 +97,7 @@ Hochschule der Medien Stuttgart
|
||||
|
||||
<!-- _class: lead -->
|
||||
|
||||
# Herzlich willkommen!
|
||||
# Herzlich willkommen
|
||||
## Einführung
|
||||
|
||||
---
|
||||
@@ -116,13 +116,13 @@ Hochschule der Medien Stuttgart
|
||||
|
||||
---
|
||||
|
||||
# Warum dieses Modul?
|
||||
# Motivation für dieses Modul
|
||||
|
||||
**Das Internet ist überall** – aber was steckt dahinter?
|
||||
|
||||
* Warum sehen Webseiten auf dem Handy anders aus?
|
||||
* Wie funktioniert eigentlich das Internet?
|
||||
* Wie erkennen Sie, ob eine Werbeagentur gute Arbeit leistet?
|
||||
* Wie erkennt ihr, ob eine Werbeagentur gute Arbeit leistet?
|
||||
* Was hat ein Unterseekabel mit meiner E-Mail zu tun?
|
||||
* **→ Die Technik dahinter verstehen und fundierte Entscheidungen treffen**
|
||||
|
||||
@@ -136,13 +136,13 @@ Als Digital- und MedienwirtschaftlerInnen werdet ihr täglich mit technischen Fr
|
||||
* Wie baue ich eine barrierearme Webseite?
|
||||
* Welches Protokoll für welchen Zweck?
|
||||
* Wie funktioniert das Web hinter den Kulissen?
|
||||
* **→ Mitreden können – und selbst Webseiten bauen!**
|
||||
* **→ Mitreden können – und selbst Webseiten bauen**
|
||||
|
||||
---
|
||||
|
||||
# Was Sie hier lernen werden
|
||||
# Lernziele
|
||||
|
||||
**In dieser Veranstaltung erwerben Sie folgende Kompetenzen:**
|
||||
**In dieser Veranstaltung erwerbt ihr folgende Kompetenzen:**
|
||||
|
||||
* Geschichte des Computers und Internets
|
||||
* HTML-Grundlagen und semantisches Markup
|
||||
@@ -172,7 +172,7 @@ Als Digital- und MedienwirtschaftlerInnen werdet ihr täglich mit technischen Fr
|
||||
2. Netzwerke, Protokolle, **semantisches HTML** & **CSS**
|
||||
3. Interaktivität, Animationen & **JavaScript**
|
||||
|
||||
**Ziel:** "Gelernte Hilflosigkeit" ablegen
|
||||
**Ziel:** Technische Scheu abbauen und Web-Grundlagen selbst beherrschen
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -72,18 +72,6 @@ section.aufgabe footer {
|
||||
}
|
||||
section.erklaerung {
|
||||
font-size: 1.1rem;
|
||||
/**background: repeating-linear-gradient(
|
||||
135deg,
|
||||
#fce4ec,
|
||||
#fce4ec 40px,
|
||||
#fff 40px,
|
||||
#fff 80px
|
||||
) !important;**/
|
||||
}
|
||||
@media print {
|
||||
section.erklaerung {
|
||||
background: #fce4ec !important;
|
||||
}
|
||||
}
|
||||
section.erklaerung h1 {
|
||||
font-size: 1.5rem;
|
||||
@@ -138,7 +126,7 @@ Hochschule der Medien Stuttgart
|
||||
2. Netzwerke, Protokolle, **semantisches HTML** & **CSS**
|
||||
3. Interaktivität, Animationen & **JavaScript**
|
||||
|
||||
**Ziel:** "Gelernte Hilflosigkeit" ablegen
|
||||
**Ziel:** Technische Scheu abbauen und Web-Grundlagen selbst beherrschen
|
||||
|
||||
---
|
||||
|
||||
@@ -415,10 +403,10 @@ Der Turing-Test (1950): Ein Interrogator kommuniziert per Text mit einem Mensche
|
||||
- Implosions-Simulationen
|
||||
- Nuklearphysik-Gleichungen
|
||||
|
||||
**Lösung 1943:** Menschliche "Computer"
|
||||
**Lösung 1943:** Die Berechnungen übernehmen menschliche "Computer"
|
||||
→ Mathematikerinnen mit Taschenrechnern & IBM-Lochkarten
|
||||
|
||||
**Zu langsam** → Bedarf an automatischer Berechnung
|
||||
**Das ist zu langsam** → es entsteht der Bedarf an automatischer Berechnung
|
||||
|
||||
**Schlüsselfigur:** John von Neumann (Mathematik & Stoßwellen)
|
||||
|
||||
@@ -439,7 +427,7 @@ Das ENIAC-Projekt in Philadelphia sollte die Berechnungen beschleunigen.
|
||||
# John von Neumann (1903–1957)
|
||||
|
||||
- Geboren in Budapest, Österreich-Ungarn
|
||||
- Mit 8: Konnte Telefonbücher auswendig
|
||||
- Mit 8: Konnte ganze Telefonbücher auswendig aufsagen
|
||||
- Mit 22: Doktor in Mathematik
|
||||
|
||||
**Beiträge:**
|
||||
@@ -635,14 +623,14 @@ COBOL läuft heute noch auf Bankensystemen
|
||||
- Jeder Befehl entspricht direkt einer CPU-Instruktion
|
||||
- Hardwarenah, aber für Menschen lesbar
|
||||
|
||||
**Fun Fact:** Chris Sawyer schrieb **RollerCoaster Tycoon** (1999) fast komplett in x86-Assembly
|
||||
**Kuriosum:** Chris Sawyer schrieb **RollerCoaster Tycoon** (1999) fast komplett in x86-Assembly
|
||||
|
||||
<!--
|
||||
KATHLEEN BOOTH (1922–2022):
|
||||
Kathleen Hylda Valerie Booth (geb. Britten), BSc Mathematik (Royal Holloway, London), PhD Applied Mathematics (King's College London). Arbeitete ab 1946 mit Andrew Booth (später ihr Ehemann) am Birkbeck College – eines der kleinsten Computing-Teams Großbritanniens: nur Andrew, Kathleen und Xenia Sweeting.
|
||||
|
||||
WAS SIE TAT:
|
||||
Kathleen baute die Hardware des ARC (Automatic Relay Calculator) mit auf und entwickelte dann die Programmierarchitektur. 1947 reiste sie mit Andrew nach Princeton, wo sie John von Neumann trafen und die Stored-Program-Architektur kennenlernten. Sie schrieb "Coding for A.R.C." – die erste veröffentlichte Beschreibung einer Assembly-Sprache. Statt roher Binärzahlen (z.B. 10011) konnten Programmierer nun mnemonische Kürzel verwenden (z.B. M -> cR). Ein konzeptioneller Sprung – die erste Abstraktionsschicht zwischen Mensch und Maschine.
|
||||
Kathleen baute die Hardware des ARC (Automatic Relay Calculator) mit auf und entwickelte dann die Programmierarchitektur. 1947 reiste sie mit Andrew nach Princeton, wo sie John von Neumann trafen und die Stored-Program-Architektur kennenlernten. Sie schrieb "Coding for A.R.C." – die erste veröffentlichte Beschreibung einer Assembly-Sprache. Statt roher Binärzahlen (z.B. 10011) konnten Programmierende nun mnemonische Kürzel verwenden (z.B. M -> cR). Ein konzeptioneller Sprung – die erste Abstraktionsschicht zwischen Mensch und Maschine.
|
||||
|
||||
VOR ASSEMBLER:
|
||||
ENIAC (1943–46) wurde durch physisches Umstecken von Kabeln programmiert – Programmwechsel dauerten Tage. Danach kamen Stored-Program-Computer, aber Programme mussten als rohe Binärzahlen eingegeben werden. Jeder Tippfehler in einer 0/1-Folge konnte den Befehl komplett verfälschen.
|
||||
@@ -726,7 +714,7 @@ Cambridge Analytica (2018): Facebook-Daten von 87 Mio. Nutzern wurden für polit
|
||||
|
||||
China Social Credit System: Punktesystem für "gutes Verhalten". Wer zu oft bei Rot geht, bekommt keinen Kredit mehr.
|
||||
|
||||
Die Frage: Wer entscheidet, was mit Technologie gemacht wird? Die Entwickler? Die Firmen? Die Politik? Wir alle?
|
||||
Die Frage: Wer entscheidet, was mit Technologie gemacht wird? Die Entwickelnden? Die Firmen? Die Politik? Wir alle?
|
||||
-->
|
||||
|
||||
---
|
||||
@@ -905,7 +893,7 @@ Geprägt 1948 von Claude Shannon
|
||||
|
||||
<!--
|
||||
Alles Digitale basiert darauf
|
||||
Transistoren in CPUs: Milliarden Bits schalten Millionen Mal pro Sekunde
|
||||
Transistoren in CPUs: Milliarden Bit schalten Millionen Mal pro Sekunde
|
||||
Shannon: Begründer der Informationstheorie
|
||||
-->
|
||||
|
||||
@@ -913,31 +901,58 @@ Shannon: Begründer der Informationstheorie
|
||||
|
||||
# Das Byte
|
||||
|
||||
**8 Bit = 1 Byte**
|
||||
### Die kleinste *adressierbare* Informationseinheit
|
||||
|
||||
### 8 bit = 1 Byte
|
||||
|
||||
```
|
||||
0 0 1 0 1 0 1 0
|
||||
0 0 1 0 1 0 1 0 = 42
|
||||
```
|
||||
|
||||
**Wie viele Kombinationen?**
|
||||
2⁸ = 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 = **256 Möglichkeiten** (0-255)
|
||||
|
||||
<!--
|
||||
BYTE = Wortspiel aus "Bit" + "Bite" (Bissen)
|
||||
Warum 8 Bits? IBM System/360 (1964) setzte Standard
|
||||
2×2×2×2×2×2×2×2 = 256
|
||||
BYTE = Wortspiel aus "Bit" + "Bite" (Bissen) — Begriff geprägt von Werner Buchholz (IBM, 1956)
|
||||
Schreibweise mit "y" statt "i": bewusste Änderung zur Vermeidung von Verwechslung mit "Bit"
|
||||
binary 00101010 = decimal 42
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
# Was kann man mit 256 Zuständen machen?
|
||||
# Das Byte
|
||||
|
||||
### 8 bit = 1 Byte
|
||||
|
||||
```
|
||||
0 0 0 0 0 0 0 0 = 0
|
||||
0 0 0 0 0 0 0 1 = 1
|
||||
0 0 0 0 0 0 1 0 = 2
|
||||
0 0 0 0 0 0 1 1 = 3
|
||||
0 0 0 0 0 1 0 0 = 4
|
||||
|
||||
0 1 0 0 0 0 0 0 = 64
|
||||
0 1 1 1 1 1 1 1 = 127
|
||||
1 1 1 1 1 1 1 1 = ?
|
||||
```
|
||||
|
||||
2 × 2 × 2 × 2 × 2 × 2 × 2 × 2 = 2⁸ = **256 mögliche Zustände**
|
||||
|
||||
<!--
|
||||
- 1964: IBM System/360 setzte den 8-Bit-Byte-Standard — vorher 6-Bit/7-Bit-Systeme
|
||||
- ASCII (1963) brauchte 7 Bit für 128 Zeichen
|
||||
- 8 Bit = praktisch für Hardware (Zweierpotenz: 2³ = 8)
|
||||
- 8 Bit = 2 Hexadezimalziffern (elegante Darstellung)
|
||||
- Kleinste adressierbare Einheit im Speicher: Prozessor kann nicht einzelne Bit direkt ansprechen, immer Byte
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
# 256 Zustände – was sich damit darstellen lässt
|
||||
|
||||
- **256 Zeichen** (Buchstaben, Zahlen, Symbole)
|
||||
- **256 Graustufen** (0 = Schwarz, 255 = Weiß)
|
||||
- **256 Lautstärkestufen**
|
||||
- **Zahlen 0-255**
|
||||
|
||||
**Für Farbe:** 3 Bytes pro Pixel (RGB)
|
||||
**Für Farbe:** 3 Byte pro Pixel (RGB)
|
||||
R=0-255, G=0-255, B=0-255 → **16,7 Millionen Farben**
|
||||
|
||||
<!--
|
||||
@@ -957,6 +972,22 @@ Farbbild: Rot + Grün + Blau
|
||||
Kernidee: jedes Byte lässt sich sauber in zwei 4-Bit-Hälften (Nibbles) zerlegen. Jede Hälfte hat 2⁴ = 16 Zustände – und genau 16 Symbole hat Hex (0-F). Deshalb passt Hex perfekt: 1 Nibble = 1 Hex-Ziffer, 1 Byte = 2 Hex-Ziffern. Keine krumme Umrechnung.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
<!--
|
||||
Warum gerade 8 Bit?
|
||||
- CPU adressiert byteweise — kleinste adressierbare Einheit
|
||||
- Halbe Byte (z.B. 0x0000.5) existieren nicht
|
||||
- Speichercontroller, Bus, CPU-Register sind alle auf 8-Bit-Häppchen ausgelegt
|
||||
- Einzelne Bit lesen: erst Byte holen, dann mit Bitmaske isolieren (byte & 0b1000_0000)
|
||||
- Hardware-Geschichte: IBM System/360 (1964) setzte 8-Bit-Standard, 7-Bit-ASCII + 1 Paritätsbit
|
||||
-->
|
||||
|
||||
<!--
|
||||
# Zahlensysteme
|
||||
|
||||
@@ -973,7 +1004,7 @@ Kernidee: jedes Byte lässt sich sauber in zwei 4-Bit-Hälften (Nibbles) zerlege
|
||||
<!--
|
||||
Dezimal = was wir im Alltag nutzen (10 Finger → 10 Ziffern)
|
||||
Binär = was Computer nutzen (Strom an/aus → 2 Zustände)
|
||||
Hexadezimal = Kompromiss: kompakter als Binär, aber passt perfekt auf Bytes (2 Hex-Ziffern = 1 Byte = 8 Bit)
|
||||
Hexadezimal = Kompromiss: kompakter als Binär, aber passt perfekt auf Byte (2 Hex-Ziffern = 1 Byte = 8 Bit)
|
||||
Die Basis bestimmt, wie viele Symbole pro Stelle zur Verfügung stehen:
|
||||
- Dezimal: 10 (0–9)
|
||||
- Binär: 2 (0, 1)
|
||||
@@ -990,7 +1021,7 @@ Die Basis bestimmt, wie viele Symbole pro Stelle zur Verfügung stehen:
|
||||
**Hexadezimal (Base 16):**
|
||||
`4D 50 33` (= "MP3" in ASCII)
|
||||
|
||||
**Jede Hex-Ziffer = 4 Bits (ein "Nibble")**
|
||||
**Jede Hex-Ziffer = 4 Bit (ein "Nibble")**
|
||||
0-9, A-F (A=10, B=11, ..., F=15)
|
||||
|
||||
`4 = 0100` `D = 1101`
|
||||
@@ -1000,7 +1031,7 @@ Die Basis bestimmt, wie viele Symbole pro Stelle zur Verfügung stehen:
|
||||
<!--
|
||||
Hex = Shortcut für Menschen
|
||||
1 Byte = 2 Hex-Ziffern (00-FF)
|
||||
"Nibble" = 4 Bits = halbes Byte (Wortspiel: nibble = knabbern, byte = beißen)
|
||||
"Nibble" = 4 Bit = halbes Byte (Wortspiel: nibble = knabbern, byte = beißen)
|
||||
CSS (Cascading Style Sheets)-Farben nutzen Hex: #FF0000 = Rot
|
||||
-->
|
||||
|
||||
@@ -1013,7 +1044,39 @@ CSS (Cascading Style Sheets)-Farben nutzen Hex: #FF0000 = Rot
|
||||
|
||||
---
|
||||
|
||||
# Wo begegnet Ihnen Hex-Code?
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
<!--
|
||||
1 Byte = 8 Bit = 2 Hex-Ziffern = 1 ASCII-Zeichen
|
||||
- Dieselbe Datei, drei Schreibweisen
|
||||
- Jeder Rahmen = ein Byte
|
||||
- Byte ändern sich nicht, nur unsere Anzeige
|
||||
- ↵ (0x0A) = nicht druckbar → Hex-Editoren zeigen . als Platzhalter
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
<!--
|
||||
Dieselben 8 Byte (PNG-Dateianfang: 89 50 4E 47 0D 0A 1A 0A) — drei Perspektiven:
|
||||
1. Bitstream — was wirklich gespeichert wird (unleserlich)
|
||||
2. Hex — gruppiert in 8-Bit-Häppchen (kompakt)
|
||||
3. Bedeutung — was die Byte signalisieren:
|
||||
- 89: Magic Byte (>127 → "ich bin Binärdatei")
|
||||
- 50 4E 47: P N G (ASCII-Format-Kürzel)
|
||||
- 0D 0A 1A 0A: CR LF EOF LF (erkennt kaputte Übertragung)
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
# Wo euch Hex-Code begegnet
|
||||
|
||||
| Kontext | Beispiel |
|
||||
|---------|----------|
|
||||
@@ -1049,7 +1112,7 @@ Fehlercodes: Windows zeigt diese bei Bluescreens
|
||||
|
||||
---
|
||||
|
||||
# Bit vs. Byte: Die Verwirrung
|
||||
# Bit vs. Byte: der Unterschied
|
||||
|
||||
**1 Byte = 8 Bit** → Bit ÷ 8 = Byte
|
||||
|
||||
@@ -1063,7 +1126,7 @@ Fehlercodes: Windows zeigt diese bei Bluescreens
|
||||
|
||||
|
||||
<!--
|
||||
Bits = kleines b, Bytes = großes B
|
||||
Bit = kleines b, Byte = großes B
|
||||
Internetanbieter: Mbit/s (klingt größer!)
|
||||
100 Mbit/s ÷ 8 = 12,5 MB/s
|
||||
Warum? Marketing - 100 klingt besser als 12,5
|
||||
@@ -1083,7 +1146,7 @@ Warum? Marketing - 100 klingt besser als 12,5
|
||||
**H**yper**T**ext **M**arkup **L**anguage
|
||||
|
||||
* **Keine** Programmiersprache
|
||||
* Am ehesten eine Formattierungssprache
|
||||
* Am ehesten eine Auszeichnungssprache (Markup)
|
||||
* Beschreibt **Struktur und Aufbau** einer Webseite
|
||||
* ```html
|
||||
<p>Das ist ein Absatz.</p>
|
||||
@@ -1104,8 +1167,8 @@ Aktuelle Version: HTML5 (seit 2014)
|
||||
# Was ist HTML? (2/2)
|
||||
|
||||
* Mit HTML werden **HTML-Dokumente** beschrieben (bspw. `index.html`)
|
||||
* Ein HTML-Dokument beschreibt das/den `Document Object Model (DOM)`
|
||||
* Der DOM ist eine **hierarchische Baumstruktur** (Kind- und Elternelemente)
|
||||
* Ein HTML-Dokument beschreibt das **Document Object Model (DOM)**
|
||||
* Das DOM ist eine **hierarchische Baumstruktur** (Kind- und Elternelemente)
|
||||
* *parent nodes / elements*
|
||||
* *child nodes / elements*
|
||||
* *sibling nodes / elements*
|
||||
@@ -1115,12 +1178,12 @@ Aktuelle Version: HTML5 (seit 2014)
|
||||
|
||||
# HTML-Anatomie (1/2)
|
||||
|
||||
Formattierungssprache
|
||||
HTML ist eine Auszeichnungssprache (Markup).
|
||||
|
||||
* HTML folgt einer strengen **Syntax** zur Formattieren von digitalen Inhalten
|
||||
* HTML folgt einer strengen **Syntax** zum Auszeichnen digitaler Inhalte
|
||||
* Syntax: `<element attribute="value"> ... </element>`
|
||||
* Öffnende und schließende HTML-Tags müssen identisch sein
|
||||
* Das schließende Tag muss ein `/` nach der Spitzenklammer haben
|
||||
* Das schließende Tag hat ein `/` direkt nach der öffnenden spitzen Klammer (`</p>`)
|
||||
|
||||
---
|
||||
|
||||
@@ -1136,7 +1199,7 @@ Formattierungssprache
|
||||
* `<b> ... </b>` fett-gedruckter Text (inline)
|
||||
* `<a> ... </a>` Hyperlink (Verlinkung zu anderen Webseiten, Bildern)
|
||||
* `<body> ... </body>` Sichtbarer Teil eines HTML-Dokuments
|
||||
* `<details> ... </details>` Collapsable / Akkordion / Aufklapp-Text
|
||||
* `<details> ... </details>` Aufklapp-Text (Akkordion)
|
||||
* **Selbst-schließend**
|
||||
* `<img />` Bild, Vektorgrafik etc.
|
||||
* `<input />` Eingabefeld
|
||||
@@ -1322,7 +1385,7 @@ a = anchor (Anker)
|
||||
|
||||
# Natives HTML
|
||||
|
||||
Was noch üblicherweise mit Skriptsprachen wie JavaScript und anderen umgesetzt werden musste, kann nun HTML selber. Wer dieses dennoch verwendet, hat das einen sog. *"Code smell"*.
|
||||
Vieles, was früher JavaScript erforderte, beherrscht HTML inzwischen selbst. Wer es dennoch per Skript löst, produziert einen sogenannten *"Code Smell"*.
|
||||
|
||||
| Funktion | HTML-Element |
|
||||
|----------|--------------|
|
||||
@@ -1447,7 +1510,7 @@ Accessibility ist eingebaut
|
||||
|
||||
<!-- _class: lead -->
|
||||
|
||||
# Wie nutzen Menschen das Web?
|
||||
# Wie Menschen das Web nutzen
|
||||
|
||||
---
|
||||
|
||||
@@ -1491,7 +1554,7 @@ Accessibility ist eingebaut
|
||||
|
||||
---
|
||||
|
||||
# Wie nutzen Menschen das Web?
|
||||
# Wie Menschen das Web nutzen
|
||||
|
||||
| Eingabe | Nutzungsweise |
|
||||
|---------|---------------|
|
||||
@@ -1523,10 +1586,9 @@ PRÜFUNGSRELEVANT: Arten von Einschränkungen, Screenreader-Beispiele, WCAG
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: "erklaerung klausur" -->
|
||||
<!-- _class: erklaerung -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
<!-- _backgroundColor: #fce4ec -->
|
||||
|
||||
# Web-Zugänglichkeit – Vertiefung
|
||||
|
||||
@@ -1551,10 +1613,10 @@ Screenreader lesen den DOM sequenziell – semantisches HTML (`<nav>`, `<main>`,
|
||||
|
||||
---
|
||||
|
||||
# Warum "Barrierefreiheit"?
|
||||
# Bedeutung von "Barrierefreiheit"
|
||||
|
||||
**Rechtlich:**
|
||||
- EU: European Accessibility Act (seit Juni 2025 in Kraft!)
|
||||
- EU: European Accessibility Act (seit Juni 2025 in Kraft)
|
||||
- DE: BITV 2.0 (Behörden), Privatwirtschaft betroffen
|
||||
|
||||
**Ethisch:**
|
||||
@@ -1686,7 +1748,7 @@ button:focus-visible {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Anti-Pattern: Tastatur-Nutzer sind "blind" */
|
||||
/* Anti-Pattern: Tastatur-Nutzende sind "blind" */
|
||||
button:focus { outline: none; }
|
||||
```
|
||||
|
||||
@@ -1826,7 +1888,7 @@ Fokus-Indikator immer sichtbar? Logische Reihenfolge? Alle Elemente erreichbar?
|
||||
|
||||
<!--
|
||||
WAVE Tab-Reihenfolge: visualisiert DOM-Reihenfolge als Tab-Pfad.
|
||||
Wenn Pfad kreuz und quer → schlechte Struktur für Tastatur-/Screenreader-Nutzer.
|
||||
Wenn Pfad kreuz und quer → schlechte Struktur für Tastatur-/Screenreader-Nutzende.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
@@ -98,7 +98,7 @@ Hochschule der Medien Stuttgart
|
||||
|
||||
<!-- _class: lead -->
|
||||
|
||||
# Kapitel 3 – 24.01.2026
|
||||
# Kapitel 3
|
||||
## Interaktivität & JavaScript
|
||||
|
||||
---
|
||||
@@ -119,16 +119,15 @@ Hochschule der Medien Stuttgart
|
||||
|
||||
---
|
||||
|
||||
# Was ist JavaScript?
|
||||
# JavaScript im Überblick
|
||||
|
||||
**Die einzige Programmiersprache, die im Browser läuft!**
|
||||
JavaScript ist die einzige Programmiersprache, die nativ im Browser läuft.
|
||||
|
||||
- **1995:** In 10 Tagen entwickelt (Brendan Eich, Netscape)
|
||||
- **Heute:** Überall – Browser, Server, Apps, IoT
|
||||
|
||||
**Nicht verwechseln:**
|
||||
- Java ≠ JavaScript (komplett unterschiedlich!)
|
||||
- "JavaScript ist zu Java wie Carpet zu Car"
|
||||
- Java und JavaScript sind zwei völlig verschiedene Sprachen.
|
||||
|
||||
<!--
|
||||
Ursprünglich "Mocha", dann "LiveScript", dann "JavaScript" (Marketing!)
|
||||
@@ -159,7 +158,7 @@ ES6 = großes Update mit vielen Features
|
||||
|
||||
# JavaScript einbinden
|
||||
|
||||
**Option 1: Externe Datei (empfohlen!)**
|
||||
**Option 1: Externe Datei (empfohlen)**
|
||||
```html
|
||||
<body>
|
||||
<!-- Inhalt -->
|
||||
@@ -174,7 +173,7 @@ ES6 = großes Update mit vielen Features
|
||||
</script>
|
||||
```
|
||||
|
||||
⚠️ Script am **Ende** des `<body>` – damit HTML zuerst lädt!
|
||||
**Wichtig:** Das Script gehört ans **Ende** des `<body>`, damit das HTML zuerst lädt.
|
||||
|
||||
<!--
|
||||
Externe Datei = wiederverwendbar, cachebar
|
||||
@@ -184,7 +183,7 @@ Oder: defer-Attribut
|
||||
|
||||
---
|
||||
|
||||
# Die Konsole: Euer bester Freund
|
||||
# Die JavaScript-Konsole
|
||||
|
||||
**Browser DevTools → Console**
|
||||
- Windows/Linux: `F12` oder `Strg+Shift+I`
|
||||
@@ -196,7 +195,7 @@ console.log(42);
|
||||
console.log("Die Antwort ist:", 42);
|
||||
```
|
||||
|
||||
**Direkt in der Konsole tippen und Enter!**
|
||||
Befehle lassen sich direkt in der Konsole eingeben und mit Enter ausführen.
|
||||
|
||||
<!--
|
||||
Console = REPL (Read-Eval-Print-Loop)
|
||||
@@ -348,20 +347,20 @@ true || false // true (ODER)
|
||||
|
||||
**In der Browser-Konsole:**
|
||||
|
||||
1. Erstellen Sie Variablen für sich:
|
||||
1. Erstellt Variablen für euch:
|
||||
```javascript
|
||||
const name = "Your Name";
|
||||
let age = 20;
|
||||
const hobbies = ["Hobby1", "Hobby2"];
|
||||
```
|
||||
|
||||
2. Gib sie aus:
|
||||
2. Gebt sie aus:
|
||||
```javascript
|
||||
console.log(`I am ${name}, ${age} years old.`);
|
||||
console.log("My hobbies:", hobbies);
|
||||
```
|
||||
|
||||
3. Erstelle ein `person`-Objekt und greife auf Properties zu
|
||||
3. Erstellt ein `person`-Objekt und greift auf Properties zu
|
||||
|
||||
---
|
||||
|
||||
@@ -491,7 +490,7 @@ Funktionale Programmierung, Original-Array bleibt unverändert
|
||||
|
||||
---
|
||||
|
||||
# Was ist das DOM?
|
||||
# Das DOM im Überblick
|
||||
|
||||
**D**ocument **O**bject **M**odel
|
||||
|
||||
@@ -512,7 +511,7 @@ Jedes Element ist ein Objekt
|
||||
# Elemente finden
|
||||
|
||||
```javascript
|
||||
// Einzelnes Element (erstes gefundene)
|
||||
// Einzelnes Element (erstes gefundenes)
|
||||
document.getElementById("meinId");
|
||||
document.querySelector(".meineKlasse");
|
||||
document.querySelector("nav > a");
|
||||
@@ -632,10 +631,10 @@ remove = aus DOM entfernen
|
||||
|
||||
**Aufgaben (in script.js):**
|
||||
|
||||
1. Ändere den Text von `#output`
|
||||
2. Füge 5 `<li>`-Elemente zur Liste hinzu (mit Schleife!)
|
||||
3. Gib jedem geraden Element eine Klasse `highlight`
|
||||
4. Style `.highlight` in CSS (z.B. andere Farbe)
|
||||
1. Ändert den Text von `#output`
|
||||
2. Fügt 5 `<li>`-Elemente zur Liste hinzu (mit Schleife)
|
||||
3. Gebt jedem geraden Element eine Klasse `highlight`
|
||||
4. Gestaltet `.highlight` in CSS (z. B. andere Farbe)
|
||||
|
||||
---
|
||||
|
||||
@@ -646,7 +645,7 @@ remove = aus DOM entfernen
|
||||
|
||||
---
|
||||
|
||||
# Was sind Events?
|
||||
# Events im Überblick
|
||||
|
||||
**NutzerInnen interagieren mit der Seite:**
|
||||
- Klicken
|
||||
@@ -654,7 +653,7 @@ remove = aus DOM entfernen
|
||||
- Scrollen
|
||||
- Formulare absenden
|
||||
|
||||
**JavaScript kann darauf reagieren!**
|
||||
JavaScript kann auf diese Ereignisse reagieren.
|
||||
|
||||
<!--
|
||||
Event = Ereignis
|
||||
@@ -784,9 +783,9 @@ toggleButton.addEventListener("click", () => {
|
||||
|
||||
// Button-Text ändern
|
||||
if (body.classList.contains("dark-mode")) {
|
||||
toggleButton.textContent = "☀️ Light Mode";
|
||||
toggleButton.textContent = "Light Mode";
|
||||
} else {
|
||||
toggleButton.textContent = "🌙 Dark Mode";
|
||||
toggleButton.textContent = "Dark Mode";
|
||||
}
|
||||
});
|
||||
```
|
||||
@@ -1002,21 +1001,21 @@ Frameworks für größere Apps, Komponenten-basiert
|
||||
|
||||
---
|
||||
|
||||
# Ihr könnt jetzt...
|
||||
# Das könnt ihr jetzt
|
||||
|
||||
- Den historischen Kontext von IT verstehen
|
||||
- Erklären wie das Internet funktioniert
|
||||
- HTML-Seiten strukturieren
|
||||
- CSS für Layout und Design nutzen
|
||||
- JavaScript für Interaktivität einsetzen
|
||||
- **Mit EntwicklerInnen auf Augenhöhe sprechen!**
|
||||
- **Mit EntwicklerInnen auf Augenhöhe sprechen**
|
||||
|
||||
---
|
||||
|
||||
# Feedback – Eure Meinung zählt!
|
||||
# Feedback
|
||||
|
||||
Euer ehrliches Feedback hilft mir, den Kurs zu verbessern.
|
||||
**Es gibt keine falschen Antworten!**
|
||||
Es gibt keine falschen Antworten.
|
||||
|
||||
- Was hat gut funktioniert?
|
||||
- Was könnte besser sein?
|
||||
@@ -1028,7 +1027,7 @@ Euer ehrliches Feedback hilft mir, den Kurs zu verbessern.
|
||||
|
||||
<!-- _class: lead -->
|
||||
|
||||
# Danke!
|
||||
# Danke
|
||||
|
||||
**Kontakt:** lb-czechowski@hdm-stuttgart.de
|
||||
|
||||
|
||||
@@ -6,47 +6,70 @@
|
||||
<style>
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, sans-serif; }
|
||||
body { padding: 30px 40px; display: inline-block; }
|
||||
.wrap { display: flex; gap: 36px; align-items: flex-start; }
|
||||
.col { display: flex; flex-direction: column; gap: 4px; }
|
||||
h3 { margin: 0 0 10px; font-size: 1.1rem; color: #1a1a2e; text-align: center; }
|
||||
.headers {
|
||||
display: grid;
|
||||
grid-template-columns: 240px 240px;
|
||||
column-gap: 36px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.headers h3 { margin: 0; font-size: 1.1rem; color: #1a1a2e; text-align: center; }
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 240px 240px;
|
||||
grid-template-rows: repeat(7, 48px);
|
||||
column-gap: 36px;
|
||||
row-gap: 6px;
|
||||
}
|
||||
.box {
|
||||
border: 2px solid #4a4a6a;
|
||||
padding: 10px 20px;
|
||||
min-width: 230px;
|
||||
font-size: 0.95rem;
|
||||
background: #f3f4f6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
font-size: 0.95rem;
|
||||
text-align: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.osi .box { background: #dbeafe; border-color: #1e40af; color: #1e3a8a; }
|
||||
.tcpip .box { background: #fce7d6; border-color: #c2410c; color: #7c2d12; font-weight: 600; }
|
||||
.tcpip .app { min-height: 126px; }
|
||||
.tcpip .trans { min-height: 40px; }
|
||||
.tcpip .net { min-height: 40px; }
|
||||
.tcpip .link { min-height: 82px; }
|
||||
.osi {
|
||||
background: #dbeafe;
|
||||
border-color: #1e40af;
|
||||
color: #1e3a8a;
|
||||
}
|
||||
.tcpip {
|
||||
background: #fce7d6;
|
||||
border-color: #c2410c;
|
||||
color: #7c2d12;
|
||||
font-weight: 600;
|
||||
}
|
||||
/* OSI: column 1, one box per row */
|
||||
.o7 { grid-area: 1 / 1; } .o6 { grid-area: 2 / 1; } .o5 { grid-area: 3 / 1; }
|
||||
.o4 { grid-area: 4 / 1; } .o3 { grid-area: 5 / 1; } .o2 { grid-area: 6 / 1; }
|
||||
.o1 { grid-area: 7 / 1; }
|
||||
/* TCP/IP: column 2, each block spans its OSI equivalents */
|
||||
.t-app { grid-area: 1 / 2 / 4 / 2; } /* OSI 5-7 */
|
||||
.t-tra { grid-area: 4 / 2 / 5 / 2; } /* OSI 4 */
|
||||
.t-net { grid-area: 5 / 2 / 6 / 2; } /* OSI 3 */
|
||||
.t-link { grid-area: 6 / 2 / 8 / 2; } /* OSI 1-2 */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="col osi">
|
||||
<h3>OSI (7 Schichten)</h3>
|
||||
<div class="box">7. Anwendung</div>
|
||||
<div class="box">6. Darstellung</div>
|
||||
<div class="box">5. Sitzung</div>
|
||||
<div class="box">4. Transport</div>
|
||||
<div class="box">3. Vermittlung</div>
|
||||
<div class="box">2. Sicherung</div>
|
||||
<div class="box">1. Bitübertragung</div>
|
||||
</div>
|
||||
<div class="col tcpip">
|
||||
<h3>TCP/IP (4 Schichten)</h3>
|
||||
<div class="box app">Anwendung</div>
|
||||
<div class="box trans">Transport</div>
|
||||
<div class="box net">Internet</div>
|
||||
<div class="box link">Netzzugang</div>
|
||||
</div>
|
||||
<div class="headers">
|
||||
<h3>OSI (7 Schichten)</h3>
|
||||
<h3>TCP/IP (4 Schichten)</h3>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="box osi o7">7. Anwendung</div>
|
||||
<div class="box osi o6">6. Darstellung</div>
|
||||
<div class="box osi o5">5. Sitzung</div>
|
||||
<div class="box osi o4">4. Transport</div>
|
||||
<div class="box osi o3">3. Vermittlung</div>
|
||||
<div class="box osi o2">2. Sicherung</div>
|
||||
<div class="box osi o1">1. Bitübertragung</div>
|
||||
|
||||
<div class="box tcpip t-app">Anwendung</div>
|
||||
<div class="box tcpip t-tra">Transport</div>
|
||||
<div class="box tcpip t-net">Internet</div>
|
||||
<div class="box tcpip t-link">Netzzugang</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 63 KiB |
@@ -5,18 +5,17 @@
|
||||
<title>Port-Highlight</title>
|
||||
<style>
|
||||
html, body { margin: 0; padding: 0; background: #fff; font-family: system-ui, sans-serif; }
|
||||
body { padding: 28px 40px; display: inline-block; }
|
||||
body { padding: 28px 40px 110px; display: inline-block; }
|
||||
.line {
|
||||
font-family: ui-monospace, "SF Mono", monospace;
|
||||
font-size: 2.2rem;
|
||||
color: #1a1a2e;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: baseline;
|
||||
}
|
||||
.port-wrap {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.port {
|
||||
@@ -26,11 +25,20 @@
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.below {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.connector {
|
||||
width: 2px;
|
||||
height: 18px;
|
||||
background: #c2410c;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.caption-text {
|
||||
background: #fed7aa;
|
||||
@@ -42,16 +50,13 @@
|
||||
font-size: 1rem;
|
||||
font-family: system-ui, sans-serif;
|
||||
margin-top: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="line">
|
||||
212.132.79.37:<div class="port-wrap">
|
||||
<span class="port">443</span>
|
||||
<div class="connector"></div>
|
||||
<div class="caption-text">Port 443 = HTTPS</div>
|
||||
</div>
|
||||
212.132.79.37:<span class="port-wrap"><span class="port">443</span><span class="below"><span class="connector"></span><span class="caption-text">Port 443 = HTTPS</span></span></span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@@ -104,7 +104,7 @@
|
||||
<!-- SYN-ACK -->
|
||||
<div class="msg arrow-left msg-reverse" style="left: 83px; right: 80px; top: 180px;">
|
||||
<div class="label reply" style="left: 50%; transform: translateX(-50%);">SYN-ACK · Seq=5000, Ack=1001</div>
|
||||
<div class="quote" style="left: 50%; transform: translateX(-50%);">"OK, ich starte bei 5000"</div>
|
||||
<div class="quote" style="left: 50%; transform: translateX(-50%);">"OK, ich starte bei 5000 und erwarte 1001"</div>
|
||||
</div>
|
||||
|
||||
<!-- ACK -->
|
||||
@@ -113,7 +113,7 @@
|
||||
<div class="quote" style="left: 50%; transform: translateX(-50%);">"Verstanden, los geht's"</div>
|
||||
</div>
|
||||
|
||||
<div class="final">✓ Verbindung steht</div>
|
||||
<div class="final">Verbindung steht</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
@@ -5,7 +5,7 @@ paginate: true
|
||||
backgroundColor: #fff
|
||||
header: "Grundlagen IT- und Internettechnik (223015c)"
|
||||
footer: "Michael Czechowski – HdM Stuttgart – SoSe 2026"
|
||||
title: "Kapitel 1: Geschichte, Grundlagen & HTML"
|
||||
title: Grundlagen IT- und Internettechnik
|
||||
---
|
||||
<style>
|
||||
:root {
|
||||
@@ -20,13 +20,13 @@ section {
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
h1 {
|
||||
color: #a02060; /* darker raspberry */
|
||||
color: #a02060;
|
||||
}
|
||||
section.invert h1 {
|
||||
color: #fff;
|
||||
}
|
||||
h2 {
|
||||
color: #1f2937; /* dark gray, almost black */
|
||||
color: #1f2937;
|
||||
}
|
||||
pre {
|
||||
background: #0f0f23;
|
||||
@@ -82,10 +82,9 @@ section.aufgabe footer {
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
<!-- _backgroundColor: black -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||

|
||||

|
||||
|
||||
# Grundlagen IT- und Internettechnik
|
||||
|
||||
@@ -103,39 +102,6 @@ Hochschule der Medien Stuttgart
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# Die 5 Komponenten
|
||||
|
||||
| Komponente | Funktion |
|
||||
|------------|----------|
|
||||
| **Rechenwerk (ALU)** | Führt Berechnungen durch |
|
||||
| **Steuerwerk** | Interpretiert Befehle, steuert Ablauf |
|
||||
| **Speicherwerk** | Speichert Programme UND Daten |
|
||||
| **Ein-/Ausgabe** | Tastatur, Bildschirm, Netzwerk |
|
||||
| **Bus-System** | Verbindet alle Komponenten |
|
||||
|
||||
**Wichtig:** Programme und Daten **gemeinsam** im Speicher!
|
||||
|
||||
<!--
|
||||
VON-NEUMANN-ARCHITEKTUR (1945): Grundlage aller modernen Computer
|
||||
5 KOMPONENTEN:
|
||||
- ALU (Arithmetic Logic Unit): Rechnet (+, -, ×, ÷) und vergleicht (>, <, =)
|
||||
- Steuerwerk: Holt Befehle, dekodiert sie, steuert Ausführung (Fetch-Decode-Execute)
|
||||
- Speicherwerk: RAM (flüchtig) + ROM (permanent), enthält Code UND Daten
|
||||
- Ein-/Ausgabe (I/O): Tastatur, Maus, Bildschirm, Netzwerk, USB, Sensoren
|
||||
- Bus-System: Adressbus (wo), Datenbus (was), Steuerbus (wie)
|
||||
KERNPRINZIP: Stored Program Concept - Programme im selben Speicher wie Daten
|
||||
VORHER (z.B. ENIAC): Programme durch Umstecken von Kabeln, tagelange Arbeit
|
||||
NACHHER: Programme als austauschbare Daten → Flexibilität, Software-Industrie möglich
|
||||
PRÜFUNGSRELEVANT: 5 Komponenten benennen und erklären können, Stored Program Concept
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: "" -->
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# Von-Neumann-Architektur: Bedeutung
|
||||
|
||||
**Ohne Von-Neumann-Architektur:**
|
||||
@@ -173,117 +139,18 @@ PRÜFUNGSRELEVANT: Warum Von-Neumann revolutionär, Unterschied zu Harvard, Beis
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# HTML Metadaten
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Im Browsertab als Überschrift sichtbar</title>
|
||||
|
||||
<meta name="description" content="" />
|
||||
|
||||
<meta name="og:image" content="https://...." />
|
||||
</head>
|
||||
|
||||
...
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
- <small>*Webseiten werden nicht nur von Menschen besucht, sondern auch von Suchmaschinen, Programmen, Bots etc.*</small>
|
||||
- <small>*Metadaten helfen bspw. auch Screen-Readern beim "Verstehen" der Inhalte*</small>
|
||||
|
||||
<!--
|
||||
HEAD-BEREICH: Metadaten, nicht sichtbar im Browser-Fenster
|
||||
WICHTIGE META-TAGS:
|
||||
- <title>: Browser-Tab, Lesezeichen, Suchergebnis-Titel
|
||||
- <meta name="description">: Suchergebnis-Snippet (max. 160 Zeichen)
|
||||
- <meta name="viewport">: Mobile Darstellung (width=device-width)
|
||||
- <meta charset="UTF-8">: Zeichenkodierung (Umlaute!)
|
||||
OPEN GRAPH (og:*): Social Media Vorschau (Facebook, LinkedIn, WhatsApp)
|
||||
- og:title, og:description, og:image, og:url
|
||||
TWITTER CARDS: twitter:card, twitter:title, twitter:image
|
||||
SEO-RELEVANZ: Google nutzt title + description für Ranking und Snippet
|
||||
ACCESSIBILITY: <html lang="de"> für Screenreader-Aussprache
|
||||
PRÜFUNGSRELEVANT: Was gehört in <head>, Unterschied zu <body>, wichtigste Meta-Tags
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: "" -->
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# Wie nutzen Menschen das Web?
|
||||
|
||||
| Eingabe | Nutzungsweise |
|
||||
|---------|---------------|
|
||||
| **Maus** | Klicken, Scrollen |
|
||||
| **Tastatur** | Tab-Navigation, Enter, Pfeiltasten |
|
||||
| **Screenreader** | Vorlesen von Inhalten (NVDA, VoiceOver, JAWS) |
|
||||
| **Sprachsteuerung** | "Klicke auf Anmelden" |
|
||||
| **Augensteuerung** | Eye-Tracking |
|
||||
| **Switch-Geräte** | Ein-/Aus-Schalter |
|
||||
|
||||
→ **Nicht alle nutzen Maus + Bildschirm!**
|
||||
|
||||
<!--
|
||||
ACCESSIBILITY (a11y): a + 11 Buchstaben + y
|
||||
STATISTIK: ~15% der Weltbevölkerung haben eine Behinderung (WHO)
|
||||
ARTEN VON EINSCHRÄNKUNGEN:
|
||||
- Permanent: Blindheit, Taubheit, motorische Einschränkungen
|
||||
- Temporär: Gebrochener Arm, Augen-OP, Ohrenentzündung
|
||||
- Situativ: Helle Sonne (Kontrast), laute Umgebung (kein Audio), Baby auf Arm (eine Hand)
|
||||
ASSISTIVE TECHNOLOGIEN:
|
||||
- Screenreader: NVDA (Windows, kostenlos), VoiceOver (Apple), JAWS (kommerziell)
|
||||
- Braillezeilen: Taktile Ausgabe für Blinde
|
||||
- Switch-Geräte: Für motorische Einschränkungen
|
||||
- Spracheingabe: Dragon NaturallySpeaking, Siri, Google Assistant
|
||||
WCAG: Web Content Accessibility Guidelines (A, AA, AAA)
|
||||
EUROPEAN ACCESSIBILITY ACT: Seit Juni 2025 verpflichtend!
|
||||
PRÜFUNGSRELEVANT: Arten von Einschränkungen, Screenreader-Beispiele, WCAG
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: "" -->
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# Warum "Barrierefreiheit"?
|
||||
|
||||
**Rechtlich:**
|
||||
- EU: European Accessibility Act (seit Juni 2025 in Kraft!)
|
||||
- DE: BITV 2.0 (Behörden), Privatwirtschaft betroffen
|
||||
|
||||
**Ethisch:**
|
||||
- Teilhabe für alle Menschen
|
||||
- Digitale Inklusion
|
||||
|
||||
**Praktisch:**
|
||||
- Bessere UX für **alle** (SEO, Mobile, ältere Menschen)
|
||||
- Größerer Markt (~15% der Bevölkerung)
|
||||
|
||||
<!--
|
||||
RECHTLICHER RAHMEN:
|
||||
- European Accessibility Act (EAA): EU-Richtlinie, seit 28. Juni 2025 in Kraft
|
||||
- Betrifft: E-Commerce, Banking, Telekommunikation, Transport, E-Books
|
||||
- BITV 2.0: Barrierefreie Informationstechnik-Verordnung (DE, öffentliche Stellen)
|
||||
- BFSG: Barrierefreiheitsstärkungsgesetz (DE-Umsetzung des EAA)
|
||||
- Strafen: Bis zu 100.000€ Bußgeld möglich
|
||||
CURB-CUT-EFFEKT:
|
||||
- Bordsteinabsenkung für Rollstuhlfahrer → hilft auch Kinderwagen, Rollkoffern, Fahrrädern
|
||||
- Untertitel für Gehörlose → helfen in lauter Umgebung, beim Sprachlernen
|
||||
- Kontrastreiche Farben → besser bei Sonnenlicht, für ältere Menschen
|
||||
BUSINESS CASE:
|
||||
- 15% Zielgruppe (Menschen mit Behinderung)
|
||||
- +20% ältere Menschen (Sehschwäche, motorische Einschränkungen)
|
||||
- SEO-Vorteile: Alt-Texte, semantisches HTML = besser für Google
|
||||
PRÜFUNGSRELEVANT: EAA kennen, Curb-Cut-Effekt erklären können, Business Case
|
||||
-->
|
||||
# HTML-Anatomie (2/2)
|
||||
|
||||
- Wir unterscheiden zwischen **umschließende** und **selbst-schließende** HTML-Tags
|
||||
- **Umschließend**
|
||||
- `<b> ... </b>` fett-gedruckter Text (inline)
|
||||
- `<a> ... </a>` Hyperlink (Verlinkung zu anderen Webseiten, Bildern)
|
||||
- `<body> ... </body>` Sichtbarer Teil eines HTML-Dokuments
|
||||
- `<details> ... </details>` Aufklapp-Text (Akkordion)
|
||||
- **Selbst-schließend**
|
||||
- `<img />` Bild, Vektorgrafik etc.
|
||||
- `<input />` Eingabefeld
|
||||
- `<meta />` Metadaten
|
||||
|
||||
---
|
||||
|
||||
@@ -294,17 +161,16 @@ PRÜFUNGSRELEVANT: EAA kennen, Curb-Cut-Effekt erklären können, Business Case
|
||||
# Barrieren im Netz vermeiden (a11y)
|
||||
|
||||
**Tastatur-Test:**
|
||||
- Alle Funktionen nur mit Tab + Enter nutzbar?
|
||||
- Fokus immer sichtbar?
|
||||
- Logische Tab-Reihenfolge?
|
||||
- Alle Funktionen sind nur mit Tab + Enter nutzbar
|
||||
- Fokus immer sichtbar: Textlinks, Buttons, Texteingabe etc.
|
||||
- Logische Tab-Reihenfolge: "Skip to main", Navigation, Hauptinhalt
|
||||
|
||||
**Screenreader-Test:**
|
||||
- VoiceOver (Mac): `Cmd + F5`
|
||||
- NVDA (Windows): Gratis-Download
|
||||
|
||||
**Tools:**
|
||||
- axe DevTools, WAVE (Browser-Extensions)
|
||||
- Lighthouse (in Chrome DevTools)
|
||||
**Tools/Browser-Extensions:**
|
||||
- [WAVE](https://wave.webaim.org/), [axe DevTools](https://www.deque.com/axe/devtools/), [Web Disability Simulator](https://chromewebstore.google.com/detail/web-disability-simulator/olioanlbgbpmdlgjnnampnnlohigkjla)
|
||||
|
||||
<!--
|
||||
Automatische Tests finden ~30% der Probleme
|
||||
@@ -319,168 +185,19 @@ Echte NutzerInnen einbeziehen = Gold-Standard
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# Das TCP/IP-Modell
|
||||
|
||||
| Schicht | Name | Aufgabe | Protokolle |
|
||||
|---------|------|---------|------------|
|
||||
| **4** | Anwendung | Was? Welcher Dienst? | HTTP, DNS, SMTP |
|
||||
| **3** | Transport | Zuverlässigkeit, Ports | TCP, UDP |
|
||||
| **2** | Internet | Routing, globale Adressen | IP |
|
||||
| **1** | Netzzugang | Lokale Übertragung | Ethernet, WLAN |
|
||||
|
||||
**4 Schichten. Das reicht, um das Internet zu verstehen.**
|
||||
|
||||
<!--
|
||||
SPEAKER NOTES:
|
||||
- TCP/IP = das echte Internet
|
||||
- 4 Schichten:
|
||||
- Anwendung: Was will ich? (HTTP, DNS, Mail)
|
||||
- Transport: Kommt an? Welches Programm? (TCP, UDP)
|
||||
- Internet: Wie zum Zielrechner? (IP)
|
||||
- Netzzugang: Wie zum nächsten Gerät? (Ethernet, WLAN)
|
||||
- KLAUSUR: Welches Protokoll → welche Schicht
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: "" -->
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# Dateneinheiten pro Schicht
|
||||
|
||||
| Schicht | Name | Dateneinheit | Was kommt hinzu? |
|
||||
|---------|------|--------------|------------------|
|
||||
| Anwendung | HTTP, DNS | **Daten** | – |
|
||||
| Transport | TCP, UDP | **Segment** | Ports, Sequenznummern |
|
||||
| Internet | IP | **Paket** | IP-Adressen |
|
||||
| Netzzugang | Ethernet | **Frame** | MAC-Adressen, Prüfsumme |
|
||||
|
||||
Merkhilfe: **D**aten → **S**egment → **P**aket → **F**rame
|
||||
(„**D**er **S**ache **P**raktischer **F**olgen")
|
||||
|
||||
<!--
|
||||
SPEAKER NOTES:
|
||||
- Glossar-Begriffe jetzt eingeordnet:
|
||||
- Anwendung: Daten/Message
|
||||
- Transport: Segment
|
||||
- Internet: Paket
|
||||
- Netzzugang: Frame
|
||||
- Merkhilfe: D-S-P-F (eigene erfinden!)
|
||||
- KLAUSUR: Begriff → Schicht
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: "" -->
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# IP vs. MAC vs. Port
|
||||
|
||||
| | IP-Adresse | MAC-Adresse | Port |
|
||||
|---|-----------|-------------|------|
|
||||
| **Frage** | Welcher Rechner? | Welches Gerät nebenan? | Welches Programm? |
|
||||
| **Reichweite** | Global | Lokal (1 Hop) | Auf einem Rechner |
|
||||
| **Ändert sich?** | Nein | Ja, bei jedem Hop | Nein |
|
||||
| **Schicht** | Internet (IP) | Netzzugang (Ethernet) | Transport (TCP/UDP) |
|
||||
| **Beispiel** | 212.132.79.37 | aa:bb:cc:dd:ee | 443 |
|
||||
|
||||
<!--
|
||||
SPEAKER NOTES:
|
||||
- KLAUSUR: Diese Tabelle
|
||||
- IP: Welcher Rechner? Global. Bleibt gleich.
|
||||
- MAC: Welcher Nachbar? Lokal. Ändert sich.
|
||||
- Port: Welches Programm? Bleibt gleich.
|
||||
- Merken: NUR MAC ändert sich unterwegs
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: "" -->
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# Der 3-Way-Handshake
|
||||
|
||||
```
|
||||
Client (dein Laptop) Server
|
||||
| |
|
||||
|-------- SYN (Seq=1000) --------------->|
|
||||
| "Ich will reden" |
|
||||
| |
|
||||
|<--- SYN-ACK (Seq=5000, Ack=1001) ------|
|
||||
| "OK, ich auch. Ich starte bei 5000"|
|
||||
| |
|
||||
|-------- ACK (Ack=5001) --------------->|
|
||||
| "Verstanden, los geht's" |
|
||||
| |
|
||||
| [ Verbindung steht ] |
|
||||
```
|
||||
|
||||
**SYN** = Synchronize · **ACK** = Acknowledge
|
||||
|
||||
<!--
|
||||
SPEAKER NOTES:
|
||||
- 3 Pakete → Verbindung steht
|
||||
- 1. Client SYN: "Will Verbindung, starte bei 1000"
|
||||
- 2. Server SYN-ACK: "OK, ich starte bei 5000, erwarte 1001"
|
||||
- 3. Client ACK: "Verstanden, erwarte 5001"
|
||||
- Seq-Nr wichtig: Beide wissen nächstes erwartetes Byte
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: "" -->
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# TCP vs. UDP
|
||||
|
||||
| | TCP | UDP |
|
||||
|---|-----|-----|
|
||||
| **Verbindung** | Ja (Handshake) | Nein |
|
||||
| **Reihenfolge** | Garantiert | Nicht garantiert |
|
||||
| **Verlorene Pakete** | Werden nachgefordert | Gehen verloren |
|
||||
| **Overhead** | Höher | Niedriger |
|
||||
| **Use Cases** | Web, Email, Downloads | Video-Calls, Gaming, DNS |
|
||||
|
||||
<!--
|
||||
SPEAKER NOTES:
|
||||
- Zwei Transport-Protokolle
|
||||
- TCP: zuverlässig
|
||||
- Handshake, Seq-Nr, Bestätigungen, Neuübertragung
|
||||
- Perfekt für Web, Downloads (nichts darf fehlen)
|
||||
- UDP: schneller
|
||||
- Kein Handshake, keine Bestätigungen
|
||||
- Verloren = weg
|
||||
- Perfekt für Video-Calls (Verzögerung schlimmer als Verlust)
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: "" -->
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# HTTP-Methoden
|
||||
|
||||
| Methode | Bedeutung | Beispiel |
|
||||
|---------|-----------|----------|
|
||||
| **GET** | Daten abrufen | Seite laden |
|
||||
| **POST** | Daten senden | Formular absenden |
|
||||
| **PUT** | Daten ersetzen | Profil aktualisieren |
|
||||
| **DELETE** | Daten löschen | Account löschen |
|
||||
HTTP-Methoden beschreiben, **was** der Client mit einer Ressource tun will.
|
||||
|
||||
| Methode | Bedeutung | Beispiel |
|
||||
|---------|-----------|-----------------------------------------------|
|
||||
| **GET** | Daten abrufen | Seite laden `GET /index.html HTTP/2.0` |
|
||||
| **POST** | Daten senden | Formular absenden `POST /login HTTP/2.0` |
|
||||
| **PUT** | Daten ersetzen | Profil aktualisieren `PUT /users/42 HTTP/2.0` |
|
||||
| **DELETE** | Daten löschen | Account löschen `DELETE /users/42 HTTP/2.0` |
|
||||
|
||||
<small>CRUD = Create, Read, Update, Delete</small>
|
||||
|
||||
```http
|
||||
GET /index.html HTTP/1.1
|
||||
POST /login HTTP/1.1
|
||||
```
|
||||
|
||||
<!--
|
||||
SPEAKER NOTES:
|
||||
@@ -505,10 +222,12 @@ Das wird wichtiger, wenn ihr mit REST-APIs arbeitet.
|
||||
|
||||
# HTTP Status-Codes
|
||||
|
||||
Jede HTTP-Response enthält einen dreistelligen Status-Code — die erste Ziffer verrät die Kategorie.
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
HTTP/1.1 404 Not Found
|
||||
HTTP/1.1 500 Internal Server Error
|
||||
HTTP/2.0 200 OK
|
||||
HTTP/2.0 404 Not Found
|
||||
HTTP/2.0 500 Internal Server Error
|
||||
```
|
||||
|
||||
| Code-Bereich | Bedeutung |
|
||||
@@ -539,112 +258,76 @@ Status-Codes sagen euch, was passiert ist.
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# Zusammenfassung Teil 1
|
||||
# Selektoren: ID
|
||||
|
||||
**Der Ablauf:**
|
||||
1. DNS (UDP:53) → Name zu IP
|
||||
2. TCP-Handshake (SYN → SYN-ACK → ACK)
|
||||
3. HTTP-Request (GET /...)
|
||||
4. HTTP-Response (200 OK + HTML)
|
||||

|
||||
|
||||
**Die Konzepte:**
|
||||
- 4 Schichten: Anwendung → Transport → Internet → Netzzugang
|
||||
- IP bleibt gleich, MAC ändert sich pro Hop
|
||||
- Ports identifizieren Programme
|
||||
- Encapsulation: Jede Schicht verpackt die darüberliegende
|
||||
|
||||
<!--
|
||||
SPEAKER NOTES:
|
||||
|
||||
Das ist die Zusammenfassung für Teil 1. Das solltet ihr können:
|
||||
|
||||
Den Ablauf: DNS, TCP-Handshake, HTTP-Request, HTTP-Response.
|
||||
|
||||
Die Schichten und ihre Protokolle.
|
||||
|
||||
Den Unterschied zwischen IP und MAC – was bleibt gleich, was ändert sich.
|
||||
|
||||
Was Ports tun.
|
||||
|
||||
Wie Encapsulation funktioniert.
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: "" -->
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# Spezifität: Welche Regel gewinnt?
|
||||
|
||||
| Selektor | Spezifität |
|
||||
|----------|------------|
|
||||
| Element (`p`) | 0,0,0,1 |
|
||||
| Klasse (`.wichtig`) | 0,0,1,0 |
|
||||
| ID (`#header`) | 0,1,0,0 |
|
||||
| Inline (`style="..."`) | 1,0,0,0 |
|
||||
|
||||
```css
|
||||
p { color: blue; } /* 0,0,0,1 */
|
||||
.text { color: green; } /* 0,0,1,0 → gewinnt */
|
||||
#intro { color: red; } /* 0,1,0,0 → gewinnt über beide */
|
||||
```html
|
||||
<nav id="hauptnavigation">...</nav>
|
||||
```
|
||||
|
||||
<!--
|
||||
SPEAKER NOTES:
|
||||
|
||||
Wenn mehrere Regeln auf ein Element zutreffen, gewinnt die spezifischere.
|
||||
|
||||
Spezifität wird in vier Zahlen gemessen. Von links nach rechts: Inline, IDs, Klassen, Elemente.
|
||||
|
||||
Ein Element-Selektor hat 0,0,0,1. Eine Klasse hat 0,0,1,0. Eine ID hat 0,1,0,0.
|
||||
|
||||
0,0,1,0 schlägt 0,0,0,99 – auch hundert Element-Selektoren schlagen keine Klasse.
|
||||
|
||||
Inline-Styles schlagen alles – außer !important, was ihr vermeiden solltet.
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: "" -->
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# Responsive Design
|
||||
|
||||
```css
|
||||
/* Mobile First: Basis-Styles */
|
||||
.container {
|
||||
#hauptnavigation {
|
||||
background: #333;
|
||||
padding: 1rem;
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Ab 768px (Tablet): Anpassungen */
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
padding: 2rem;
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ab 1024px (Desktop): Weitere Anpassungen */
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
background: green;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Raute** vor dem Namen = ID
|
||||
|
||||
**Achtung:** IDs sollten **einmalig** pro Seite sein.
|
||||
|
||||
<!--
|
||||
SPEAKER NOTES:
|
||||
|
||||
Responsive Design: Die Seite passt sich der Bildschirmgröße an.
|
||||
ID-Selektoren sind noch spezifischer. Raute vor dem Namen.
|
||||
|
||||
Mobile First: Ihr schreibt zuerst die Styles für kleine Screens. Dann fügt ihr mit @media-Queries Anpassungen für größere Screens hinzu.
|
||||
IDs sollten auf einer Seite nur einmal vorkommen. Für Navigation, Header, Footer, Main – einmalige Elemente.
|
||||
|
||||
min-width heißt: "ab dieser Breite". Also: Basis gilt für alle. Ab 768px gelten zusätzliche Regeln. Ab 1024px noch mehr.
|
||||
|
||||
Das Gegenteil wäre Desktop First mit max-width – aber Mobile First ist heute Standard.
|
||||
In der Praxis: Klassen sind meist besser. IDs haben hohe Spezifität und sind unflexibel.
|
||||
-->
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: "" -->
|
||||
<!-- _footer: "" -->
|
||||
|
||||
|
||||
# Farben
|
||||
|
||||

|
||||
|
||||
CSS kennt fünf Farb-Notationen: **Keyword**, **Hex**, **RGB**, **RGBA** und **HSL**.
|
||||
|
||||
```css
|
||||
/* Keyword */
|
||||
color: red;
|
||||
color: rebeccapurple;
|
||||
/* Hex */
|
||||
color: #FF0000;
|
||||
color: #F00;
|
||||
/* RGB = Red Green Blue, RGBA mit Alpha */
|
||||
color: rgb(255, 0, 0);
|
||||
color: rgba(255, 0, 0, 0.5);
|
||||
/* HSL = Hue, Saturation, Lightness */
|
||||
color: hsl(0, 100%, 50%);
|
||||
```
|
||||
|
||||
MDN-Referenz: [CSS `<color>`](https://developer.mozilla.org/de/docs/Web/CSS/Reference/Values/color_value)
|
||||
|
||||
<!--
|
||||
SPEAKER NOTES:
|
||||
|
||||
Farben können auf verschiedene Arten angegeben werden.
|
||||
|
||||
Keywords: red, blue, rebeccapurple. Begrenzte Auswahl, aber lesbar.
|
||||
|
||||
Hex: Sechs Zeichen, je zwei für Rot, Grün, Blau. #FF0000 ist reines Rot.
|
||||
|
||||
RGB: Gleiche Idee, aber dezimal. rgb(255, 0, 0).
|
||||
|
||||
RGBA: Mit Alpha-Kanal für Transparenz. 0 ist unsichtbar, 1 ist voll sichtbar.
|
||||
|
||||
HSL: Hue, Saturation, Lightness. Manchmal intuitiver für Farbanpassungen.
|
||||
-->
|
||||
@@ -0,0 +1,666 @@
|
||||
---
|
||||
marp: true
|
||||
theme: gaia
|
||||
paginate: true
|
||||
backgroundColor: #fff
|
||||
header: "Web Engineering – DHBW Stuttgart"
|
||||
footer: "Michael Czechowski – SoSe 2026"
|
||||
title: Intro
|
||||
---
|
||||
|
||||
<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 -->
|
||||
|
||||

|
||||
|
||||
# Web Engineering
|
||||
|
||||
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
|
||||
|
||||
**Sommersemester 2026**
|
||||
|
||||
[https://librete.ch/dhbw/](https://librete.ch/dhbw/)
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Ressourcen zum Selbstlernen
|
||||
|
||||
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
|
||||
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
|
||||
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](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
|
||||
|
||||

|
||||
|
||||
**Michael Werner Czechowski**
|
||||
|
||||
- Systems and Platform Engineer
|
||||
- Schwerpunkte:
|
||||
- Web-Technologien, Barriere-Armut, Open Source
|
||||
- Hintergrund:
|
||||
- Philosophie (Uni Stuttgart)
|
||||
- Wirtschaftsinformatik (Leibniz-FH Hannover)
|
||||
- Honorardozent (DHBW Stuttgart, HdM Stuttgart, Leibniz-FH)
|
||||
- Kontakt: michael.czechowski@lehre.dhbw-stuttgart.de
|
||||
|
||||
---
|
||||
|
||||
# Vorstellungsrunde
|
||||
|
||||
## Wer bist du, was machst du?
|
||||
|
||||
> Ich bin \_\_\_, mache bei \_\_\_ \_\_\_ und ein Fun Fact über mich ist, dass ich ...
|
||||
|
||||
- ... sämtliche One Piece Bände gelesen habe.
|
||||
- ... alle XKCD Comics mit Titel kenne.
|
||||
- ... irgendwann bei \_\_\_ arbeiten möchte.
|
||||
- ... Paulaner Spezi im Blindtest herausschmecken kann.
|
||||
|
||||
---
|
||||
|
||||
# Vorlesungsplan
|
||||
|
||||
<style scoped>
|
||||
.lp-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.lp-card {
|
||||
border: 1px solid #d63384;
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
background: #fff;
|
||||
font-size: 0.95em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.lp-card .head {
|
||||
font-weight: bold;
|
||||
color: #a02060;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.lp-card .head .num {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.lp-card .topic {
|
||||
color: #1a1a2e;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.lp-card.milestone {
|
||||
background: #fce4ec;
|
||||
border-color: #a02060;
|
||||
border-width: 2px;
|
||||
}
|
||||
.lp-card.milestone .topic {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="lp-grid">
|
||||
<div class="lp-card"><div class="head"><span class="num">1</span><span>08.05.</span></div><div class="topic">Intro · Projektgruppen · Internet 101</div></div>
|
||||
<div class="lp-card"><div class="head"><span class="num">2</span><span>15.05.</span></div><div class="topic">HTML und CSS (Frameworks)</div></div>
|
||||
<div class="lp-card"><div class="head"><span class="num">3</span><span>22.05.</span></div><div class="topic">JS (Frameworks) und npm</div></div>
|
||||
<div class="lp-card"><div class="head"><span class="num">4</span><span>29.05.</span></div><div class="topic">nodeJS: Scripting, Running, Building</div></div>
|
||||
<div class="lp-card"><div class="head"><span class="num">5</span><span>05.06.</span></div><div class="topic">Express API, CRUD, Middlewares</div></div>
|
||||
<div class="lp-card milestone"><div class="head"><span class="num">6</span><span>12.06.</span></div><div class="topic">Projektwerkstattbericht</div></div>
|
||||
<div class="lp-card"><div class="head"><span class="num">7</span><span>19.06.</span></div><div class="topic">Testing (Unit · Integration · E2E)</div></div>
|
||||
<div class="lp-card"><div class="head"><span class="num">8</span><span>26.06.</span></div><div class="topic">TypeScript</div></div>
|
||||
<div class="lp-card"><div class="head"><span class="num">9</span><span>03.07.</span></div><div class="topic">Docker · Proxies · DBs</div></div>
|
||||
<div class="lp-card"><div class="head"><span class="num">10</span><span>10.07.</span></div><div class="topic">Wrap-up & Projektsupport</div></div>
|
||||
<div class="lp-card milestone"><div class="head"><span class="num">11</span><span>17.07.</span></div><div class="topic">Präsentation</div></div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
# Erwartungen und Wünsche
|
||||
|
||||
- Was haben wir bisher gelernt?
|
||||
- Was kennen wir bereits aus dem Arbeitskontext?
|
||||
- Welche Technologien nutzen wir bereits?
|
||||
- Was möchte ich hier lernen: Frontend, Backend, DevOps?
|
||||
- Eigene FOSS entwickeln?
|
||||
- CLI-Skills verbessern?
|
||||
- Cloud, Cloud, Cloud?
|
||||
|
||||
---
|
||||
|
||||
# Prüfungsleistung – Übersicht
|
||||
|
||||
Eigenes Projekt: **Web-Applikation** XOR **Backend** (API/CLI).
|
||||
|
||||
| Punkte | Bereich |
|
||||
|--------|---------|
|
||||
| 30 | Grundanforderungen (Projektgrundlagen + Präsentation) |
|
||||
| 25 | Clean Code Prinzipien |
|
||||
| 25 | Accessibility & UX |
|
||||
| 20 | Technische Umsetzung |
|
||||
| **100** | **Basispunkte** |
|
||||
| +15 | Bonuspunkte (max. 3 Features a 5 P.) |
|
||||
|
||||
---
|
||||
|
||||
# Prüfungsleistung – Grundanforderungen (30 P.)
|
||||
|
||||
**Projektgrundlagen (15 P.)**
|
||||
- Funktionsfähige Anwendung (8 P.)
|
||||
- Repository mit Versionskontrolle (4 P.)
|
||||
- Projektdokumentation / README (3 P.)
|
||||
|
||||
**Präsentation (15 P.)**
|
||||
- Elevator Pitch (5 P.)
|
||||
- Technische Präsentation (5 P.)
|
||||
- Projektevolution: wie hat sich das Projekt vs. Ursprungsidee verändert? (5 P.)
|
||||
|
||||
---
|
||||
|
||||
# Prüfungsleistung – Clean Code (25 P.)
|
||||
|
||||
**Projektstruktur (10 P.)**
|
||||
- Ordnerstruktur (5 P.)
|
||||
- Datei-Organisation (5 P.)
|
||||
|
||||
**Code-Qualität (10 P.)**
|
||||
- KISS – Keep It Simple, Stupid (3 P.)
|
||||
- Beschreibende Namen (3 P.)
|
||||
- Funktionsverantwortlichkeiten (4 P.)
|
||||
|
||||
**Kommentierung & Dokumentation (5 P.)**
|
||||
|
||||
---
|
||||
|
||||
# Prüfungsleistung – Accessibility & UX (25 P.)
|
||||
|
||||
**Web Accessibility (15 P.)**
|
||||
- Semantisches HTML (5 P.)
|
||||
- ARIA Labels & Alt-Texte (5 P.)
|
||||
- Tastaturnavigation (5 P.)
|
||||
|
||||
**Responsive Design (10 P.)**
|
||||
- Mobile First Approach (5 P.)
|
||||
- Flexbox/Grid Layout (5 P.)
|
||||
|
||||
---
|
||||
|
||||
# Prüfungsleistung – Technische Umsetzung (20 P.)
|
||||
|
||||
- Plattformunabhängigkeit (5 P.)
|
||||
- Package Management (5 P.)
|
||||
- Build Tools & Optimierung (5 P.)
|
||||
- Live-Demo (5 P.)
|
||||
|
||||
---
|
||||
|
||||
# Prüfungsleistung – Bonuspunkte (max. 15 P.)
|
||||
|
||||
Mindestens 2 Bereiche wählen, je 5 Punkte:
|
||||
|
||||
- API Integration
|
||||
- Local Storage / State Management
|
||||
- Progressive Web App Features
|
||||
- Testing
|
||||
- Erweiterte Responsive Features (3+ Breakpoints)
|
||||
- Security Features
|
||||
|
||||
---
|
||||
|
||||
# Prüfungsleistung – Abgabe & Termine
|
||||
|
||||
- **Code-Upload:** bis 27.07.
|
||||
- **Präsentation:** 17.07. (Gruppen, ~10 Min.)
|
||||
|
||||
Details: https://git.librete.ch/DHBW/pruefungsleistung
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Was ist (k)eine Web Applikation?
|
||||
## Exkurs
|
||||
|
||||
---
|
||||
|
||||
# Was ist (k)eine Web Applikation?
|
||||
|
||||
**Keine Web Applikation ist:**
|
||||
* Betriebssystem (GNU/Linux, Windows, macOS, iOS, Android)
|
||||
* Desktop-Applikation
|
||||
* Mobile-Applikation
|
||||
* Embedded System (Microcontroller, IoT)
|
||||
|
||||
**Eine Web Applikation ist:**
|
||||
* plattformunabhängig (läuft im Browser)
|
||||
* nutzt in der Regel HTTP-Requests
|
||||
* keine Installation nötig — serverseitig aktualisiert
|
||||
* gebaut in Web-Technologien (HTML, CSS, JS/TS)
|
||||
* skalierbar durch verteilte Architektur
|
||||
|
||||
---
|
||||
|
||||
# Internet-Timeline
|
||||
|
||||
| Jahr | Meilenstein |
|
||||
|------|-------------|
|
||||
| **1966** | ARPANET-Projekt startet (DARPA) |
|
||||
| **1969** | Erste Verbindung zwischen UCLA und Stanford |
|
||||
| **1974** | TCP/IP-Protokoll entwickelt |
|
||||
| **1989** | Tim Berners-Lee erfindet das WWW (CERN) |
|
||||
| **1993** | Mosaic — erster grafischer Browser |
|
||||
| **1994** | W3C gegründet (HTML, CSS, SVG, WCAG) |
|
||||
| **1995** | ECMAScript / JavaScript wird Standard |
|
||||
| **2008** | V8 JavaScript Engine (Chrome / Node.js) |
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Das WWW (1989)
|
||||
|
||||
**Tim Berners-Lee** am **CERN** (Genf)
|
||||
|
||||
**Problem:** Wissenschaftler:innen können Forschungsdokumente nicht verlinken — Inkompatible Systeme, kein gemeinsames Format.
|
||||
|
||||
**Lösung — drei Bausteine:**
|
||||
|
||||
- **HTML** — Auszeichnungssprache für Dokumente
|
||||
- **HTTP** — Protokoll zur Übertragung
|
||||
- **URL** — eindeutige Adressen
|
||||
|
||||
**1991:** erste Webseite geht online · 1993 vom CERN gemeinfrei freigegeben.
|
||||
|
||||
→ WWW ≠ Internet. Das WWW ist eine **Anwendung auf** dem Internet.
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Heute: Physische Infrastruktur
|
||||
|
||||
- **5,5 Milliarden** Menschen online
|
||||
- **> 400 Unterseekabel**, ca. **1,3 Mio. km** Glasfaser
|
||||
- Daten reisen mit **Lichtgeschwindigkeit**
|
||||
- Latenz Frankfurt — New York: ~80 ms
|
||||
|
||||
→ Keine "Cloud" ohne Kabel. Das Internet ist **physische** Infrastruktur — und damit angreifbar.
|
||||
|
||||
**Geopolitik:** Five-Eyes-Allianz (USA, UK, CA, AU, NZ) zapft Unterseekabel ab (Programm "Tempora", aufgedeckt 2013 durch Snowden). DE ist Teil der Fourteen Eyes.
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
|
||||
---
|
||||
|
||||
# HTTP – Hypertext Transfer Protocol
|
||||
|
||||
**Request:**
|
||||
```
|
||||
GET /products HTTP/1.1
|
||||
Host: localhost:8080
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[{"id": 1, "name": "Produkt A"}, ...]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Onboarding
|
||||
## Tools, Runtimes, Workflows
|
||||
|
||||
---
|
||||
|
||||
# Werkzeuge & Betriebssystem
|
||||
|
||||
**Erforderliche Werkzeuge:**
|
||||
- **Terminal** (zsh / bash / fish / pwsh) — Kommandozeile
|
||||
- **Editor** (siehe folgende Folie)
|
||||
- **JavaScript-Runtime** (Node.js / Bun / Deno)
|
||||
- **Browser mit DevTools** (Chromium / Firefox / Safari)
|
||||
- **Git** — verteiltes Versionskontrollsystem (Stand der Industrie)
|
||||
|
||||
**OS-Empfehlung:**
|
||||
|
||||
| OS | Status |
|
||||
|----|--------|
|
||||
| **macOS** | First-class, viele Tools "just work" |
|
||||
| **Linux** | First-class, beste Server-Parität |
|
||||
| **Windows + WSL2** | Empfohlen — Windows-CLI hat Edge Cases |
|
||||
| **Windows nativ** (PowerShell) | Funktioniert, aber holpriger Pfad |
|
||||
|
||||
---
|
||||
|
||||
# Node.js installieren – macOS
|
||||
|
||||
Empfohlen: **LTS** (aktuell **Node.js 24** — Stand 2026).
|
||||
|
||||
Paketmanager: **[Homebrew](https://brew.sh)**
|
||||
|
||||
```bash
|
||||
# Homebrew installieren (falls nicht vorhanden)
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
|
||||
# Node.js LTS installieren
|
||||
brew install node@24
|
||||
|
||||
# Version prüfen
|
||||
node --version
|
||||
npm --version
|
||||
```
|
||||
|
||||
Alternative: Installer von [nodejs.org](https://nodejs.org)
|
||||
|
||||
---
|
||||
|
||||
# Node.js installieren – Linux
|
||||
|
||||
Empfohlen: **LTS** (aktuell **Node.js 24**).
|
||||
|
||||
**Debian / Ubuntu** ([NodeSource](https://github.com/nodesource/distributions)):
|
||||
```bash
|
||||
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
|
||||
sudo apt install -y nodejs
|
||||
```
|
||||
|
||||
**Arch / Manjaro:**
|
||||
```bash
|
||||
sudo pacman -S nodejs npm
|
||||
```
|
||||
|
||||
**Fedora / RHEL:**
|
||||
```bash
|
||||
sudo dnf module install nodejs:24/common
|
||||
```
|
||||
|
||||
→ Distro-Pakete sind oft **veraltet**. Versionsmanager (folgende Folie) bevorzugen.
|
||||
|
||||
---
|
||||
|
||||
# Node.js installieren – Windows
|
||||
|
||||
Empfohlen: **LTS** (aktuell **Node.js 24**).
|
||||
|
||||
**Option 1: winget** (empfohlen, Win 10/11):
|
||||
```powershell
|
||||
winget install OpenJS.NodeJS.LTS
|
||||
```
|
||||
|
||||
**Option 2: Chocolatey** ([chocolatey.org](https://chocolatey.org)):
|
||||
```powershell
|
||||
choco install nodejs-lts
|
||||
```
|
||||
|
||||
**Option 3: Scoop** ([scoop.sh](https://scoop.sh)):
|
||||
```powershell
|
||||
scoop install nodejs-lts
|
||||
```
|
||||
|
||||
**Option 4:** Installer von [nodejs.org](https://nodejs.org)
|
||||
|
||||
→ **Empfehlung:** WSL2 + Linux-Workflow nutzen.
|
||||
|
||||
---
|
||||
|
||||
# Direkt-Install: Wenn es eng wird
|
||||
|
||||
**Typisches Szenario** — drei parallele Projekte mit unterschiedlichen Anforderungen:
|
||||
|
||||
| Projekt | Stack | Node-Version |
|
||||
|---------|-------|--------------|
|
||||
| Praktikums-API | Express (Legacy) | 18.x (LTS Hydrogen) |
|
||||
| Studienarbeit | Next.js 15 | 22.x (LTS Jod) |
|
||||
| Aktuelles Lab-Projekt | neueste Features | 24.x (Iron, current LTS) |
|
||||
|
||||
System-Install kennt nur **eine** Node-Version global → Konflikt.
|
||||
|
||||
---
|
||||
|
||||
# Symptome im Alltag
|
||||
|
||||
- `npm install` bricht mit `engines`-Warning ab
|
||||
- native Module (z. B. `node-sass`) bauen nicht
|
||||
- CI-Pipeline ist grün, lokal rot — oder umgekehrt
|
||||
- erheblicher Zeitverlust beim Debuggen von Versions-Konflikten
|
||||
|
||||
→ Lösung: **Versionsmanager** und Node-Version pro Projekt pinnen.
|
||||
|
||||
---
|
||||
|
||||
# Node-Versionsmanager
|
||||
|
||||
Verschiedene Projekte brauchen oft verschiedene Node-Versionen. **Lösung:** Versionsmanager.
|
||||
|
||||
| Tool | Sprache | Geschwindigkeit | Plattform |
|
||||
|------|---------|-----------------|-----------|
|
||||
| **nvm** | Bash | langsam (Shell-Funktion) | macOS, Linux |
|
||||
| **fnm** | Rust | sehr schnell | macOS, Linux, Windows |
|
||||
| **asdf** | Bash | mittel, multi-language | macOS, Linux |
|
||||
| **volta** | Rust | schnell, pinning per `package.json` | alle |
|
||||
|
||||
```bash
|
||||
# fnm
|
||||
curl -fsSL https://fnm.vercel.app/install | bash
|
||||
fnm install --lts # aktuell Node 24
|
||||
fnm use lts-latest
|
||||
|
||||
# .nvmrc / .node-version automatisch
|
||||
echo "lts/*" > .nvmrc
|
||||
fnm use # liest .nvmrc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Alternative Runtimes
|
||||
|
||||
| Runtime | Erschienen | Engine | USP |
|
||||
|---------|------------|--------|-----|
|
||||
| **Node.js** | 2009 | V8 (C++) | Standard, größtes Ökosystem |
|
||||
| **Deno** | 2020 | V8 (Rust) | Sicherheit, TypeScript nativ, Std-Lib |
|
||||
| **Bun** | 2022 | JavaScriptCore (Zig) | Geschwindigkeit, all-in-one |
|
||||
|
||||
```bash
|
||||
# Deno
|
||||
curl -fsSL https://deno.land/install.sh | sh
|
||||
deno run main.ts
|
||||
|
||||
# Bun
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
bun install
|
||||
bun run dev
|
||||
```
|
||||
|
||||
→ Im Kurs primär **Node.js**. Bun und Deno sind ebenfalls zulässig.
|
||||
|
||||
---
|
||||
|
||||
# Editor (IDE)
|
||||
|
||||
- **VS Code** (empfohlen) — kostenlos, große Plugin-Auswahl
|
||||
- **JetBrains WebStorm** — sehr mächtig, kostenpflichtig
|
||||
- kostenlos für Studierende via [JetBrains Education](https://www.jetbrains.com/community/education/#students)
|
||||
- **Vim / Neovim / Emacs** — für CLI-Profis
|
||||
- **Zed** — modern, schnell
|
||||
|
||||
---
|
||||
|
||||
# Browser DevTools
|
||||
|
||||
Chromium / Firefox / Safari — `F12` oder `Cmd+Opt+I`
|
||||
|
||||
<small>Chromium = Open-Source-Basis von Chrome, Edge, Brave, Opera, Vivaldi etc.</small>
|
||||
|
||||
**Wichtigste Panels:**
|
||||
- **Network** — HTTP-Requests, Responses, Timing
|
||||
- **Console** — `console.log`, JS-Errors, REPL
|
||||
- **Elements** — DOM-Inspektor, Live-CSS-Editor
|
||||
- **Sources** — Debugger, Breakpoints, Source-Maps
|
||||
- **Performance** — Profiling, Frame-Rate, FPS
|
||||
- **Application** — LocalStorage, Cookies, Service Workers
|
||||
|
||||
---
|
||||
|
||||
# HTTP-Clients
|
||||
|
||||
- **Bruno** ([usebruno.com](https://usebruno.com)) — open source, git-friendly
|
||||
- **Hoppscotch** ([hoppscotch.io](https://hoppscotch.io)) — open source, browser-basiert (vormals Postwoman)
|
||||
- **Insomnia** / **Postman** — etabliert, freemium
|
||||
- **curl** / **httpie** — CLI (Command-Line Interface, scriptbar)
|
||||
|
||||
---
|
||||
|
||||
# Git Setup & Workflow
|
||||
|
||||
**Einmalige Konfiguration:**
|
||||
```bash
|
||||
git config --global user.name "Vorname Nachname"
|
||||
git config --global user.email "vorname.nachname@example.com"
|
||||
git config --global init.defaultBranch main
|
||||
git config --global pull.rebase true
|
||||
```
|
||||
|
||||
**SSH-Key für GitHub/GitLab:**
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -C "vorname.nachname@example.com"
|
||||
cat ~/.ssh/id_ed25519.pub # → in GitHub-Settings einfügen
|
||||
```
|
||||
|
||||
**Tägliche Befehle:**
|
||||
```bash
|
||||
git status # was hat sich geändert?
|
||||
git add <file> # für Commit vormerken
|
||||
git commit -m "feat: ..." # Conventional Commits
|
||||
git push # hochladen
|
||||
git pull --rebase # neueste Änderungen holen
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Projekt-Skeleton
|
||||
|
||||
```bash
|
||||
mkdir mein-projekt && cd mein-projekt
|
||||
git init
|
||||
npm init -y # erzeugt package.json
|
||||
echo "node_modules" > .gitignore
|
||||
echo ".env" >> .gitignore
|
||||
npm install express
|
||||
```
|
||||
|
||||
**`package.json` minimal:**
|
||||
```json
|
||||
{
|
||||
"name": "mein-projekt",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"dev": "node --watch index.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`.env` für Secrets (nie committen!), `.env.example` mit Dummy-Werten committen.
|
||||
|
||||
---
|
||||
@@ -0,0 +1,412 @@
|
||||
---
|
||||
marp: true
|
||||
theme: gaia
|
||||
paginate: true
|
||||
backgroundColor: #fff
|
||||
header: "Web Engineering – DHBW Stuttgart"
|
||||
footer: "Michael Czechowski – SoSe 2026"
|
||||
title: JavaScript & Frameworks
|
||||
---
|
||||
|
||||
<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 -->
|
||||
|
||||

|
||||
|
||||
# Web Engineering
|
||||
|
||||
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
|
||||
|
||||
**Sommersemester 2026** · Sitzung 3 — JS, Frameworks & Build Tools
|
||||
|
||||
[https://librete.ch/dhbw/](https://librete.ch/dhbw/)
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Ressourcen zum Selbstlernen
|
||||
|
||||
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
|
||||
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
|
||||
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/)
|
||||
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/)
|
||||
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
|
||||
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# JavaScript
|
||||
|
||||
---
|
||||
|
||||
# ECMAScript
|
||||
|
||||
- **Ecma International** — European Computer Manufacturers Association
|
||||
- Seit **1997** standardisiert als **ECMA-262**
|
||||
- Implementierungen: **JavaScript** (Netscape, später Mozilla), **JScript** (Microsoft), **ActionScript** (Adobe Flash) etc.
|
||||
- Aktuelle Version: **ECMAScript 2024 (ES15)**, jährliche Releases
|
||||
|
||||
**Browser-Kompatibilität:**
|
||||
- Faustregel: **ES2017+** läuft in allen modernen Browsern
|
||||
- Ältere Targets via Transpiler (Babel, esbuild, swc, tsc)
|
||||
- Kompatibilitätstabelle: [kangax.github.io/compat-table](https://kangax.github.io/compat-table/)
|
||||
|
||||
---
|
||||
|
||||
# JavaScript Grundlagen
|
||||
|
||||
```javascript
|
||||
// Variablen
|
||||
let name = "Welt";
|
||||
const alter = 25;
|
||||
|
||||
// Funktionen
|
||||
function gruss(name) {
|
||||
return `Hallo, ${name}!`;
|
||||
}
|
||||
|
||||
// Arrow Functions
|
||||
const add = (a, b) => a + b;
|
||||
|
||||
// Arrays
|
||||
const arr = [1, 2, 3];
|
||||
arr.map(x => x * 2); // [2, 4, 6]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# DOM Manipulation
|
||||
|
||||
```javascript
|
||||
// Element auswählen
|
||||
const title = document.querySelector('h1');
|
||||
|
||||
// Inhalt ändern
|
||||
title.textContent = 'Neue Überschrift';
|
||||
|
||||
// Event Listener
|
||||
button.addEventListener('click', () => {
|
||||
alert('Geklickt!');
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Fetch API
|
||||
|
||||
```javascript
|
||||
fetch('/api/products')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Async/Await
|
||||
|
||||
```javascript
|
||||
async function loadProducts() {
|
||||
try {
|
||||
const res = await fetch('/api/products');
|
||||
const data = await res.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('Fehler:', err);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
<!-- _backgroundColor: #050517 -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
<!-- _backgroundColor: #050517 -->
|
||||
<!-- _color: #e2e8f0 -->
|
||||
|
||||
# Web Engineering Mandala
|
||||
|
||||
Interaktive version: [nextlevelshit.github.io/web-engineering-mandala](https://nextlevelshit.github.io/web-engineering-mandala/)
|
||||
|
||||
3 Schichten:
|
||||
- **Core Layer** — JS, HTML, CSS (vanilla)
|
||||
- **Build Layer** — bundlers, runtimes, template engines, pre/post processors
|
||||
- **Application Layer** — frontend, backend, frameworks, SSR, CSS-utilities
|
||||
|
||||
3 Segmente: HTML (blau) · JavaScript (rot) · CSS (grün) · Mittelpunkt: `package.json`
|
||||
|
||||
---
|
||||
|
||||
# JavaScript Frameworks – Kategorien
|
||||
|
||||
| Kategorie | Beispiele |
|
||||
|-----------|-----------|
|
||||
| **CSS Frameworks** | Tailwind, Bootstrap, shadcn/ui |
|
||||
| **Frontend Frameworks** | React, Vue, Svelte, Astro |
|
||||
| **Rendering / Meta** | Next.js, Nuxt, Gatsby |
|
||||
| **Build Tools / Bundler** | Webpack, Vite, Parcel, esbuild |
|
||||
| **Backend Frameworks** | Express, Fastify, NestJS |
|
||||
|
||||
---
|
||||
|
||||
# Vanilla JS – Counter
|
||||
|
||||
```html
|
||||
<button id="myButton">Clicked 0 times</button>
|
||||
<script>
|
||||
let count = 0;
|
||||
const btn = document.getElementById('myButton');
|
||||
btn.addEventListener('click', () => {
|
||||
count++;
|
||||
btn.textContent = `Clicked ${count} times`;
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
**Pro:** hohe Kompatibilität, simpel
|
||||
**Contra:** State-Management und Re-Rendering werden schnell komplex
|
||||
|
||||
---
|
||||
|
||||
# React – Counter (JSX)
|
||||
|
||||

|
||||
|
||||
```jsx
|
||||
import { useState } from "react";
|
||||
|
||||
function MyButton() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<button onClick={() => setCount(count + 1)}>
|
||||
Clicked {count} times
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- **Component-Based** Architecture
|
||||
- **Virtual DOM**, JSX, Hooks
|
||||
- großes Ökosystem (React Router, Redux, …)
|
||||
|
||||
---
|
||||
|
||||
# Vue 3 – Composition API
|
||||
|
||||

|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
const count = ref(0);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button @click="count++">
|
||||
Clicked {{ count }} times
|
||||
</button>
|
||||
</template>
|
||||
```
|
||||
|
||||
- **Reactive Data Binding**
|
||||
- Single-File Components (`.vue`)
|
||||
- Vue Router, Pinia (State)
|
||||
|
||||
---
|
||||
|
||||
# Svelte – Counter
|
||||
|
||||

|
||||
|
||||
```svelte
|
||||
<script>
|
||||
let count = 0;
|
||||
</script>
|
||||
|
||||
<button on:click={() => count += 1}>
|
||||
Clicked {count} {count === 1 ? 'time' : 'times'}
|
||||
</button>
|
||||
```
|
||||
|
||||
- **Compiler-Based** – kein Runtime-Framework
|
||||
- Reactive Assignments (`let`)
|
||||
- Built-in Animationen
|
||||
|
||||
---
|
||||
|
||||
# Astro – Content-First
|
||||
|
||||

|
||||
|
||||
```astro
|
||||
---
|
||||
const greeting = "Hallo DHBW";
|
||||
---
|
||||
<html>
|
||||
<body>
|
||||
<h1>{greeting}</h1>
|
||||
<MyReactButton client:load />
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
- **Islands Architecture** – Komponenten mehrerer Frameworks mischen
|
||||
- Default: kein JS im Output (nur statisches HTML)
|
||||
- Hydration on demand (`client:load`, `client:visible`)
|
||||
|
||||
---
|
||||
|
||||
# Rendering Frameworks
|
||||
|
||||
| | Frontend | SSR | SSG | File Routing |
|
||||
|---|---|---|---|---|
|
||||
| **Next.js** | React | ja | ja | `/app/page.js` |
|
||||
| **Nuxt** | Vue | ja | ja | `/pages/*.vue` |
|
||||
| **Gatsby** | React | nein | ja | GraphQL Data |
|
||||
|
||||
---
|
||||
|
||||
# Build Tools – Webpack
|
||||
|
||||

|
||||
|
||||
- Module Bundling (JS, CSS, Images)
|
||||
- Module Loaders + großes Plugin-Ökosystem
|
||||
- Code Splitting, HMR
|
||||
- Konfigurations-lastig (`webpack.config.js`)
|
||||
|
||||
→ Standard für viele Legacy-Setups.
|
||||
|
||||
---
|
||||
|
||||
# Build Tools – Vite
|
||||
|
||||

|
||||
|
||||
```javascript
|
||||
// vite.config.js
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://localhost:4567",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- Rollup unter der Haube, **HMR mit ES Modules**
|
||||
- Tree-Shaking, Code Splitting, React + Vue out-of-the-box
|
||||
|
||||
---
|
||||
|
||||
# Build Tools – Parcel
|
||||
|
||||

|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-project",
|
||||
"source": "src/index.html",
|
||||
"browserslist": "> 0.5%, last 2 versions, not dead",
|
||||
"scripts": {
|
||||
"start": "parcel",
|
||||
"build": "parcel build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"parcel": "latest"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Zero-Config**
|
||||
- Auto-Resolution, HMR
|
||||
- TS / CSS-Preprocessors out-of-the-box
|
||||
|
||||
---
|
||||
|
||||
# Microfrontends
|
||||
|
||||
- Mehrere unabhängige Frontend-Apps in einer Page
|
||||
- Module Federation (Webpack 5)
|
||||
- **single-spa**: https://single-spa.js.org/
|
||||
- Anwendungsfall: große Teams, unterschiedliche Tech-Stacks pro Domain
|
||||
|
||||
→ Komplexitätskosten: nur bei echtem Skalierungsbedarf.
|
||||
|
||||
---
|
||||
|
||||
# Demo-Repos
|
||||
|
||||
- https://github.com/nextlevelshit/dhbw-client-js (Counter, multi-framework)
|
||||
- https://github.com/nextlevelshit/node-cache-api (API + Tests)
|
||||
- https://github.com/nextlevelshit/dhbw-docker (Compose-Stack)
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
---
|
||||
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: invert -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Web Engineering
|
||||
|
||||
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
|
||||
|
||||
**Sommersemester 2026** · Sitzung 4 — Node.js: Scripting, Running and Building
|
||||
|
||||
[https://librete.ch/dhbw/](https://librete.ch/dhbw/)
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Ressourcen zum Selbstlernen
|
||||
|
||||
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
|
||||
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
|
||||
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/)
|
||||
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/)
|
||||
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
|
||||
|
||||
---
|
||||
|
||||
# Inhalt
|
||||
|
||||
1. Node.js: was und warum
|
||||
2. `node --check`, `node -e` (One-Liner)
|
||||
3. Module: `require` / `import`, `__dirname` / `__filename`
|
||||
4. HTTP-Server (built-in)
|
||||
5. `fs` — Dateien lesen / schreiben
|
||||
6. `package.json` — Aufbau und Lifecycle
|
||||
7. npm Scripts
|
||||
8. Module exportieren
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Node.js – Scripting, Running and Building
|
||||
|
||||
---
|
||||
|
||||
# Node.js – Was ist das?
|
||||
|
||||
- JavaScript Runtime (V8 Engine)
|
||||
- Server-side JavaScript
|
||||
- npm Package Manager
|
||||
- Ideal für: APIs, CLI Tools, Automation
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
|
||||
---
|
||||
|
||||
# node --check und node -e
|
||||
|
||||
```bash
|
||||
# Syntax check only
|
||||
node --check script.js
|
||||
|
||||
# One-liner
|
||||
node -e "console.log('Hallo Welt')"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# node -e Beispiele
|
||||
|
||||
```bash
|
||||
# String
|
||||
node -e "console.log('Hello ' + process.argv[2])" Welt
|
||||
|
||||
# File reading (ESM via --input-type)
|
||||
node --input-type=module -e \
|
||||
"import { readFileSync } from 'node:fs'; \
|
||||
console.log(readFileSync('data.json', 'utf8'))"
|
||||
|
||||
# JSON parsen
|
||||
node -e "console.log(JSON.parse(process.argv[1]))" '{"a":1}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Module laden – ESM (modern)
|
||||
|
||||
`"type": "module"` in `package.json` einschalten.
|
||||
|
||||
```javascript
|
||||
// Eigene Datei (Endung .js erforderlich!)
|
||||
import { add, greet } from "./utils.js";
|
||||
|
||||
// npm Modul (default + named import)
|
||||
import express from "express";
|
||||
import { Router } from "express";
|
||||
|
||||
// Built-in (mit "node:"-Präfix)
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
```
|
||||
|
||||
<small>**Legacy CJS:** `const express = require("express")` — funktioniert noch, aber ESM ist heute Standard.</small>
|
||||
|
||||
---
|
||||
|
||||
# Pfad-Auflösung in ESM
|
||||
|
||||
```javascript
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const configPath = join(__dirname, "..", "config.json");
|
||||
```
|
||||
|
||||
<small>In ESM gibt es **kein** automatisches `__dirname` / `__filename` (anders als CJS). Über `import.meta.url` rekonstruieren.</small>
|
||||
|
||||
---
|
||||
|
||||
# HTTP Server mit Node.js (Built-in)
|
||||
|
||||
**ESM** (modern, `"type": "module"` in `package.json`):
|
||||
```javascript
|
||||
import { createServer } from "node:http";
|
||||
|
||||
const server = createServer((req, res) => {
|
||||
res.writeHead(200, { "Content-Type": "text/html" });
|
||||
res.end("<h1>Hallo Welt!</h1>");
|
||||
});
|
||||
|
||||
server.listen(3000, () => {
|
||||
console.log("Server läuft auf Port 3000");
|
||||
});
|
||||
```
|
||||
|
||||
<small>**CommonJS** (legacy): `const http = require("http");` — funktioniert noch, aber neue Projekte nutzen ESM.</small>
|
||||
|
||||
---
|
||||
|
||||
# fs – Dateien lesen/schreiben
|
||||
|
||||
```javascript
|
||||
import { readFile, readFileSync, writeFile } from "node:fs";
|
||||
import { readFile as readFileAsync } from "node:fs/promises";
|
||||
|
||||
// Promise-API (empfohlen, modern)
|
||||
const data = await readFileAsync("data.json", "utf8");
|
||||
console.log(JSON.parse(data));
|
||||
|
||||
// Callback-API
|
||||
readFile("data.json", "utf8", (err, data) => {
|
||||
if (err) throw err;
|
||||
console.log(JSON.parse(data));
|
||||
});
|
||||
|
||||
// Sync (nur für Setup / Scripts)
|
||||
const content = readFileSync("data.json", "utf8");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# package.json – Das Herzstück
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "mein-projekt",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"dev": "node --watch index.js",
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# npm Scripts
|
||||
|
||||
```bash
|
||||
# npm run <script>
|
||||
npm run start
|
||||
npm run dev
|
||||
|
||||
# Mit Argumenten
|
||||
npm run build -- --production
|
||||
|
||||
# Lifecycle scripts
|
||||
npm install → postinstall
|
||||
npm start → start (direkt)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Node.js Module – exports (ESM)
|
||||
|
||||
```javascript
|
||||
// utils.js — named exports
|
||||
export const add = (a, b) => a + b;
|
||||
export const greet = (name) => `Hallo ${name}!`;
|
||||
|
||||
// utils.js — default export
|
||||
export default {
|
||||
add,
|
||||
greet
|
||||
};
|
||||
```
|
||||
|
||||
```javascript
|
||||
// nutzen
|
||||
import { add, greet } from "./utils.js"; // named
|
||||
import utils from "./utils.js"; // default
|
||||
import utils, { add } from "./utils.js"; // beides
|
||||
```
|
||||
|
||||
<small>**Legacy CJS:** `module.exports = { add, greet }` + `const { add } = require("./utils")`.</small>
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
---
|
||||
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: invert -->
|
||||
<!-- _header: '' -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Web Engineering
|
||||
|
||||
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
|
||||
|
||||
**Sommersemester 2026** · Sitzung 5 — Express API, CRUD und Middlewares
|
||||
|
||||
[https://librete.ch/dhbw/](https://librete.ch/dhbw/)
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Ressourcen zum Selbstlernen
|
||||
|
||||
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
|
||||
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
|
||||
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/)
|
||||
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/)
|
||||
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
|
||||
|
||||
---
|
||||
|
||||
# Inhalt
|
||||
|
||||
1. Node.js — Fun Facts & alternative Runtimes
|
||||
2. `process` — Signal-Handling, Graceful Shutdown
|
||||
3. Express — Setup, Routing
|
||||
4. Middlewares (eigene + built-in)
|
||||
5. CRUD-Endpunkte (Create, Read, Update, Delete)
|
||||
6. Error Handling
|
||||
7. WebSockets
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Node.js – Advanced
|
||||
|
||||
---
|
||||
|
||||
# node.js – Fun Facts
|
||||
|
||||
- Entwickelt von Ryan Dahl in 2009
|
||||
- Erste serverseitige Laufzeitumgebung in JavaScript (C++)
|
||||
- Inspiriert von Nginx (Non-blocking I/O)
|
||||
|
||||
**Alternativen heute:**
|
||||
- **Deno** (2020): Rust, Tokio
|
||||
- **Bun** (2022): Zig
|
||||
|
||||
---
|
||||
|
||||
# process – Prozess-Steuerung
|
||||
|
||||
```javascript
|
||||
process.on("SIGINT", () => {
|
||||
console.log("Received SIGINT. Closing server...");
|
||||
server.close(() => {
|
||||
console.log("Server closed.");
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
// Signale
|
||||
process.exit(0); // Erfolgreich
|
||||
process.exit(1); // Fehler
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# node.js Module System
|
||||
|
||||
```javascript
|
||||
// ES Modules (modern, "type": "module")
|
||||
import express from "express";
|
||||
import { port } from "./src/config/constants.js";
|
||||
import { readFileSync } from "node:fs";
|
||||
```
|
||||
|
||||
```javascript
|
||||
// CommonJS (legacy)
|
||||
const express = require("express");
|
||||
const fs = require("fs");
|
||||
```
|
||||
|
||||
→ Neue Projekte: ESM. Built-ins immer mit `node:`-Präfix.
|
||||
|
||||
---
|
||||
|
||||
# HTTP Request mit node-fetch
|
||||
|
||||
```javascript
|
||||
// Node.js 18+ hat fetch eingebaut
|
||||
const res = await fetch('https://api.example.com/data');
|
||||
const data = await res.json();
|
||||
|
||||
// Ältere Versionen: node-fetch
|
||||
import fetch from 'node-fetch';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Web Server, Builder, Bundler
|
||||
|
||||
```bash
|
||||
# Boilerplates erstellen
|
||||
npm create vite@latest my-app -- --template react
|
||||
npm create svelte@latest
|
||||
npm create astro@latest
|
||||
|
||||
# nuxt
|
||||
npm install nuxt
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
|
||||
---
|
||||
|
||||
# Express – Grundlagen
|
||||
|
||||
```javascript
|
||||
import express from 'express';
|
||||
const app = express();
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server läuft auf Port ${port}`);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Express – Middleware
|
||||
|
||||
```javascript
|
||||
app.use(express.json());
|
||||
|
||||
app.use('/api', (req, res, next) => {
|
||||
console.log('Time:', Date.now());
|
||||
next();
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
console.log('Middleware 2');
|
||||
next();
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Express – CRUD Routes
|
||||
|
||||
```javascript
|
||||
app.get('/api/:key', async (req, res) => {
|
||||
const context = cache.get(req.params.key);
|
||||
res.json({ context });
|
||||
});
|
||||
|
||||
app.post('/api/:key', async (req, res) => {
|
||||
const payload = req.body;
|
||||
res.json({ created: cache.set(req.params.key, payload) });
|
||||
});
|
||||
|
||||
app.put('/api/:key', async (req, res) => {
|
||||
const payload = req.body;
|
||||
res.json({ updated: cache.update(req.params.key, payload) });
|
||||
});
|
||||
|
||||
app.delete('/api/:key', async (req, res) => {
|
||||
cache.remove(req.params.key);
|
||||
res.json({ deleted: req.params.key });
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Express – server.listen
|
||||
|
||||
```javascript
|
||||
const server = app.listen(port, () => {
|
||||
console.log(`Server läuft auf Port ${port}`);
|
||||
});
|
||||
|
||||
server.on('error', (err) => {
|
||||
console.error('Server error:', err);
|
||||
});
|
||||
|
||||
server.close(() => {
|
||||
console.log('Server geschlossen');
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# WebSockets
|
||||
|
||||
```javascript
|
||||
import { WebSocketServer } from 'ws';
|
||||
|
||||
const wss = new WebSocketServer({ port: 8080 });
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
console.log('Client verbunden');
|
||||
|
||||
ws.on('message', (data) => {
|
||||
console.log('Empfangen:', data.toString());
|
||||
ws.send('Pong');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -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;
|
||||
--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 -->
|
||||
|
||||
# Web Engineering
|
||||
|
||||
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
|
||||
|
||||
**Sommersemester 2026** · Sitzung 7 — Testing
|
||||
|
||||
[https://librete.ch/dhbw/](https://librete.ch/dhbw/)
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
# Ressourcen zum Selbstlernen
|
||||
|
||||
- **CODE CRISPIES** — [codecrispi.es](https://codecrispi.es/)
|
||||
- **Online Code-Editor** — [codepen.io](https://codepen.io/pen/)
|
||||
- **MDN** (Mozilla Developer Network) — [developer.mozilla.org/de/](https://developer.mozilla.org/de/)
|
||||
- **Flexbox-Spiel** — [flexboxfroggy.com](https://flexboxfroggy.com/)
|
||||
- **Grid-Spiel** — [cssgridgarden.com](https://cssgridgarden.com/)
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Testing
|
||||
|
||||
## Unit · Integration · End-to-End
|
||||
|
||||
---
|
||||
|
||||
# Inhalt
|
||||
|
||||
1. Test Automation Pyramid
|
||||
2. Test Automation Trophy
|
||||
3. Static Code Analysis
|
||||
4. Unit Tests
|
||||
5. Integration Tests
|
||||
6. End-to-End Tests (API + Client)
|
||||
7. Live Coding
|
||||
|
||||
---
|
||||
|
||||
# Test Automation Pyramid
|
||||
|
||||
```
|
||||
▲ manual / exploratory
|
||||
▲ ▲ E2E (UI + API)
|
||||
▲▲▲▲ Integration (class + real deps)
|
||||
▲▲▲▲▲▲ Unit (class + mocked deps)
|
||||
▲▲▲▲▲▲▲▲ Static analysis (compiler, linter)
|
||||
```
|
||||
|
||||
- Oben: wenige, langsam, teuer, brüchig
|
||||
- Unten: viele, schnell, billig, stabil
|
||||
|
||||
---
|
||||
|
||||
# Test Automation V-Modell
|
||||
|
||||
```
|
||||
Requirements ────────────► Acceptance Test
|
||||
▼ ▲
|
||||
Architecture ────────► Integration Test
|
||||
▼ ▲
|
||||
Modular Design ──────► Unit Test
|
||||
▼ ▲
|
||||
Implementation
|
||||
```
|
||||
|
||||
Jede Design-Phase hat Testpendant. Testfälle früh ableiten.
|
||||
|
||||
---
|
||||
|
||||
# Test Automation Trophy (Kent C. Dodds)
|
||||
|
||||
```
|
||||
┌─────────┐
|
||||
│ E2E │ ← klein
|
||||
├─────────┤
|
||||
│ Integr. │ ← GROSS (Hauptfokus)
|
||||
├─────────┤
|
||||
│ Unit │ ← mittel
|
||||
├─────────┤
|
||||
│ Static │ ← Basis (TS, ESLint)
|
||||
└─────────┘
|
||||
```
|
||||
|
||||
Andere Gewichtung: **Integration im Zentrum**, Static als breite Basis.
|
||||
|
||||
---
|
||||
|
||||
# Testing Frameworks Übersicht
|
||||
|
||||
**Static Code Analysis**
|
||||
- ESLint, Prettier, SonarQube, TypeScript
|
||||
|
||||
**Unit + Integration**
|
||||
- jest, mocha, **vitest**
|
||||
|
||||
**End-to-End**
|
||||
- **Cypress**, **Playwright**, supertest (API only)
|
||||
|
||||
**Visual / Interaction**
|
||||
- Storybook, Chromatic
|
||||
|
||||
---
|
||||
|
||||
# Static Code Analysis
|
||||
|
||||
- Läuft in Millisekunden
|
||||
- Findet Syntaxfehler, ungenutzte Variablen, undefined refs
|
||||
- Kein Runtime-Overhead
|
||||
- **Erste Verteidigungslinie**
|
||||
|
||||
```bash
|
||||
npm i -D eslint prettier
|
||||
npx eslint --init
|
||||
npx prettier --write .
|
||||
```
|
||||
|
||||
`SonarQube`: zentraler Server, Code Smells, Security, Coverage.
|
||||
|
||||
---
|
||||
|
||||
# Unit Tests – Eigenschaften
|
||||
|
||||
- **Isolation** – jede Dependency gemockt
|
||||
- **Deterministisch** – gleicher Input → gleicher Output
|
||||
- **Schnell** – kein I/O, kein Netzwerk, kein FS
|
||||
- **Fokussiert** – eine Funktion pro Test
|
||||
|
||||
---
|
||||
|
||||
# Unit Test – Beispiel (vitest)
|
||||
|
||||
```javascript
|
||||
// cache.unit.test.js
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { Cache } from "./cache.js";
|
||||
|
||||
describe("Cache", () => {
|
||||
it("creates and retrieves data with custom key", () => {
|
||||
const cache = new Cache();
|
||||
const data = { user: "wolfgang", city: "stuttgart" };
|
||||
|
||||
const key = cache.create(data, "custom-key");
|
||||
|
||||
expect(key).toBe("custom-key");
|
||||
expect(cache.get("custom-key")).toEqual(data);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Integration Tests – Eigenschaften
|
||||
|
||||
- **Reale Komponenten** – echte Cache + Express-Instanzen
|
||||
- **Supertest-Magie** – simuliert HTTP ohne Server-Startup
|
||||
- **In-Process** – alles im selben Node-Prozess
|
||||
- **Kein Netzwerk** – keine TCP-Connections
|
||||
|
||||
→ teste Zusammenspiel mehrerer Module ohne Deployment.
|
||||
|
||||
---
|
||||
|
||||
# Integration Test – Beispiel (supertest)
|
||||
|
||||
```javascript
|
||||
// app.integration.test.js
|
||||
import request from "supertest";
|
||||
import { app, cache } from "./app.js";
|
||||
|
||||
test("full CRUD lifecycle with real cache", async () => {
|
||||
const res = await request(app)
|
||||
.post("/api/entries")
|
||||
.send({ name: "integration-test", status: "active" });
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
const { key } = res.body;
|
||||
|
||||
expect(cache.keys()).toContain(key);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# End-to-End Tests (API)
|
||||
|
||||
- **Echter Server** – HTTP auf TCP-Port
|
||||
- **Echtes Netzwerk** – `fetch` über localhost
|
||||
- **Echte Umgebung** – CORS, Middleware-Stack
|
||||
- **User-Perspektive** – exakt was Clients sehen
|
||||
|
||||
---
|
||||
|
||||
# E2E API Test – Beispiel
|
||||
|
||||
```javascript
|
||||
// index.e2e.test.js
|
||||
import { createServer } from "node:http";
|
||||
import { app } from "./app.js";
|
||||
|
||||
let server;
|
||||
const TEST_PORT = 8081;
|
||||
const baseUrl = `http://localhost:${TEST_PORT}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
server = createServer(app);
|
||||
await new Promise((r) => server.listen(TEST_PORT, r));
|
||||
});
|
||||
|
||||
afterAll(() => server.close());
|
||||
|
||||
test("complete user journey", async () => {
|
||||
const res = await fetch(`${baseUrl}/api`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ message: "e2e", timestamp: Date.now() }),
|
||||
});
|
||||
expect(res.status).toBe(201);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# End-to-End Tests (Client)
|
||||
|
||||
- **Echter Browser** – Chromium / Firefox / WebKit
|
||||
- **Echtes DOM** – Klicks, Tippen, Scrollen
|
||||
- **Echtes Rendering** – CSS, async Operationen
|
||||
- **Cross-Origin** – Client- und Server-Kommunikation
|
||||
|
||||
Tools: **Cypress**, **Playwright**
|
||||
|
||||
---
|
||||
|
||||
# Cypress – Minimalbeispiel
|
||||
|
||||
```javascript
|
||||
// cypress/e2e/counter.cy.js
|
||||
describe("Counter App", () => {
|
||||
it("increments on click", () => {
|
||||
cy.visit("http://localhost:3000");
|
||||
cy.contains("button", "Clicked 0 times").click();
|
||||
cy.contains("button", "Clicked 1 times");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```bash
|
||||
npx cypress open # GUI
|
||||
npx cypress run # headless / CI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Playwright – Minimalbeispiel
|
||||
|
||||
```javascript
|
||||
// tests/counter.spec.js
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("counter increments", async ({ page }) => {
|
||||
await page.goto("http://localhost:3000");
|
||||
await page.getByRole("button").click();
|
||||
await expect(page.getByRole("button")).toContainText("Clicked 1 times");
|
||||
});
|
||||
```
|
||||
|
||||
```bash
|
||||
npx playwright test
|
||||
npx playwright test --ui
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Coverage Reports
|
||||
|
||||
```bash
|
||||
npx vitest run --coverage
|
||||
```
|
||||
|
||||
```
|
||||
File | % Stmts | % Branch | % Funcs | % Lines
|
||||
--------------|---------|----------|---------|--------
|
||||
cache.js | 95.2 | 80.0 | 100.0 | 95.2
|
||||
app.js | 88.7 | 75.0 | 90.0 | 88.7
|
||||
```
|
||||
|
||||
- `lcov.info` → CI / SonarQube
|
||||
- HTML Report → `coverage/index.html`
|
||||
|
||||
---
|
||||
|
||||
# Best Practices
|
||||
|
||||
- **AAA**: Arrange · Act · Assert
|
||||
- Tests sind **Dokumentation** – beschreibend benennen
|
||||
- Ein Verhalten pro Test
|
||||
- Keine Logik im Test (keine `if`/`for`)
|
||||
- **Test Doubles**: stub, mock, spy, fake
|
||||
- CI: Tests bei jedem Push
|
||||
|
||||
---
|
||||
|
||||
# Live Coding
|
||||
|
||||
Repo: `node-cache-api`
|
||||
|
||||
```bash
|
||||
git clone https://github.com/nextlevelshit/node-cache-api
|
||||
cd node-cache-api
|
||||
npm install
|
||||
npm test # unit + integration
|
||||
npm run test:e2e # e2e
|
||||
```
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
---
|
||||
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;
|
||||
--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 -->
|
||||
|
||||
# Web Engineering
|
||||
|
||||
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
|
||||
|
||||
**Sommersemester 2026** · Sitzung 8 — TypeScript
|
||||
|
||||
[https://librete.ch/dhbw/](https://librete.ch/dhbw/)
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# TypeScript
|
||||
|
||||
## JavaScript on Steroids
|
||||
|
||||
---
|
||||
|
||||
# Inhalt
|
||||
|
||||
1. TypeScript: was und warum
|
||||
2. `tsc` + `ts-node`
|
||||
3. `tsconfig.json`
|
||||
4. Type Primitives
|
||||
5. Union Types, Type Alias, Interface
|
||||
6. Enum, Generics, Type Casting
|
||||
7. Compilation: types stripped
|
||||
8. Decorators + reflect-metadata
|
||||
|
||||
---
|
||||
|
||||
# TypeScript – Was?
|
||||
|
||||
- **Superset von JavaScript** (Microsoft, 2012)
|
||||
- Jede `.js` lässt sich zu `.ts` umbenennen
|
||||
- **Type Errors zur Compile-/Transpile-Zeit**
|
||||
- Neuere ECMAScript-Features verfügbar
|
||||
- **Im Browser:** läuft nicht direkt – wird zu JS kompiliert
|
||||
|
||||
---
|
||||
|
||||
# Setup: tsc + ts-node
|
||||
|
||||
```bash
|
||||
npm i -D typescript ts-node @types/node
|
||||
npx tsc --init # erzeugt tsconfig.json
|
||||
```
|
||||
|
||||
**Run:**
|
||||
```bash
|
||||
npx ts-node index.ts # statt: node index.js
|
||||
```
|
||||
|
||||
**Build:**
|
||||
```bash
|
||||
npx tsc # kompiliert nach ./dist
|
||||
node dist/index.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# tsconfig.json
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Type Primitives
|
||||
|
||||
```typescript
|
||||
let loading: boolean = false;
|
||||
let count: number = 42;
|
||||
let pi: number = 3.14;
|
||||
let name: string = "DHBW ist fetzig!";
|
||||
let empty: null = null;
|
||||
let missing: undefined = undefined;
|
||||
|
||||
function log(msg: string): void {
|
||||
console.log(msg);
|
||||
}
|
||||
```
|
||||
|
||||
`any`, `unknown`, `never`, `void` sind weitere TS-Spezialtypen.
|
||||
|
||||
---
|
||||
|
||||
# Union Types
|
||||
|
||||
```typescript
|
||||
let id: number | string;
|
||||
|
||||
id = 101; // ok
|
||||
id = "101"; // ok
|
||||
id = true; // TypeError (nicht erlaubt)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Type Alias
|
||||
|
||||
```typescript
|
||||
type NumOrString = number | string;
|
||||
|
||||
let id: NumOrString;
|
||||
id = 101;
|
||||
id = "abc";
|
||||
|
||||
type UserId = string;
|
||||
type Status = "active" | "inactive" | "pending";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Interface
|
||||
|
||||
```typescript
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email?: string; // optional
|
||||
readonly createdAt: Date; // schreibgeschützt
|
||||
greet(msg: string): void;
|
||||
}
|
||||
|
||||
const u: User = {
|
||||
id: 1,
|
||||
name: "Lisa",
|
||||
createdAt: new Date(),
|
||||
greet(msg) { console.log(msg); }
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Interface vs Type
|
||||
|
||||
**Interface**
|
||||
- nur Objektformen
|
||||
- kann mehrfach deklariert (Merging)
|
||||
- bei perf-kritischen Checks oft schneller
|
||||
|
||||
**Type Alias**
|
||||
- alles: Primitive, Union, Tuple, Function-Signaturen
|
||||
- nicht erweiterbar nach Definition
|
||||
|
||||
→ Faustregel: **Interfaces für API-Shapes, Types für alles andere.**
|
||||
|
||||
---
|
||||
|
||||
# Enum
|
||||
|
||||
```typescript
|
||||
enum Direction {
|
||||
Up = 1,
|
||||
Down, // 2
|
||||
Left, // 3
|
||||
Right // 4
|
||||
}
|
||||
|
||||
function walk(d: Direction): string {
|
||||
switch (d) {
|
||||
case Direction.Up: return "bow";
|
||||
case Direction.Down: return "stern";
|
||||
case Direction.Left: return "windward";
|
||||
case Direction.Right: return "leeward";
|
||||
}
|
||||
}
|
||||
|
||||
walk(Direction.Up); // "bow"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Generics
|
||||
|
||||
```typescript
|
||||
class GenericContainer<T> {
|
||||
private readonly value: T;
|
||||
|
||||
constructor(value: T) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
getValue(): T {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
const numbers = new GenericContainer<number>(10);
|
||||
const strings = new GenericContainer<string>("Hello");
|
||||
|
||||
console.log(numbers.getValue()); // 10
|
||||
console.log(strings.getValue()); // "Hello"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Type Casting
|
||||
|
||||
```typescript
|
||||
let someValue: any = "this is a string";
|
||||
|
||||
let len: number = (someValue as string).length;
|
||||
console.log(len); // 16
|
||||
|
||||
// Alt-Syntax (nicht in TSX):
|
||||
let len2: number = (<string>someValue).length;
|
||||
```
|
||||
|
||||
`as unknown as T` für "double cast" – nur Notbehelf!
|
||||
|
||||
---
|
||||
|
||||
# Compilation: Types werden gestripped
|
||||
|
||||
**TypeScript:**
|
||||
```typescript
|
||||
type Result = "pass" | "fail";
|
||||
|
||||
function verify(result: Result) {
|
||||
if (result === "pass") console.log("Passed");
|
||||
else console.log("Failed");
|
||||
}
|
||||
```
|
||||
|
||||
**JavaScript (nach `tsc`):**
|
||||
```javascript
|
||||
function verify(result) {
|
||||
if (result === "pass") console.log("Passed");
|
||||
else console.log("Failed");
|
||||
}
|
||||
```
|
||||
|
||||
**Types existieren nur zur Compile-Zeit.** Kein Runtime-Check.
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Decorators
|
||||
|
||||
## Meta-Programmierung mit TypeScript
|
||||
|
||||
---
|
||||
|
||||
# TypeScript: Decorators
|
||||
|
||||
Vier Typen:
|
||||
|
||||
- **Class Decorators** – auf ganze Klasse
|
||||
- **Property Decorators** – auf Felder
|
||||
- **Method Decorators** – auf Methoden
|
||||
- **Parameter Decorators** – auf Methoden-Parameter
|
||||
|
||||
Genutzt von: Angular, NestJS, TypeORM, class-validator.
|
||||
|
||||
---
|
||||
|
||||
# Decorators aktivieren
|
||||
|
||||
**`tsconfig.json`:**
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Root-File:**
|
||||
```typescript
|
||||
import "reflect-metadata";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Decorator-Signaturen
|
||||
|
||||
```typescript
|
||||
type ClassDecorator = <T extends Function>(target: T) => T | void;
|
||||
|
||||
type PropertyDecorator = (
|
||||
target: Object,
|
||||
propertyKey: string | symbol
|
||||
) => void;
|
||||
|
||||
type MethodDecorator = <T>(
|
||||
target: Object,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: TypedPropertyDescriptor<T>
|
||||
) => TypedPropertyDescriptor<T> | void;
|
||||
|
||||
type ParameterDecorator = (
|
||||
target: Object,
|
||||
propertyKey: string | symbol | undefined,
|
||||
parameterIndex: number
|
||||
) => void;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Decorator – Beispiel
|
||||
|
||||
```typescript
|
||||
function Log(target: any, key: string, desc: PropertyDescriptor) {
|
||||
const original = desc.value;
|
||||
desc.value = function (...args: any[]) {
|
||||
console.log(`→ ${key}(${JSON.stringify(args)})`);
|
||||
return original.apply(this, args);
|
||||
};
|
||||
}
|
||||
|
||||
class Calculator {
|
||||
@Log
|
||||
add(a: number, b: number) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
|
||||
new Calculator().add(2, 3); // logs: → add([2,3])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Reflect: Meta-Daten lesen
|
||||
|
||||
```typescript
|
||||
import "reflect-metadata";
|
||||
|
||||
Reflect.defineMetadata("role", "admin", target);
|
||||
Reflect.hasMetadata("role", target); // true
|
||||
Reflect.getMetadata("role", target); // "admin"
|
||||
```
|
||||
|
||||
Frameworks (NestJS, TypeORM) nutzen das, um zur **Runtime** Type-Informationen zu rekonstruieren – z. B. für DI, ORM-Mapping, Validierung.
|
||||
|
||||
---
|
||||
|
||||
# Anwendung: NestJS-Beispiel
|
||||
|
||||
```typescript
|
||||
@Controller("users")
|
||||
export class UsersController {
|
||||
constructor(private users: UsersService) {}
|
||||
|
||||
@Get(":id")
|
||||
findOne(@Param("id") id: string) {
|
||||
return this.users.find(+id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`@Controller`, `@Get`, `@Param` sind Decorators – Metadaten ⇒ Routing.
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
---
|
||||
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;
|
||||
--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 -->
|
||||
|
||||
# Web Engineering
|
||||
|
||||
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
|
||||
|
||||
**Sommersemester 2026** · Sitzung 9 — Docker, Proxies and DBs
|
||||
|
||||
[https://librete.ch/dhbw/](https://librete.ch/dhbw/)
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Docker
|
||||
|
||||
## Service Orchestration
|
||||
|
||||
---
|
||||
|
||||
# Inhalt
|
||||
|
||||
1. Architektur: Reverse Proxy + API + DB
|
||||
2. Container vs VM
|
||||
3. `Dockerfile`
|
||||
4. `compose.yml`
|
||||
5. Docker CLI / Compose Befehle
|
||||
6. Live Demo: `dhbw-docker`
|
||||
|
||||
---
|
||||
|
||||
# Architektur
|
||||
|
||||
```
|
||||
Browser (localhost:10007)
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Reverse Proxy │ (nginx / traefik)
|
||||
└─────────┬────────┘
|
||||
┌────┴────┐
|
||||
▼ ▼
|
||||
┌─────────┐ ┌────────┐
|
||||
│ Web │ │ API │
|
||||
│ App │ │ (Node) │
|
||||
└────┬────┘ └────┬───┘
|
||||
└─────┬──────┘
|
||||
▼
|
||||
┌────────┐
|
||||
│ DB │ (Postgres)
|
||||
└────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Container vs VM
|
||||
|
||||
| | VM | Container |
|
||||
|---|----|-----------|
|
||||
| Kernel | eigener | Host-Kernel |
|
||||
| Boot | Minuten | Sekunden |
|
||||
| Größe | GB | MB |
|
||||
| Isolation | stark | namespaces, cgroups |
|
||||
| Image | komplett | Layer (diff) |
|
||||
|
||||
→ **Docker** = Container-Engine + Image-Format + Registry.
|
||||
|
||||
---
|
||||
|
||||
# Dockerfile – Express API
|
||||
|
||||
```dockerfile
|
||||
FROM node:lts-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./package*.json ./
|
||||
RUN npm ci --no-audit --no-fund --no-progress --loglevel=error
|
||||
|
||||
COPY ./ ./
|
||||
|
||||
ENV NODE_ENV="production"
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
**`npm ci`** statt `npm i` für reproduzierbare Builds.
|
||||
|
||||
---
|
||||
|
||||
# Multi-Stage Build
|
||||
|
||||
```dockerfile
|
||||
FROM node:lts-slim AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:lts-slim AS runtime
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY package*.json ./
|
||||
EXPOSE 8080
|
||||
CMD ["node", "dist/index.js"]
|
||||
```
|
||||
|
||||
→ kleines Final-Image, keine Build-Tools im Runtime.
|
||||
|
||||
---
|
||||
|
||||
# .dockerignore
|
||||
|
||||
```
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.env
|
||||
*.log
|
||||
coverage
|
||||
```
|
||||
|
||||
Spart Build-Kontext und verhindert Geheimnis-Leaks.
|
||||
|
||||
---
|
||||
|
||||
# compose.yml – Services
|
||||
|
||||
```yaml
|
||||
name: dhbw-docker-app
|
||||
|
||||
services:
|
||||
api:
|
||||
build: ./api
|
||||
container_name: dhbw-api
|
||||
environment:
|
||||
DATABASE_USERNAME: ${DATABASE_USERNAME}
|
||||
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
|
||||
DATABASE_NAME: ${DATABASE_NAME}
|
||||
DATABASE_HOST: db
|
||||
DATABASE_PORT: 5432
|
||||
ports:
|
||||
- "10000:8080"
|
||||
networks: [net, data]
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# compose.yml – Datenbank + Proxy
|
||||
|
||||
```yaml
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_USER: ${DATABASE_USERNAME}
|
||||
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
|
||||
POSTGRES_DB: ${DATABASE_NAME}
|
||||
volumes:
|
||||
- dbdata:/var/lib/postgresql/data
|
||||
networks: [data]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $$DATABASE_USERNAME"]
|
||||
interval: 5s
|
||||
retries: 5
|
||||
|
||||
proxy:
|
||||
image: nginx:alpine
|
||||
ports: ["10007:80"]
|
||||
volumes: ["./nginx.conf:/etc/nginx/nginx.conf:ro"]
|
||||
networks: [net]
|
||||
depends_on: [api]
|
||||
|
||||
volumes:
|
||||
dbdata:
|
||||
|
||||
networks:
|
||||
net:
|
||||
data:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Docker Compose – Befehle
|
||||
|
||||
```bash
|
||||
docker compose -p "dhbw-docker-app" \
|
||||
-f compose.yml \
|
||||
--env-file .env \
|
||||
up \
|
||||
--build \
|
||||
--remove-orphans \
|
||||
--force-recreate
|
||||
```
|
||||
|
||||
| Flag | Bedeutung |
|
||||
|------|-----------|
|
||||
| `-p` | Projektname |
|
||||
| `-f` | mehrere Compose-Files möglich |
|
||||
| `--env-file` | `.env` einlesen |
|
||||
| `--build` | Images neu bauen |
|
||||
| `--remove-orphans` | verwaiste Container weg |
|
||||
| `--force-recreate` | Container neu starten |
|
||||
|
||||
---
|
||||
|
||||
# Compose – Alltag
|
||||
|
||||
```bash
|
||||
docker compose up -d # detached starten
|
||||
docker compose ps # Status
|
||||
docker compose logs -f api # Logs streamen
|
||||
docker compose exec api sh # Shell im Container
|
||||
docker compose down -v # Stop + Volumes löschen
|
||||
docker compose restart api # Neustart einzelner Service
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Health Checks
|
||||
|
||||
```yaml
|
||||
api:
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
```
|
||||
|
||||
`depends_on: { condition: service_healthy }` wartet, bis Health passt.
|
||||
|
||||
---
|
||||
|
||||
# Networks: Segmentierung
|
||||
|
||||
- **`net`** – öffentlich erreichbar (Proxy — App)
|
||||
- **`data`** – nur intern (App — DB)
|
||||
- DB ist **nicht** vom Internet erreichbar
|
||||
- Service-DNS: `api`, `db`, `proxy` (Container-Name = Hostname)
|
||||
|
||||
→ Defense in Depth.
|
||||
|
||||
---
|
||||
|
||||
# Volumes – Daten persistent
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
dbdata: # benannt
|
||||
- ./code:/app # bind mount (dev)
|
||||
- /app/node_modules # anonymous (überschreibt bind)
|
||||
```
|
||||
|
||||
`docker compose down` ohne `-v` lässt Volumes leben.
|
||||
|
||||
---
|
||||
|
||||
# Live Demo
|
||||
|
||||
Repo: https://github.com/nextlevelshit/dhbw-docker
|
||||
|
||||
```bash
|
||||
git clone https://github.com/nextlevelshit/dhbw-docker
|
||||
cd dhbw-docker
|
||||
cp .env.example .env
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
→ http://localhost:10007
|
||||
|
||||
---
|
||||
|
||||
# 100 Concepts of Docker (Exkurs)
|
||||
|
||||
- Image · Container · Volume · Network
|
||||
- Dockerfile · Layer Cache · BuildKit
|
||||
- Compose · Stack · Swarm · Kubernetes
|
||||
- Registry · Tag · Digest
|
||||
- Dev/Prod-Parity (12-Factor)
|
||||
- Secrets · Configs
|
||||
- Logging Drivers
|
||||
- Health Checks · Restart Policies
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
---
|
||||
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;
|
||||
--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 -->
|
||||
|
||||
# Web Engineering
|
||||
|
||||
**DHBW Stuttgart** · Informatik / Wirtschaftsinformatik
|
||||
|
||||
**Sommersemester 2026** · Sitzung 10 — Wrap-up & Projektsupport
|
||||
|
||||
[https://librete.ch/dhbw/](https://librete.ch/dhbw/)
|
||||
|
||||
---
|
||||
|
||||
<!-- _header: '' -->
|
||||
<!-- _footer: '' -->
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Best Practices
|
||||
|
||||
## Wrap-up & Projektsupport
|
||||
|
||||
---
|
||||
|
||||
# Inhalt
|
||||
|
||||
1. Semantic Versioning
|
||||
2. Git Commit Messages (Udacity Style)
|
||||
3. 12 Factor App
|
||||
4. Make it Work · Make it Right · Make it Fast
|
||||
5. Präsentations-Tipps
|
||||
6. Feedback (Sandwich-Methode)
|
||||
7. Prüfungsleistung Recap
|
||||
|
||||
---
|
||||
|
||||
# Semantic Versioning (SemVer 2.0.0)
|
||||
|
||||
```
|
||||
MAJOR.MINOR.PATCH
|
||||
│ │ └─ Bugfix, abwärtskompatibel
|
||||
│ └────── Neue Funktion, abwärtskompatibel
|
||||
└──────────── Breaking Change
|
||||
```
|
||||
|
||||
**Beispiele:**
|
||||
- `1.4.2` → `1.4.3` Bugfix
|
||||
- `1.4.3` → `1.5.0` neues Feature
|
||||
- `1.5.0` → `2.0.0` Breaking Change
|
||||
|
||||
Pre-Release: `1.0.0-alpha.1`, `1.0.0-rc.2`
|
||||
|
||||
→ https://semver.org/
|
||||
|
||||
---
|
||||
|
||||
# package.json: Versionsranges
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"express": "^4.19.2", // ≥4.19.2 < 5.0.0 (gleiche MAJOR)
|
||||
"lodash": "~4.17.21", // ≥4.17.21 < 4.18.0 (gleiche MINOR)
|
||||
"react": "19.0.0" // exakt
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`package-lock.json` pinnt die **tatsächlich installierten** Versionen.
|
||||
|
||||
---
|
||||
|
||||
# Git Commit Messages – Udacity Style
|
||||
|
||||
```
|
||||
<type>: <Subject>
|
||||
<blank>
|
||||
<Body>
|
||||
<blank>
|
||||
<Footer>
|
||||
```
|
||||
|
||||
**Type:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
|
||||
|
||||
**Subject:** Imperativ, ≤50 Zeichen, klein, kein Punkt
|
||||
**Body:** **Was** und **Warum** – nicht **Wie**
|
||||
**Footer:** Issue-Refs, Breaking Changes
|
||||
|
||||
---
|
||||
|
||||
# Commit-Beispiele
|
||||
|
||||
**Gut:**
|
||||
```
|
||||
feat: add user logout endpoint
|
||||
|
||||
Allows authenticated clients to invalidate their session token
|
||||
on the server side instead of only deleting the cookie.
|
||||
|
||||
Closes #142
|
||||
```
|
||||
|
||||
**Schlecht:**
|
||||
```
|
||||
fixed stuff
|
||||
WIP
|
||||
update
|
||||
asdf
|
||||
```
|
||||
|
||||
→ https://udacity.github.io/git-styleguide/
|
||||
|
||||
---
|
||||
|
||||
# Conventional Commits
|
||||
|
||||
```
|
||||
feat(auth): add OAuth2 login
|
||||
fix(api): return 404 instead of 500 on missing user
|
||||
docs: update README install steps
|
||||
chore(deps): bump express from 4.18 to 4.19
|
||||
BREAKING CHANGE: drop Node 16 support
|
||||
```
|
||||
|
||||
Vorteile: automatische Changelogs, automatische Version-Bumps (`semantic-release`).
|
||||
|
||||
---
|
||||
|
||||
# 12 Factor App
|
||||
|
||||
Methodik für moderne, skalierbare SaaS-Apps – https://12factor.net/
|
||||
|
||||
| # | Faktor |
|
||||
|---|--------|
|
||||
| 1 | Codebase – ein Repo, viele Deploys |
|
||||
| 2 | Dependencies – explizit deklariert (`package.json`) |
|
||||
| 3 | Config – via Environment-Variablen |
|
||||
| 4 | Backing Services – als Attached Resources |
|
||||
| 5 | Build, Release, Run – getrennt |
|
||||
| 6 | Processes – stateless |
|
||||
|
||||
---
|
||||
|
||||
# 12 Factor (2/2)
|
||||
|
||||
| # | Faktor |
|
||||
|---|--------|
|
||||
| 7 | Port Binding – self-contained, exportiert HTTP |
|
||||
| 8 | Concurrency – horizontal skalieren via Prozesse |
|
||||
| 9 | Disposability – schneller Start, Graceful Shutdown |
|
||||
| 10 | Dev/Prod Parity – möglichst gleiche Umgebung |
|
||||
| 11 | Logs – als Event-Streams (`stdout`) |
|
||||
| 12 | Admin Processes – als One-Off |
|
||||
|
||||
---
|
||||
|
||||
# Config via ENV
|
||||
|
||||
```javascript
|
||||
// schlecht
|
||||
const db = "postgres://user:secret@prod-db:5432/app";
|
||||
|
||||
// gut
|
||||
const db = process.env.DATABASE_URL;
|
||||
```
|
||||
|
||||
```bash
|
||||
# .env (NICHT committen!)
|
||||
DATABASE_URL=postgres://user:secret@db:5432/app
|
||||
NODE_ENV=production
|
||||
PORT=8080
|
||||
```
|
||||
|
||||
`.env.example` mit Dummy-Werten committen.
|
||||
|
||||
---
|
||||
|
||||
# Make It Work · Right · Fast
|
||||
|
||||
```
|
||||
1. Make It Work → MVP, hässlich aber funktional
|
||||
2. Make It Right → Refactor, Tests, Clean Code
|
||||
3. Make It Fast → Profiling, Optimierung
|
||||
```
|
||||
|
||||
Don't optimize what doesn't work.
|
||||
Don't refactor what isn't working yet.
|
||||
|
||||
→ https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast
|
||||
|
||||
---
|
||||
|
||||
# KISS · DRY · YAGNI · SOLID
|
||||
|
||||
- **KISS** – Keep It Simple, Stupid
|
||||
- **DRY** – Don't Repeat Yourself
|
||||
- **YAGNI** – You Aren't Gonna Need It
|
||||
- **SOLID**:
|
||||
- **S**ingle Responsibility
|
||||
- **O**pen/Closed
|
||||
- **L**iskov Substitution
|
||||
- **I**nterface Segregation
|
||||
- **D**ependency Inversion
|
||||
|
||||
→ Faustregeln, keine Dogmen.
|
||||
|
||||
---
|
||||
|
||||
# Präsentation – Aufbau
|
||||
|
||||
- **Einstieg** mit aktuellem Bezug (optional)
|
||||
- **Problemdefinition**: welches Bedürfnis / welches Problem?
|
||||
- Was hat **blockiert**, wie gelöst?
|
||||
- **Geschichte mit Spannungsbogen**
|
||||
- Vorher klären: Fragen während oder nach der Präsentation?
|
||||
|
||||
---
|
||||
|
||||
# Präsentation – Pitfalls
|
||||
|
||||
- Am Thema vorbeireden
|
||||
- Folien vorlesen
|
||||
- Live-Demo ohne Backup-Video
|
||||
- "Sieht man hier nicht so gut, aber..."
|
||||
- Zu viel Text pro Folie
|
||||
- Keine klare Botschaft pro Slide
|
||||
|
||||
→ **1 Folie = 1 Aussage.**
|
||||
|
||||
---
|
||||
|
||||
# Feedback – Sandwich-Methode
|
||||
|
||||
1. **Eröffnung** — Lob / positive Beobachtungen
|
||||
2. **Kern** — Kritik + konkreter Verbesserungsvorschlag
|
||||
3. **Abschluss** — Lob / wertschätzender Ausklang
|
||||
|
||||
**Pitfalls:**
|
||||
- Am Thema vorbeireden
|
||||
- Bei Unklarheiten keine Fragen stellen
|
||||
- Subjektives Feedback ("Farbe gefällt mir nicht")
|
||||
|
||||
→ **Konkret, sachlich, umsetzbar.**
|
||||
|
||||
---
|
||||
|
||||
# Prüfungsleistung – Recap
|
||||
|
||||
**Grundanforderungen (75 Punkte):**
|
||||
|
||||
| Punkte | Kriterium |
|
||||
|--------|-----------|
|
||||
| 20 | Idee, Konzeption, Planung |
|
||||
| 5 | Plattformunabhängigkeit |
|
||||
| 25 | Clean Code (KISS, SOLID, DI, Testing, Error Handling) |
|
||||
| 15 | Präsentation |
|
||||
| 10 | Dokumentation (README, etc.) |
|
||||
|
||||
**Zusatzpunkte (max. 10):** TypeScript, Docker, Dev/Prod-Parity, `.env`, npm-Publish, Domain, HTTPS, Responsive Design.
|
||||
|
||||
→ https://librete.ch/DHBW/pruefungsleistung
|
||||
|
||||
---
|
||||
|
||||
# Abgabe + Termine
|
||||
|
||||
- **Code-Upload:** bis 27.07.
|
||||
- **Präsentation:** 17.07. (in Gruppen, ~10 Min)
|
||||
- Pitch (~1 Min)
|
||||
- Agenda
|
||||
- Idee / Problem
|
||||
- Learnings & Pitfalls
|
||||
- "It is what it is" – Doku
|
||||
|
||||
---
|
||||
|
||||
# 70 Things I Regret as a Developer (Auswahl)
|
||||
|
||||
- Nicht früher Tests geschrieben zu haben
|
||||
- Zu viel Zeit mit "perfektem" Setup statt Code
|
||||
- Nicht öfter `git commit`
|
||||
- Nicht früher in Pull Requests reviewt
|
||||
- Keine Doku gepflegt
|
||||
- Eigene Lösungen für Standard-Probleme
|
||||
- Frameworks gehyped, Grundlagen vernachlässigt
|
||||
- "Funktioniert bei mir" akzeptiert
|
||||
|
||||
→ Lehre: **Disziplin > Talent.**
|
||||
|
||||
---
|
||||
|
||||
# Weiterlesen
|
||||
|
||||
- https://12factor.net/
|
||||
- https://semver.org/
|
||||
- https://udacity.github.io/git-styleguide/
|
||||
- https://www.conventionalcommits.org/
|
||||
- https://refactoring.guru/
|
||||
- https://martinfowler.com/
|
||||
- MDN Web Docs · web.dev · web.dev/learn
|
||||
|
||||
---
|
||||
|
||||
<!-- _class: invert -->
|
||||
<!-- _backgroundColor: #000 -->
|
||||
|
||||
# Viel Erfolg mit den Projekten!
|
||||
|
||||
Fragen? Probleme? → Telegram, Mail, Sprechstunde.
|
||||
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
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>
|
||||
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
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>
|
||||
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 29 KiB |