Update: Aktuelle Änderungen
Deploy Portfolio / deploy (push) Successful in 26s

This commit is contained in:
2026-05-10 13:05:00 +02:00
parent 6f9e92c55f
commit 4b2300234c
37 changed files with 814 additions and 80 deletions
+341
View File
@@ -0,0 +1,341 @@
% ============================================
% STEP 02: STYLING MIT TAILWIND & URBANIST
% ============================================
\section{Styling mit Tailwind CSS und Urbanist-Schriftart}
\label{sec:step02}
In diesem Schritt gestalten wir unsere Portfolio-Seite mit Tailwind CSS, binden eine eigene Schriftart ein und erstellen einen benutzerdefinierten Scrollbalken. Jede Zeile Code wird ausführlich erklärt.
\subsection{Schriftart Urbanist installieren}
Urbanist ist eine moderne, serifenlose Schriftart von Google Fonts mit 18 verschiedene Schnitten (Thin bis Black, jeweils normal und italic). Wir verwenden fünf Grundschnitte.
\textbf{Schritt 1: Schriftart-Dateien ins Projekt kopieren}
\begin{lstlisting}[language=Bash, caption={Schriftart-Dateien ins Public-Verzeichnis kopieren}]
cd ~/projects/portfolio
cp -r Urbanist apps/web/public/Urbanist
\end{lstlisting}
\textbf{Warum \texttt{public/}?}
\begin{itemize}
\item Alles im \texttt{public/}-Ordner wird von Vite unverändert übernommen
\item Dateien sind unter \texttt{/Urbanist/static/...} im Browser erreichbar
\item Kein Import nötig direkter Pfad in CSS
\end{itemize}
\subsection{Index.css Das Herzstück des Stylings}
Die Datei \texttt{apps/web/src/index.css} vereint Tailwind, Schriftarten und Custom-Styles. Hier jede Zeile im Detail erklärt.
\subsubsection{Tailwind Import}
\begin{lstlisting}[language=CSS, caption={Tailwind CSS importieren}]
@import "tailwindcss";
\end{lstlisting}
\textbf{Erklärung:}
\begin{itemize}
\item \texttt{@import} CSS-Regel zum Importieren anderer Stylesheets
\item \texttt{"tailwindcss"} Lädt alle Tailwind-Basis-Styles, Komponenten und Utilities
\item In Tailwind 4 \textbf{kein} \texttt{@tailwind base/components/utilities} mehr!
\item Diese einzige Zeile ersetzt Hunderte von handgeschriebenen CSS-Zeilen
\end{itemize}
\subsubsection{Schriftart-Definitionen (5 Schnitte)}
\begin{lstlisting}[language=CSS, caption={Urbanist Regular (400)}]
@font-face {
font-family: "Urbanist";
src: url("/Urbanist/static/Urbanist-Regular.ttf") format("truetype");
font-weight: 400;
font-style: normal;
font-display: swap;
}
\end{lstlisting}
\textbf{Zeile für Zeile:}
\begin{itemize}
\item \texttt{@font-face} CSS-At-Regel zum Definieren einer eigenen Schriftart
\item \texttt{font-family: "Urbanist"} Der Name, unter dem wir die Schrift später verwenden (z.B. in \texttt{font-family: var(--font-display)})
\item \texttt{src: url("/Urbanist/...")} Pfad zur Schriftdatei. Beginnt mit \texttt{/}, ist also relativ zur Domain-Root
\item \texttt{format("truetype")} Sagt dem Browser, welches Format die Datei hat (.ttf = TrueType)
\item \texttt{font-weight: 400} Diesen Schnitt für normale Textstärke verwenden (400 = Regular)
\item \texttt{font-style: normal} Kein Kursiv (italic wäre \texttt{font-style: italic})
\item \texttt{font-display: swap} WICHTIG! Zeigt sofort Text in einer Ersatzschrift an und tauscht sie aus, sobald Urbanist geladen ist. Verhindert "Flash of Invisible Text" (FOIT)
\end{itemize}
\textbf{Die 5 definierten Schnitte:}
\begin{enumerate}
\item \texttt{Urbanist-Light.ttf} Gewicht 300 (dünn, für Fließtext)
\item \texttt{Urbanist-Regular.ttf} Gewicht 400 (normal, Standard)
\item \texttt{Urbanist-Medium.ttf} Gewicht 500 (halbfett)
\item \texttt{Urbanist-SemiBold.ttf} Gewicht 600 (fast fett)
\item \texttt{Urbanist-Bold.ttf} Gewicht 700 (fett, für Überschriften)
\end{enumerate}
\subsubsection{Tailwind Theme}
\begin{lstlisting}[language=CSS, caption={Tailwind Custom Theme}]
@theme {
--font-display: "Urbanist", sans-serif;
--breakpoint-3xl: 1920px;
--color-primary: #8dff69;
}
\end{lstlisting}
\textbf{Zeile für Zeile:}
\begin{itemize}
\item \texttt{@theme} Tailwind 4 Direktive zum Erweitern/Überschreiben des Standard-Themes
\item \texttt{--font-display: "Urbanist", sans-serif} Definiert eine neue Schrift-Klasse \texttt{font-display}. \texttt{sans-serif} ist der Fallback, falls Urbanist nicht lädt
\item \texttt{--breakpoint-3xl: 1920px} Neuer Breakpoint für sehr große Bildschirme. Nutzbar als \texttt{3xl:text-7xl}
\item \texttt{--color-primary: \#8dff69} Definiert eine neue Farbe \texttt{primary} (helles Grün). Nutzbar als \texttt{text-primary}, \texttt{bg-primary}, \texttt{border-primary}, etc.
\end{itemize}
\textbf{Verwendung der Custom-Properties in Tailwind:}
\begin{itemize}
\item \texttt{font-display} in \texttt{font-family: var(--font-display)}
\item \texttt{3xl} als Breakpoint: \texttt{3xl:grid-cols-4}
\item \texttt{primary} als Farbe: \texttt{text-primary}, \texttt{bg-primary/50}, \texttt{border-primary}
\end{itemize}
\subsubsection{Base-Layer (globale Stile)}
\begin{lstlisting}[language=CSS, caption={Globale Stile}]
@layer base {
html {
font-family: var(--font-display);
scroll-behavior: smooth;
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #000000;
color: #ffffff;
overflow-x: hidden;
}
}
\end{lstlisting}
\textbf{Zeile für Zeile:}
\begin{itemize}
\item \texttt{@layer base} Tailwinds "base"-Layer. Styles hier haben niedrigste Spezifität und können von Utilities überschrieben werden
\item \texttt{font-family: var(--font-display)} Setzt Urbanist als Standardschrift für die gesamte Seite
\item \texttt{scroll-behavior: smooth} Sanftes Scrollen bei Ankerlinks (z.B. \texttt{\#projekte})
\item \texttt{-webkit-font-smoothing: antialiased} Glättet Schrift auf macOS/iOS (WebKit-Browser)
\item \texttt{-moz-osx-font-smoothing: grayscale} Gleiches für Firefox auf macOS
\item \texttt{background-color: \#000000} Schwarzer Hintergrund
\item \texttt{color: \#ffffff} Weiße Schrift als Standard
\item \texttt{overflow-x: hidden} Verhindert horizontales Scrollen (wichtig für Animationen, die über den Rand hinausgehen)
\end{itemize}
\subsubsection{Custom Scrollbar (WebKit)}
\begin{lstlisting}[language=CSS, caption={Scrollbar für Chrome, Edge, Safari}]
::-webkit-scrollbar {
width: 16px;
}
::-webkit-scrollbar-track {
background: #1a1a1a;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #8dff69, #6fe047);
border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #a8ff8d, #5bc92e);
}
\end{lstlisting}
\textbf{Zeile für Zeile:}
\begin{itemize}
\item \texttt{::-webkit-scrollbar} Pseudo-Element für die gesamte Scrollbar (Breite, Hintergrund)
\item \texttt{width: 16px} Breite der Scrollbar. Größer = dickerer Balken
\item \texttt{::-webkit-scrollbar-track} Der "Hintergrund" der Scrollbar (die Schiene)
\item \texttt{background: \#1a1a1a} Dunkelgrauer Track (fast schwarz)
\item \texttt{::-webkit-scrollbar-thumb} Der bewegliche "Griff" der Scrollbar
\item \texttt{linear-gradient(180deg, \#8dff69, \#6fe047)} Farbverlauf von hellgrün (oben) nach dunkelgrün (unten). \texttt{180deg} = von oben nach unten
\item \texttt{border-radius: 8px} Abgerundete Ecken am Griff
\item \texttt{::-webkit-scrollbar-thumb:hover} Wenn die Maus über dem Griff ist
\item \texttt{\#a8ff8d, \#5bc92e} Hellere Grüntöne beim Hover für visuelles Feedback
\end{itemize}
\subsubsection{Firefox Scrollbar}
\begin{lstlisting}[language=CSS, caption={Scrollbar für Firefox}]
* {
scrollbar-width: auto;
scrollbar-color: #6fe047 #1a1a1a;
}
\end{lstlisting}
\textbf{Erklärung:}
\begin{itemize}
\item Firefox unterstützt \texttt{::-webkit-scrollbar} \textbf{nicht}
\item \texttt{scrollbar-width: auto} Normale Breite (Alternative: \texttt{thin})
\item \texttt{scrollbar-color: GRIFF SCHIENE} Erste Farbe = Griff, zweite Farbe = Schiene
\end{itemize}
\subsubsection{Hilfsklasse: Scrollbar ausblenden}
\begin{lstlisting}[language=CSS, caption={Hilfsklasse für Karussells}]
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
\end{lstlisting}
Für horizontale Karussells der Inhalt scrollt, aber die Scrollbar ist unsichtbar.
\subsection{App.tsx Die React-Komponente erklärt}
\begin{lstlisting}[language=TypeScript, caption={Vollständige App.tsx mit Zeilen-Erklärung}]
function App() {
return (
// Äußerster Container
// min-h-screen: mindestens Bildschirmhöhe (wichtig für zentrierte Inhalte)
// bg-black: schwarzer Hintergrund
// text-white: weiße Schrift als Basis
// p-8: 32px Innenabstand auf allen Seiten
<div className="min-h-screen bg-black text-white p-8">
{/* Zentrierte Content-Box */}
{/* text-center: zentriert Text und Inline-Elemente */}
{/* max-w-2xl: maximale Breite 672px */}
{/* mx-auto: automatischer horizontaler Margin = zentriert die Box */}
{/* pt-20: 80px Abstand nach oben (Padding-Top) */}
<div className="text-center max-w-2xl mx-auto pt-20">
{/* Kategorie-Badge: Kleine Beschriftung ÜBER der Überschrift */}
{/* text-primary: verwendet unsere Custom-Farbe #8dff69 */}
{/* text-sm: kleinere Schriftgröße (14px) */}
{/* font-medium: halbfette Schrift (Gewicht 500) */}
{/* tracking-widest: sehr breite Buchstabenabstände */}
{/* uppercase: GROSSBUCHSTABEN */}
<span className="text-primary text-sm font-medium tracking-widest uppercase">
Full-Stack Entwickler
</span>
{/* Hauptüberschrift: Dein Name */}
{/* text-7xl: sehr große Schrift (72px) */}
{/* font-bold: fette Schrift (Gewicht 700) */}
{/* mt-4: 16px Abstand nach oben */}
{/* mb-6: 24px Abstand nach unten */}
{/* leading-tight: enger Zeilenabstand (1.25) */}
<h1 className="text-7xl font-bold mt-4 mb-6 leading-tight">
Robert Bretz
</h1>
{/* Beschreibungstext */}
{/* text-xl: etwas größerer Text (20px) */}
{/* text-gray-400: graue Schrift (gedämpfter als weiß) */}
{/* max-w-lg: maximale Breite 512px (schmaler = besser lesbar) */}
{/* leading-relaxed: lockerer Zeilenabstand (1.625) */}
<p className="text-xl text-gray-400 max-w-lg mx-auto leading-relaxed">
Ich baue moderne Webanwendungen mit React, .NET und Docker.
Minimalistisch, schnell und zuverlässig.
</p>
{/* Button-Gruppe */}
{/* mt-10: 40px Abstand nach oben */}
{/* flex: Flexbox-Layout */}
{/* gap-4: 16px Abstand zwischen den Buttons */}
{/* justify-center: Buttons horizontal zentrieren */}
<div className="mt-10 flex gap-4 justify-center">
{/* Button "Projekte" */}
{/* px-6: 24px horizontaler Innenabstand */}
{/* py-3: 12px vertikaler Innenabstand */}
{/* bg-primary: Hintergrund in Custom-Farbe #8dff69 */}
{/* text-black: schwarze Schrift auf grünem Grund */}
{/* font-semibold: halbfette Schrift (Gewicht 600) */}
{/* rounded-full: komplett runde Ecken (Pillenform) */}
{/* hover:opacity-80: beim Hover auf 80% Deckkraft */}
{/* transition: sanfter Übergang */}
<a href="#" className="px-6 py-3 bg-primary text-black font-semibold rounded-full hover:opacity-80 transition">
Projekte
</a>
{/* Button "Kontakt" */}
{/* border border-gray-700: dunkelgrauer Rahmen statt Hintergrund */}
{/* text-gray-300: hellgraue Schrift */}
{/* hover:border-primary: Rahmen wird grün beim Hover */}
{/* hover:text-primary: Schrift wird grün beim Hover */}
<a href="#" className="px-6 py-3 border border-gray-700 text-gray-300 font-semibold rounded-full hover:border-primary hover:text-primary transition">
Kontakt
</a>
</div>
</div>
{/* SCROLL-TEST: Erzwingt Scrollen für den Custom Scrollbar */}
{/* mt-20: 80px Abstand nach oben */}
{/* space-y-8: 32px Abstand zwischen den Kind-Elementen */}
{/* max-w-2xl: gleiche maximale Breite wie oben */}
<div className="mt-20 space-y-8 max-w-2xl mx-auto">
{/* JavaScript: Erstelle 5 Test-Blöcke */}
{/* [...Array(5)]: Array mit 5 leeren Plätzen */}
{/* .map((_, i): für jedes Element, _ = ignorierter Wert, i = Index (0-4) */}
{[...Array(5)].map((_, i) => (
<div
key={i}
className="h-64 bg-gray-900 rounded-xl flex items-center justify-center text-gray-500 text-2xl"
>
Sektion {i + 1}
</div>
))}
</div>
</div>
);
}
export default App;
\end{lstlisting}
\subsection{Verwendete Tailwind-Klassen Cheat Sheet}
\begin{table}[h]
\centering
\caption{Alle verwendeten Tailwind-Klassen und ihre CSS-Entsprechung}
\begin{tabular}{@{}llp{5cm}@{}}
\toprule
\textbf{Klasse} & \textbf{CSS-Entsprechung} & \textbf{Erklärung} \\
\midrule
\texttt{min-h-screen} & \texttt{min-height: 100vh} & Mindestens Bildschirmhöhe \\
\texttt{bg-black} & \texttt{background: \#000} & Schwarzer Hintergrund \\
\texttt{text-white} & \texttt{color: \#fff} & Weiße Schrift \\
\texttt{p-8} & \texttt{padding: 2rem} & 32px Innenabstand \\
\texttt{text-center} & \texttt{text-align: center} & Text zentrieren \\
\texttt{max-w-2xl} & \texttt{max-width: 42rem} & Max 672px breit \\
\texttt{mx-auto} & \texttt{margin-left/right: auto} & Horizontal zentrieren \\
\texttt{pt-20} & \texttt{padding-top: 5rem} & 80px oben \\
\bottomrule
\end{tabular}
\end{table}
\subsection{Scrollbalken-Debugging}
\textbf{Problem: "Ich sehe den Scrollbalken nicht!"}
\textbf{Ursache:} Der Scrollbalken erscheint nur, wenn die Seite tatsächlich scrollbar ist. Bei kurzen Inhalten (nur Name + Buttons) passt alles auf eine Bildschirmseite kein Scrollbalken nötig.
\textbf{Lösungen:}
\begin{enumerate}
\item Browser-Fenster vertikal verkleinern
\item Genug Test-Inhalt einfügen (5 große Divs wie im Code oben)
\item Mit \texttt{overflow-y: scroll} einen permanenten Scrollbalken erzwingen
\end{enumerate}
\subsection{Zusammenfassung}
In diesem Schritt haben wir:
\begin{itemize}
\item Die Schriftart Urbanist mit 5 Schnitten eingebunden
\item Ein Tailwind Custom-Theme mit eigener Farbe und Breakpoint definiert
\item Globale Stile im \texttt{@layer base} gesetzt
\item Einen benutzerdefinierten Scrollbalken mit Farbverlauf erstellt
\item Jede Zeile der \texttt{App.tsx} und \texttt{index.css} ausführlich dokumentiert
\end{itemize}