342 lines
14 KiB
TeX
342 lines
14 KiB
TeX
% ============================================
|
||
% 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}
|