From d48f1b0f212902d1dcd595f8b2b70ff3477d3c0f Mon Sep 17 00:00:00 2001 From: ghanendra-sdet Date: Mon, 5 Oct 2026 10:20:19 +0530 Subject: [PATCH 01/21] feat: add site-wide design system (dark mode, callouts, nav chrome) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a lightweight, dependency-free JS/CSS layer applied across every page via the existing head-custom.html hook, so it benefits all 8 modules immediately without touching their content: - Light/dark theme (system-aware, manual toggle, persisted via localStorage) with a consistent color-token system - Fixes a real bug: GFM [!NOTE]/[!TIP]/[!WARNING]/etc. alerts were rendering as broken, literal "[!NOTE]" text on the live Pages site (confirmed via curl) despite rendering correctly on github.com — now upgraded into styled callout boxes on both - New callout component classes (concept, important, pro-tip, mistake, try-it, challenge, remember, recovery, real-world, sdet) for content work to use going forward - Copy-to-clipboard buttons on every code block, keyboard accessible - Auto-generated, sticky table of contents on long pages - Breadcrumb navigation built from the URL path - Back-to-top button, respects prefers-reduced-motion - Mermaid diagrams re-theme automatically with light/dark mode - localStorage-only progress tracker (mark modules complete, no login) - Focus-visible outlines and reduced-motion support throughout Co-Authored-By: Claude Sonnet 5 --- _includes/head-custom.html | 55 ++--- assets/css/custom.css | 362 +++++++++++++++++++++++++++++ assets/js/custom.js | 455 +++++++++++++++++++++++++++++++++++++ 3 files changed, 836 insertions(+), 36 deletions(-) create mode 100644 assets/css/custom.css create mode 100644 assets/js/custom.js diff --git a/_includes/head-custom.html b/_includes/head-custom.html index 74e481f..2baf12b 100644 --- a/_includes/head-custom.html +++ b/_includes/head-custom.html @@ -1,38 +1,21 @@ - + + + + + + + + + + + + + + - - - + diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..b1444bf --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,362 @@ +/* ========================================================================== + GIT Tutorials — Design System + Modern + Technical + Friendly + Educational. Not a marketing site. + Light/dark tokens, callout components, nav chrome, accessibility. + ========================================================================== */ + +/* ------------------------------------------------------------------------- + 1. Color tokens — light (default) and dark + ------------------------------------------------------------------------- */ +:root { + --color-primary: #2563eb; + --color-primary-strong: #1d4ed8; + --color-success: #16a34a; + --color-success-bg: #d4edda; + --color-warning: #d97706; + --color-warning-bg: #fff3cd; + --color-danger: #dc2626; + --color-danger-bg: #ffe2e2; + --color-info: #0891b2; + --color-info-bg: #cfe2ff; + --color-purple: #7c3aed; + --color-purple-bg: #ede9fe; + + --color-bg: #ffffff; + --color-surface: #f6f8fa; + --color-surface-raised: #ffffff; + --color-text: #1f2328; + --color-muted: #57606a; + --color-border: #d0d7de; + --color-code-bg: #f6f8fa; + --color-link: #0969da; + + --radius-md: 10px; + --radius-lg: 14px; + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.08); +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --color-bg: #0d1117; + --color-surface: #161b22; + --color-surface-raised: #1c222b; + --color-text: #e6edf3; + --color-muted: #9198a1; + --color-border: #30363d; + --color-code-bg: #161b22; + --color-link: #58a6ff; + + --color-success-bg: #10351f; + --color-warning-bg: #3a2c05; + --color-danger-bg: #3a0d0d; + --color-info-bg: #0d2a3d; + --color-purple-bg: #251a40; + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); + --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.5); + } +} + +:root[data-theme="dark"] { + --color-bg: #0d1117; + --color-surface: #161b22; + --color-surface-raised: #1c222b; + --color-text: #e6edf3; + --color-muted: #9198a1; + --color-border: #30363d; + --color-code-bg: #161b22; + --color-link: #58a6ff; + + --color-success-bg: #10351f; + --color-warning-bg: #3a2c05; + --color-danger-bg: #3a0d0d; + --color-info-bg: #0d2a3d; + --color-purple-bg: #251a40; + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); + --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.5); +} + +/* ------------------------------------------------------------------------- + 2. Base — apply tokens to the minima-rendered page without fighting it + ------------------------------------------------------------------------- */ +body { + background-color: var(--color-bg); + color: var(--color-text); + transition: background-color 0.15s ease, color 0.15s ease; +} + +.page-content, .wrapper { background-color: var(--color-bg); } + +a { color: var(--color-link); } + +pre, code { background-color: var(--color-code-bg) !important; } + +table { border-color: var(--color-border); } +table th, table td { border-color: var(--color-border) !important; color: var(--color-text); } +table th { background-color: var(--color-surface) !important; } +table tr:nth-child(2n) { background-color: var(--color-surface) !important; } + +hr { border-color: var(--color-border); } + +.site-header, .site-footer { + background-color: var(--color-surface); + border-color: var(--color-border) !important; +} +.site-title, .site-title:visited { color: var(--color-text) !important; } +.site-nav .page-link { color: var(--color-text) !important; } + +details summary { cursor: pointer; } + +/* ------------------------------------------------------------------------- + 3. Callout components + One visual language for every colored box in the course. Icon + label are + never the only signal — each type also has a distinct text label. + ------------------------------------------------------------------------- */ +.callout { + margin: 1.25em 0; + padding: 1em 1.1em; + border-radius: var(--radius-md); + border: 1px solid var(--color-border); + border-left-width: 4px; + background: var(--color-surface); + box-shadow: var(--shadow-sm); +} +.callout > :first-child { margin-top: 0; } +.callout > :last-child { margin-bottom: 0; } +.callout-label { + display: flex; + align-items: center; + gap: 0.5em; + font-weight: 700; + font-size: 0.78rem; + letter-spacing: 0.04em; + text-transform: uppercase; + margin-bottom: 0.55em; +} + +.callout-concept { border-left-color: var(--color-info); background: var(--color-info-bg); } +.callout-important { border-left-color: var(--color-danger); background: var(--color-danger-bg); } +.callout-protip { border-left-color: var(--color-primary); background: var(--color-info-bg); } +.callout-mistake { border-left-color: var(--color-warning); background: var(--color-warning-bg); } +.callout-tryit { border-left-color: var(--color-success); background: var(--color-success-bg); } +.callout-challenge { border-left-color: var(--color-purple); background: var(--color-purple-bg); } +.callout-remember { border-left-color: var(--color-purple); background: var(--color-purple-bg); } +.callout-recovery { border-left-color: var(--color-success); background: var(--color-success-bg); } +.callout-realworld { border-left-color: var(--color-muted); background: var(--color-surface); } +.callout-sdet { border-left-color: var(--color-primary); background: var(--color-info-bg); } + +/* Upgraded native GitHub alerts ([!NOTE] etc.) — same visual family */ +.gfm-alert { border-left-width: 4px; } +.gfm-alert-note { border-left-color: var(--color-info); background: var(--color-info-bg); } +.gfm-alert-tip { border-left-color: var(--color-success); background: var(--color-success-bg); } +.gfm-alert-important { border-left-color: var(--color-purple); background: var(--color-purple-bg); } +.gfm-alert-warning { border-left-color: var(--color-warning); background: var(--color-warning-bg); } +.gfm-alert-caution { border-left-color: var(--color-danger); background: var(--color-danger-bg); } + +/* ------------------------------------------------------------------------- + 4. Code blocks — copy button + ------------------------------------------------------------------------- */ +.code-block-wrap { position: relative; } +.copy-btn { + position: absolute; + top: 0.5em; + right: 0.5em; + z-index: 2; + font-size: 0.72rem; + line-height: 1; + padding: 0.35em 0.6em; + border-radius: 6px; + border: 1px solid var(--color-border); + background: var(--color-surface-raised); + color: var(--color-muted); + cursor: pointer; + opacity: 0; + transition: opacity 0.12s ease, color 0.12s ease, border-color 0.12s ease; +} +.code-block-wrap:hover .copy-btn, +.code-block-wrap:focus-within .copy-btn, +.copy-btn:focus-visible { + opacity: 1; +} +.copy-btn:hover { color: var(--color-primary); border-color: var(--color-primary); } +.copy-btn[data-copied="true"] { color: var(--color-success); border-color: var(--color-success); opacity: 1; } + +@media (max-width: 600px) { + /* Touch devices have no hover — keep the button always visible */ + .copy-btn { opacity: 1; } +} + +/* ------------------------------------------------------------------------- + 5. Destructive-command warning badge + ------------------------------------------------------------------------- */ +.danger-badge { + display: inline-flex; + align-items: center; + gap: 0.4em; + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.03em; + text-transform: uppercase; + color: #fff; + background: var(--color-danger); + padding: 0.25em 0.6em; + border-radius: 999px; + margin-bottom: 0.5em; +} + +/* ------------------------------------------------------------------------- + 6. Site chrome — theme toggle, back-to-top, breadcrumbs, TOC + ------------------------------------------------------------------------- */ +.chrome-btn { + position: fixed; + z-index: 50; + width: 42px; + height: 42px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 999px; + border: 1px solid var(--color-border); + background: var(--color-surface-raised); + color: var(--color-text); + box-shadow: var(--shadow-md); + cursor: pointer; + font-size: 1.05rem; + transition: transform 0.15s ease, opacity 0.15s ease; +} +.chrome-btn:hover { transform: translateY(-2px); } + +#theme-toggle-btn { bottom: 1.25rem; right: 1.25rem; } + +#back-to-top-btn { + bottom: 1.25rem; + right: 4.25rem; + opacity: 0; + pointer-events: none; + transform: translateY(8px); +} +#back-to-top-btn.visible { + opacity: 1; + pointer-events: auto; + transform: translateY(0); +} + +.breadcrumb-nav { + font-size: 0.85rem; + color: var(--color-muted); + margin: 0 0 1.25em 0; + display: flex; + flex-wrap: wrap; + gap: 0.35em; + align-items: center; +} +.breadcrumb-nav a { color: var(--color-muted); text-decoration: none; } +.breadcrumb-nav a:hover { color: var(--color-link); text-decoration: underline; } +.breadcrumb-nav .sep { opacity: 0.6; } +.breadcrumb-nav .current { color: var(--color-text); font-weight: 600; } + +.toc-box { + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + padding: 0.9em 1.1em; + margin: 1.5em 0; + font-size: 0.88rem; +} +.toc-box summary { + font-weight: 700; + font-size: 0.78rem; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--color-muted); +} +.toc-box ul { margin: 0.75em 0 0 0; padding-left: 1.1em; } +.toc-box li { margin: 0.3em 0; } +.toc-box a { color: var(--color-text); text-decoration: none; } +.toc-box a:hover { color: var(--color-link); text-decoration: underline; } +.toc-box a.active { color: var(--color-primary); font-weight: 600; } + +@media (min-width: 1100px) { + .toc-box[data-sticky="true"] { + position: sticky; + top: 1.5rem; + max-height: calc(100vh - 3rem); + overflow-y: auto; + } +} + +/* ------------------------------------------------------------------------- + 7. Progress tracker (home page) + ------------------------------------------------------------------------- */ +.progress-tracker { margin: 1.5em 0; } +.progress-row { + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + gap: 0.6em; + margin: 0.5em 0; + font-size: 0.85rem; +} +.progress-row .progress-label { display: flex; align-items: center; gap: 0.5em; } +.progress-row .progress-label a { color: var(--color-text); text-decoration: none; } +.progress-row .progress-label a:hover { text-decoration: underline; } +.progress-track { + grid-column: 1 / -1; + height: 8px; + border-radius: 999px; + background: var(--color-border); + overflow: hidden; +} +.progress-fill { + height: 100%; + background: var(--color-success); + border-radius: 999px; + transition: width 0.25s ease; +} +.progress-check { color: var(--color-success); font-weight: 700; } +.progress-reset-btn { + font-size: 0.75rem; + color: var(--color-muted); + background: none; + border: 1px solid var(--color-border); + border-radius: 6px; + padding: 0.3em 0.7em; + cursor: pointer; +} +.progress-reset-btn:hover { color: var(--color-danger); border-color: var(--color-danger); } + +/* ------------------------------------------------------------------------- + 8. Accessibility + ------------------------------------------------------------------------- */ +a:focus-visible, +button:focus-visible, +summary:focus-visible, +input:focus-visible { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + +@media (prefers-reduced-motion: reduce) { + * { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; } +} + +.visually-hidden { + position: absolute; + width: 1px; height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; +} + +/* ------------------------------------------------------------------------- + 9. Mobile + ------------------------------------------------------------------------- */ +@media (max-width: 600px) { + #back-to-top-btn { right: 1.25rem; bottom: 4.25rem; } + .chrome-btn { width: 38px; height: 38px; font-size: 0.95rem; } +} + +/* Mermaid diagrams readable in dark mode (mermaid itself is re-initialized + with the matching theme by custom.js — this just prevents a white flash + while it loads) */ +pre.mermaid { background: transparent; } diff --git a/assets/js/custom.js b/assets/js/custom.js new file mode 100644 index 0000000..f62c972 --- /dev/null +++ b/assets/js/custom.js @@ -0,0 +1,455 @@ +/* ========================================================================== + GIT Tutorials — site-wide progressive enhancement + Pure JS, no build step, no dependencies beyond the Mermaid CDN script + already loaded by head-custom.html. Everything here degrades gracefully: + if JS fails, the page is still a complete, readable Markdown page. + ========================================================================== */ +(function () { + "use strict"; + + var prefersReducedMotion = window.matchMedia && + window.matchMedia("(prefers-reduced-motion: reduce)").matches; + + /* ======================================================================== + 1. THEME — light / dark / system, persisted, manual toggle + ======================================================================== */ + var ThemeManager = { + STORAGE_KEY: "git-tutorials-theme", + + getStored: function () { + try { return localStorage.getItem(this.STORAGE_KEY); } catch (e) { return null; } + }, + setStored: function (value) { + try { localStorage.setItem(this.STORAGE_KEY, value); } catch (e) { /* private mode etc. */ } + }, + apply: function (theme) { + // theme is "light", "dark", or null (= follow system) + if (theme === "light" || theme === "dark") { + document.documentElement.setAttribute("data-theme", theme); + } else { + document.documentElement.removeAttribute("data-theme"); + } + this.updateButton(); + this.reinitMermaid(); + }, + effectiveTheme: function () { + var stored = this.getStored(); + if (stored === "light" || stored === "dark") return stored; + return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" : "light"; + }, + cycle: function () { + var current = this.getStored(); + // light -> dark -> system(null) -> light ... + var next = current === "light" ? "dark" : current === "dark" ? null : "light"; + if (next === null) { try { localStorage.removeItem(this.STORAGE_KEY); } catch (e) {} } + else { this.setStored(next); } + this.apply(next); + }, + updateButton: function () { + var btn = document.getElementById("theme-toggle-btn"); + if (!btn) return; + var stored = this.getStored(); + var label = stored === "light" ? "Light (click for dark)" + : stored === "dark" ? "Dark (click for system)" + : "System theme (click for light)"; + var icon = stored === "light" ? "☀️" : stored === "dark" ? "🌙" : "🖥️"; + btn.textContent = icon; + btn.setAttribute("aria-label", "Theme: " + label); + btn.title = "Theme: " + label; + }, + reinitMermaid: function () { + if (!window.mermaid) return; + var isDark = this.effectiveTheme() === "dark"; + try { + window.mermaid.initialize({ startOnLoad: false, theme: isDark ? "dark" : "default" }); + document.querySelectorAll("pre.mermaid[data-processed]").forEach(function (el) { + el.removeAttribute("data-processed"); + }); + window.mermaid.run ? window.mermaid.run() : window.mermaid.init(undefined, document.querySelectorAll("pre.mermaid")); + } catch (e) { /* older mermaid API shape — ignore, initial render already happened */ } + }, + init: function () { + this.apply(this.getStored()); + var btn = document.createElement("button"); + btn.id = "theme-toggle-btn"; + btn.type = "button"; + btn.className = "chrome-btn"; + document.body.appendChild(btn); + btn.addEventListener("click", this.cycle.bind(this)); + this.updateButton(); + } + }; + + /* ======================================================================== + 2. GFM ALERT UPGRADE — fix [!NOTE]/[!TIP]/etc. rendering on Pages + kramdown renders these as a plain

[!TYPE]\ntext

. + GitHub.com renders them with icons natively; this makes the Pages + site match that instead of showing the literal "[!TYPE]" text. + ======================================================================== */ + var ALERT_META = { + note: { icon: "ℹ️", label: "Note" }, + tip: { icon: "💡", label: "Tip" }, + important: { icon: "❗", label: "Important" }, + warning: { icon: "⚠️", label: "Warning" }, + caution: { icon: "🚨", label: "Caution" } + }; + + function upgradeGfmAlerts() { + document.querySelectorAll("blockquote").forEach(function (bq) { + var firstP = bq.querySelector("p:first-of-type"); + if (!firstP) return; + var html = firstP.innerHTML; + var match = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*\s*/i.exec(html) || + /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*\n?/i.exec(html); + if (!match) return; + var type = match[1].toLowerCase(); + firstP.innerHTML = html.slice(match[0].length); + + var meta = ALERT_META[type]; + var wrapper = document.createElement("div"); + wrapper.className = "callout gfm-alert gfm-alert-" + type; + var labelEl = document.createElement("p"); + labelEl.className = "callout-label"; + labelEl.innerHTML = ' " + meta.label; + wrapper.appendChild(labelEl); + while (bq.firstChild) wrapper.appendChild(bq.firstChild); + bq.replaceWith(wrapper); + }); + } + + /* ======================================================================== + 3. COPY BUTTONS on fenced code blocks (skips Mermaid source blocks) + ======================================================================== */ + function addCopyButtons() { + document.querySelectorAll("div.highlighter-rouge, figure.highlight, pre").forEach(function (block) { + // Normalize to the actual
 we want a button on.
+      var pre = block.tagName === "PRE" ? block : block.querySelector("pre");
+      if (!pre || pre.classList.contains("mermaid") || pre.dataset.copyWired) return;
+      var codeEl = pre.querySelector("code");
+      var text = (codeEl || pre).textContent;
+      if (!text || !text.trim()) return;
+      pre.dataset.copyWired = "true";
+
+      var wrap = document.createElement("div");
+      wrap.className = "code-block-wrap";
+      pre.parentNode.insertBefore(wrap, pre);
+      wrap.appendChild(pre);
+
+      var btn = document.createElement("button");
+      btn.type = "button";
+      btn.className = "copy-btn";
+      btn.textContent = "Copy";
+      btn.setAttribute("aria-label", "Copy code to clipboard");
+      wrap.appendChild(btn);
+
+      btn.addEventListener("click", function () {
+        var doCopy = function () {
+          btn.dataset.copied = "true";
+          btn.textContent = "Copied!";
+          setTimeout(function () {
+            btn.textContent = "Copy";
+            delete btn.dataset.copied;
+          }, 1500);
+        };
+        if (navigator.clipboard && navigator.clipboard.writeText) {
+          navigator.clipboard.writeText(text).then(doCopy, doCopy);
+        } else {
+          var ta = document.createElement("textarea");
+          ta.value = text;
+          ta.style.position = "fixed";
+          ta.style.opacity = "0";
+          document.body.appendChild(ta);
+          ta.select();
+          try { document.execCommand("copy"); } catch (e) {}
+          document.body.removeChild(ta);
+          doCopy();
+        }
+      });
+    });
+  }
+
+  /* ========================================================================
+     4. BACK TO TOP
+     ======================================================================== */
+  function initBackToTop() {
+    var btn = document.createElement("button");
+    btn.id = "back-to-top-btn";
+    btn.type = "button";
+    btn.className = "chrome-btn";
+    btn.innerHTML = '';
+    btn.setAttribute("aria-label", "Back to top");
+    document.body.appendChild(btn);
+
+    window.addEventListener("scroll", function () {
+      btn.classList.toggle("visible", window.scrollY > 480);
+    }, { passive: true });
+
+    btn.addEventListener("click", function () {
+      window.scrollTo({ top: 0, behavior: prefersReducedMotion ? "auto" : "smooth" });
+      document.body.setAttribute("tabindex", "-1");
+      document.body.focus();
+      document.body.removeAttribute("tabindex");
+    });
+  }
+
+  /* ========================================================================
+     5. BREADCRUMBS — built from the URL path, e.g.
+        Git Tutorials -> Module 01 - Git Fundamentals
+     ======================================================================== */
+  function titleFromSlug(slug) {
+    var withoutNum = slug.replace(/^\d+-/, "");
+    var words = withoutNum.split("-").map(function (w) {
+      return w.charAt(0).toUpperCase() + w.slice(1);
+    });
+    var numMatch = /^(\d+)-/.exec(slug);
+    return (numMatch ? numMatch[1] + " · " : "") + words.join(" ");
+  }
+
+  function initBreadcrumbs() {
+    var h1 = document.querySelector(".page-content h1, main h1");
+    if (!h1) return;
+    var parts = window.location.pathname.split("/").filter(Boolean);
+    // parts[0] is the repo name on Pages (e.g. "git-tutorials") — drop it.
+    var segments = parts.slice(1).filter(function (p) { return p !== "index.html"; });
+    if (segments.length === 0) return; // already on the home page
+
+    var nav = document.createElement("nav");
+    nav.className = "breadcrumb-nav";
+    nav.setAttribute("aria-label", "Breadcrumb");
+
+    var home = document.createElement("a");
+    home.href = "/" + parts[0] + "/";
+    home.textContent = "🏠 Git Tutorials";
+    nav.appendChild(home);
+
+    segments.forEach(function (seg, i) {
+      var sep = document.createElement("span");
+      sep.className = "sep";
+      sep.textContent = "›";
+      nav.appendChild(sep);
+
+      var isLast = i === segments.length - 1;
+      if (isLast) {
+        var current = document.createElement("span");
+        current.className = "current";
+        current.textContent = titleFromSlug(seg);
+        nav.appendChild(current);
+      } else {
+        var link = document.createElement("a");
+        link.href = "/" + parts.slice(0, i + 2).join("/") + "/";
+        link.textContent = titleFromSlug(seg);
+        nav.appendChild(link);
+      }
+    });
+
+    h1.parentNode.insertBefore(nav, h1);
+  }
+
+  /* ========================================================================
+     6. AUTO TABLE OF CONTENTS for long pages
+     ======================================================================== */
+  function initTOC() {
+    var content = document.querySelector(".page-content") || document.querySelector("main");
+    if (!content) return;
+    var headings = Array.prototype.slice.call(content.querySelectorAll("h2, h3"));
+    if (headings.length < 4) return; // short pages don't need one
+
+    var details = document.createElement("details");
+    details.className = "toc-box";
+    details.setAttribute("data-sticky", "true");
+    details.open = true;
+    var summary = document.createElement("summary");
+    summary.textContent = "On this page";
+    details.appendChild(summary);
+
+    var list = document.createElement("ul");
+    var linkById = {};
+    headings.forEach(function (h, i) {
+      if (!h.id) h.id = "toc-heading-" + i;
+      var li = document.createElement("li");
+      if (h.tagName === "H3") li.style.marginLeft = "1em";
+      var a = document.createElement("a");
+      a.href = "#" + h.id;
+      a.textContent = h.textContent.replace(/^[^\w]*/, ""); // trim leading emoji clutter
+      li.appendChild(a);
+      list.appendChild(li);
+      linkById[h.id] = a;
+    });
+    details.appendChild(list);
+
+    var h1 = content.querySelector("h1");
+    var insertBefore = h1 && h1.nextElementSibling;
+    if (insertBefore) insertBefore.parentNode.insertBefore(details, insertBefore);
+    else content.insertBefore(details, content.firstChild);
+
+    if ("IntersectionObserver" in window) {
+      var observer = new IntersectionObserver(function (entries) {
+        entries.forEach(function (entry) {
+          var link = linkById[entry.target.id];
+          if (!link) return;
+          link.classList.toggle("active", entry.isIntersecting);
+        });
+      }, { rootMargin: "-10% 0px -70% 0px" });
+      headings.forEach(function (h) { observer.observe(h); });
+    }
+  }
+
+  /* ========================================================================
+     7. EXTERNAL LINKS open in a new tab (same-repo links stay in-tab)
+        [carried over from the previous head-custom.html inline script]
+     ======================================================================== */
+  function externalLinksNewTab() {
+    var repoBase = location.pathname.split("/").slice(0, 2).join("/");
+    document.querySelectorAll("a[href]").forEach(function (a) {
+      var href = a.getAttribute("href");
+      if (!/^https?:\/\//i.test(href)) return;
+      try {
+        var url = new URL(href, location.href);
+        var isSameRepo = url.origin === location.origin && url.pathname.indexOf(repoBase + "/") === 0;
+        if (!isSameRepo) {
+          a.setAttribute("target", "_blank");
+          a.setAttribute("rel", "noopener noreferrer");
+        }
+      } catch (e) { /* malformed href — leave it alone */ }
+    });
+  }
+
+  /* ========================================================================
+     8. MERMAID — convert kramdown's fenced-block output, then render
+     ======================================================================== */
+  function initMermaid() {
+    document.querySelectorAll("code.language-mermaid").forEach(function (codeEl) {
+      var pre = document.createElement("pre");
+      pre.className = "mermaid";
+      pre.textContent = codeEl.textContent;
+      var oldPre = codeEl.closest("pre") || codeEl;
+      oldPre.replaceWith(pre);
+    });
+    if (window.mermaid) {
+      var isDark = ThemeManager.effectiveTheme() === "dark";
+      window.mermaid.initialize({ startOnLoad: true, theme: isDark ? "dark" : "default" });
+    }
+  }
+
+  /* ========================================================================
+     9. PROGRESS TRACKER — localStorage only, no login, no server
+     ======================================================================== */
+  var MODULES = [
+    { slug: "00-introduction", title: "Introduction & Setup" },
+    { slug: "01-git-fundamentals", title: "Git Fundamentals" },
+    { slug: "02-git-and-github", title: "Git + GitHub" },
+    { slug: "03-contributing-to-open-source", title: "Contributing to Open Source" },
+    { slug: "04-undo-and-recovery", title: "Undo & Recovery" },
+    { slug: "05-advanced-git", title: "Advanced Git" },
+    { slug: "06-certification-prep", title: "Certification Prep" },
+    { slug: "07-exercises-and-study-plan", title: "Exercises & Study Plan" }
+  ];
+  var ProgressTracker = {
+    STORAGE_KEY: "git-tutorials-progress",
+    read: function () {
+      try { return JSON.parse(localStorage.getItem(this.STORAGE_KEY) || "{}"); }
+      catch (e) { return {}; }
+    },
+    write: function (data) {
+      try { localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data)); } catch (e) {}
+    },
+    markVisited: function (slug) {
+      var data = this.read();
+      if (!data[slug]) { data[slug] = "visited"; this.write(data); }
+    },
+    markComplete: function (slug, complete) {
+      var data = this.read();
+      data[slug] = complete ? "complete" : "visited";
+      this.write(data);
+    },
+    recordCurrentPage: function () {
+      var parts = location.pathname.split("/").filter(Boolean);
+      var slug = parts[1];
+      if (slug && MODULES.some(function (m) { return m.slug === slug; })) {
+        this.markVisited(slug);
+      }
+    },
+    renderWidget: function () {
+      var mount = document.getElementById("progress-tracker-mount");
+      if (!mount) return;
+      var data = this.read();
+      mount.innerHTML = "";
+      var container = document.createElement("div");
+      container.className = "progress-tracker";
+
+      var completedCount = MODULES.filter(function (m) { return data[m.slug] === "complete"; }).length;
+
+      MODULES.forEach(function (m) {
+        var state = data[m.slug] || "not-started";
+        var row = document.createElement("div");
+        row.className = "progress-row";
+
+        var label = document.createElement("div");
+        label.className = "progress-label";
+        var link = document.createElement("a");
+        link.href = "./" + m.slug + "/";
+        link.textContent = m.title;
+        label.appendChild(link);
+        if (state === "complete") {
+          var check = document.createElement("span");
+          check.className = "progress-check";
+          check.setAttribute("aria-label", "Completed");
+          check.textContent = "✓";
+          label.appendChild(check);
+        }
+        row.appendChild(label);
+
+        var toggle = document.createElement("button");
+        toggle.type = "button";
+        toggle.className = "progress-reset-btn";
+        toggle.textContent = state === "complete" ? "Mark incomplete" : "Mark complete";
+        toggle.addEventListener("click", function () {
+          var isComplete = state === "complete";
+          ProgressTracker.markComplete(m.slug, !isComplete);
+          ProgressTracker.renderWidget();
+        });
+        row.appendChild(toggle);
+
+        var track = document.createElement("div");
+        track.className = "progress-track";
+        var fill = document.createElement("div");
+        fill.className = "progress-fill";
+        fill.style.width = (state === "complete" ? 100 : state === "visited" ? 35 : 0) + "%";
+        track.appendChild(fill);
+        row.appendChild(track);
+
+        container.appendChild(row);
+      });
+
+      var resetAll = document.createElement("button");
+      resetAll.type = "button";
+      resetAll.className = "progress-reset-btn";
+      resetAll.style.marginTop = "0.75em";
+      resetAll.textContent = "Reset all progress (" + completedCount + "/" + MODULES.length + " complete)";
+      resetAll.addEventListener("click", function () {
+        ProgressTracker.write({});
+        ProgressTracker.renderWidget();
+      });
+      container.appendChild(resetAll);
+
+      mount.appendChild(container);
+    }
+  };
+
+  /* ========================================================================
+     Boot
+     ======================================================================== */
+  document.addEventListener("DOMContentLoaded", function () {
+    ThemeManager.init();
+    initMermaid();
+    upgradeGfmAlerts();
+    addCopyButtons();
+    initBackToTop();
+    initBreadcrumbs();
+    initTOC();
+    externalLinksNewTab();
+    ProgressTracker.recordCurrentPage();
+    ProgressTracker.renderWidget();
+  });
+})();

From 2bb795e3057b356905ddf1bcf886f21556e37db9 Mon Sep 17 00:00:00 2001
From: ghanendra-sdet 
Date: Mon, 5 Oct 2026 10:20:25 +0530
Subject: [PATCH 02/21] seo: add per-page title/description front matter
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Every module page previously shared one identical, generic 
tag (verified live via curl) regardless of which page you were on —
bad for sharing and search. Each page now has its own unique title
and description, feeding the Open Graph/Twitter card tags added in
head-custom.html. Confirmed via the live site's existing markup that
this repo's pages render without a theme-injected title heading, so
this only affects <title>/meta tags, not visible page content.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 00-introduction/README.md                | 5 +++++
 01-git-fundamentals/README.md            | 5 +++++
 02-git-and-github/README.md              | 5 +++++
 03-contributing-to-open-source/README.md | 5 +++++
 04-undo-and-recovery/README.md           | 5 +++++
 05-advanced-git/README.md                | 5 +++++
 06-certification-prep/README.md          | 5 +++++
 07-exercises-and-study-plan/README.md    | 5 +++++
 README.md                                | 5 +++++
 resources/cheatsheet.md                  | 5 +++++
 resources/command-recall-drill.md        | 5 +++++
 11 files changed, 55 insertions(+)

diff --git a/00-introduction/README.md b/00-introduction/README.md
index e87a33f..815af43 100644
--- a/00-introduction/README.md
+++ b/00-introduction/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 00 — Introduction & Setup"
+description: "What version control actually is, installing Git, and the one-time config that saves you from a confusing commit history."
+---
+
 <div align="center" markdown="1">
 
 # 🏠 Module 00 — Introduction & Setup
diff --git a/01-git-fundamentals/README.md b/01-git-fundamentals/README.md
index 0e3bfd8..867f36f 100644
--- a/01-git-fundamentals/README.md
+++ b/01-git-fundamentals/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 01 — Git Fundamentals"
+description: "Staging, commits, branching, and merging — the core loop you'll run hundreds of times a week, with hands-on labs for every concept."
+---
+
 <div align="center" markdown="1">
 
 # 🧬 Module 01 — Git Fundamentals
diff --git a/02-git-and-github/README.md b/02-git-and-github/README.md
index ec3b8ef..d7c3b0f 100644
--- a/02-git-and-github/README.md
+++ b/02-git-and-github/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 02 — Git + GitHub"
+description: "SSH keys, remotes, push/pull, GitHub Flow, and GitHub Pages — where Git on your laptop becomes Git the internet can see."
+---
+
 <div align="center" markdown="1">
 
 # 🌐 Module 02 — Git + GitHub
diff --git a/03-contributing-to-open-source/README.md b/03-contributing-to-open-source/README.md
index 05ea7dc..e861b59 100644
--- a/03-contributing-to-open-source/README.md
+++ b/03-contributing-to-open-source/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 03 — Contributing to Open Source"
+description: "Fork, clone, branch, pull request — the exact sequence behind every open-source contribution you've ever seen on GitHub."
+---
+
 <div align="center" markdown="1">
 
 # 🤝 Module 03 — Contributing to Open Source
diff --git a/04-undo-and-recovery/README.md b/04-undo-and-recovery/README.md
index 5f9aae5..857648b 100644
--- a/04-undo-and-recovery/README.md
+++ b/04-undo-and-recovery/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 04 — Undo & Recovery"
+description: "Revert, reset, amend, rebase, and reflog — plus hands-on disaster labs for every 'I think I just lost three days of work' scenario."
+---
+
 <div align="center" markdown="1">
 
 # ↩️ Module 04 — Undo & Recovery
diff --git a/05-advanced-git/README.md b/05-advanced-git/README.md
index 4486078..cd90475 100644
--- a/05-advanced-git/README.md
+++ b/05-advanced-git/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 05 — Advanced Git"
+description: ".gitignore/.gitattributes, LFS, signed commits, cherry-picking, merge conflict resolution, CI/CD, hooks, submodules, and git bisect."
+---
+
 <div align="center" markdown="1">
 
 # 🚀 Module 05 — Advanced Git
diff --git a/06-certification-prep/README.md b/06-certification-prep/README.md
index 074310a..f705ec9 100644
--- a/06-certification-prep/README.md
+++ b/06-certification-prep/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 06 — Certification Prep"
+description: "A structured review pass across every module, framed as certification-style, scenario-based practice."
+---
+
 <div align="center" markdown="1">
 
 # 🎓 Module 06 — Certification Prep
diff --git a/07-exercises-and-study-plan/README.md b/07-exercises-and-study-plan/README.md
index 95af21a..19d528d 100644
--- a/07-exercises-and-study-plan/README.md
+++ b/07-exercises-and-study-plan/README.md
@@ -1,3 +1,8 @@
+---
+title: "Module 07 — Exercises & Study Plan"
+description: "Progressive exercises, a capstone Git Mastery Challenge, and a 20-question quiz bank — where reading turns into muscle memory."
+---
+
 <div align="center" markdown="1">
 
 # 🏁 Module 07 — Exercises & Study Plan (Capstone)
diff --git a/README.md b/README.md
index e688cd5..381a23d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,8 @@
+---
+title: "GIT Tutorials"
+description: "A free, hands-on Git + GitHub course. Learn → See → Do → Break → Fix → Remember, with real commands, real recovery labs, and zero corporate-training energy."
+---
+
 <div align="center" markdown="1">
 
 # 🐙 GIT Tutorials — From `git init` to "wait, how do I undo this"
diff --git a/resources/cheatsheet.md b/resources/cheatsheet.md
index f60e7fd..7cd6c78 100644
--- a/resources/cheatsheet.md
+++ b/resources/cheatsheet.md
@@ -1,3 +1,8 @@
+---
+title: "Git Cheatsheet"
+description: "A flat, one-page Git command reference — for when you already know the command and just blanked on the flag."
+---
+
 <div align="center" markdown="1">
 
 # 📖 Git Cheatsheet — One-Page Reference
diff --git a/resources/command-recall-drill.md b/resources/command-recall-drill.md
index 19cc963..b059a68 100644
--- a/resources/command-recall-drill.md
+++ b/resources/command-recall-drill.md
@@ -1,3 +1,8 @@
+---
+title: "Command Recall Drill"
+description: "Scenario-based, fold-to-reveal Git command drills across every module — spaced-repetition practice, not a reading list."
+---
+
 <div align="center" markdown="1">
 
 # 🧠 Command Recall Drill

From f9e6fd0218d636b502038ecd7950a9fd8085dd3e Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 10:21:37 +0530
Subject: [PATCH 03/21] docs: turn README into a proper landing page
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Added a start-here action-button row (Start Learning / Course
  Roadmap / Practice Labs / Cheat Sheet), matching the existing badge
  style already used elsewhere in this README
- Converted "The Path" from a plain-text diagram into a clickable
  Mermaid flowchart + a table with level badges per module
- Linked resources/interactive-cheatsheet.html, which had a fully
  built, working interactive Git sandbox + searchable command matrix
  but was completely unlinked from the README (verified via grep —
  the only reference to it anywhere was from resources/cheatsheet.md)
- Added the localStorage-only progress tracker mount point

Note: the "Practice Labs" button points at a 04-undo-and-recovery
anchor that will exist once that module's recovery scenarios are
reformatted into the labs structure (next piece of work on this
branch) — not yet live.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 46 insertions(+), 10 deletions(-)

diff --git a/README.md b/README.md
index 381a23d..18fcae0 100644
--- a/README.md
+++ b/README.md
@@ -16,11 +16,23 @@ description: "A free, hands-on Git + GitHub course. Learn → See → Do → Bre
 
 **A structured, hands-on Git + GitHub course — real commands, real analogies, real "oh no I force-pushed to main" recovery stories, zero corporate-training energy.**
 
+**Learn → See → Do → Break → Fix → Remember.** You don't just read about a command — you run it,
+in a disposable sandbox, immediately.
+
+[![Start Learning](https://img.shields.io/badge/🚀-Start%20Learning-2563eb?style=for-the-badge)](./00-introduction/)
+[![Course Roadmap](https://img.shields.io/badge/📚-Course%20Roadmap-16a34a?style=for-the-badge)](#️-the-path)
+[![Practice Labs](https://img.shields.io/badge/🧪-Practice%20Labs-d97706?style=for-the-badge)](./04-undo-and-recovery/#-disaster-labs)
+[![Cheat Sheet](https://img.shields.io/badge/⚡-Cheat%20Sheet-7c3aed?style=for-the-badge)](./resources/cheatsheet.md)
+
 **🌐 Browse it live:** [ghanendra-sdet.github.io/git-tutorials](https://ghanendra-sdet.github.io/git-tutorials/)<br><br>
 **📦 View source on GitHub:** [github.com/ghanendra-sdet/git-tutorials](https://github.com/ghanendra-sdet/git-tutorials)
 
 </div>
 
+<div id="progress-tracker-mount"></div>
+
+<p align="center"><em>Progress is tracked only in your own browser (localStorage) — no login, nothing sent anywhere. The checklist above will stay empty until JavaScript runs, on the live site.</em></p>
+
 ---
 
 ## Why this exists
@@ -48,16 +60,36 @@ This one is different for three reasons:
 
 ## 🗺️ The Path
 
+```mermaid
+flowchart TD
+    M00["00 · Introduction & Setup"] --> M01["01 · Git Fundamentals"]
+    M01 --> M02["02 · Git + GitHub"]
+    M02 --> M03["03 · Contributing to Open Source"]
+    M03 --> M04["04 · Undo & Recovery"]
+    M04 --> M05["05 · Advanced Git"]
+    M05 --> M06["06 · Certification Prep"]
+    M06 --> M07["07 · Exercises & Study Plan"]
+
+    style M00 fill:#d4edda
+    style M01 fill:#d4edda
+    style M02 fill:#cfe2ff
+    style M03 fill:#cfe2ff
+    style M04 fill:#fff3cd
+    style M05 fill:#ffcccc
+    style M06 fill:#ede9fe
+    style M07 fill:#ede9fe
 ```
-00 → Introduction & Setup       "What even is version control, and why does everyone use this one"
-01 → Git Fundamentals           "The 90% of Git you'll use every single day"
-02 → Git + GitHub               "Your laptop's Git talking to the internet's Git"
-03 → Contributing to Open Source "Fork, clone, pull request, profit (reputation-wise)"
-04 → Undo & Recovery            "You didn't break it. Well — you did. But it's fixable."
-05 → Advanced Git               "The stuff that shows up in senior job descriptions"
-06 → Certification Prep         "Prove it, on paper"
-07 → Exercises & Study Plan     "Stop reading, start typing"
-```
+
+| # | Module | Level | "In one sentence" |
+|---|---|---|---|
+| 00 | [Introduction & Setup](./00-introduction/) | 🟢 Beginner | What even is version control, and why does everyone use this one |
+| 01 | [Git Fundamentals](./01-git-fundamentals/) | 🟢 Beginner | The 90% of Git you'll use every single day |
+| 02 | [Git + GitHub](./02-git-and-github/) | 🟢 Beginner→Intermediate | Your laptop's Git talking to the internet's Git |
+| 03 | [Contributing to Open Source](./03-contributing-to-open-source/) | 🔵 Intermediate | Fork, clone, pull request, profit (reputation-wise) |
+| 04 | [Undo & Recovery](./04-undo-and-recovery/) | 🔵 Intermediate | You didn't break it. Well — you did. But it's fixable |
+| 05 | [Advanced Git](./05-advanced-git/) | 🔴 Advanced | The stuff that shows up in senior job descriptions |
+| 06 | [Certification Prep](./06-certification-prep/) | 🟣 All levels | Prove it, on paper |
+| 07 | [Exercises & Study Plan](./07-exercises-and-study-plan/) | 🟣 All levels | Stop reading, start typing |
 
 Follow the numbers in order for modules 00–02 — everything after that builds directly on branching
 and merging, which is the one concept you genuinely cannot skip.
@@ -119,9 +151,13 @@ progression. This is where reading turns into muscle memory.
 
 ## 🧠 Practice & Recall
 
-Reading a module once won't make the commands stick — using them will. Two resources built
+Reading a module once won't make the commands stick — using them will. Three resources built
 specifically for that:
 
+- **[Interactive Cheatsheet Matrix](./resources/interactive-cheatsheet.html)** — every command,
+  searchable and filterable by "how much you're panicking," with one-click copy and a live,
+  consequence-free visual Git tree sandbox (click "commit," "branch," "merge" and watch the graph
+  update in real time).
 - **[Command Recall Drill](./resources/command-recall-drill.md)** — scenario-based, fold-to-reveal
   quizzing across every module. Do 10-15 a day, not all of them at once — this is spaced
   repetition, not a reading list.

From 6cc0d745becfceaa0e94f6809bef3bca9395fad7 Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 10:23:07 +0530
Subject: [PATCH 04/21] docs(module-00): add hands-on practice loop
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Module 00 was read-only explanation with no action until git init at
the very end. Added the interactive pattern that will carry through
every module from here:

- A persistent "safe sandbox" (~/git-playground) introduced in this
  first module specifically so every later module can just say "in
  your playground" without re-explaining
- Try It Yourself blocks after install, config, and git init/status
- Your Turn challenges with hidden <details> solutions — local vs.
  global config override, and a "predict git status before running
  it" exercise
- One SDET scenario tying git init to a real automation-repo moment
- Explicit "What You'll Learn" / "You can now" bookends

Content, examples, and tone preserved — nothing removed, only the
missing "now you try" loop added around it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 00-introduction/README.md | 178 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 170 insertions(+), 8 deletions(-)

diff --git a/00-introduction/README.md b/00-introduction/README.md
index 815af43..7ba4515 100644
--- a/00-introduction/README.md
+++ b/00-introduction/README.md
@@ -14,10 +14,23 @@ description: "What version control actually is, installing Git, and the one-time
 
 </div>
 
+<div class="callout callout-concept" markdown="1">
+
+**By the end of this module you will be able to:**
+
+✓ Explain what Git actually *is*, and why it beat older version control systems
+✓ Install Git and set your identity correctly (global vs. local)
+✓ Create a real Git repository from scratch with `git init`
+✓ Read `git status`'s output and know what it's telling you to do next
+✓ Pull up Git's built-in help without leaving the terminal
+
+</div>
+
 ---
 
 ## 📑 In This Module
 
+- [Set Up Your Safe Sandbox](#-set-up-your-safe-sandbox)
 - [Git Home — What Even Is This](#-git-home--what-even-is-this)
 - [Git Intro — Version Control, Explained Like You're Five (a smart five-year-old)](#-git-intro--version-control-explained-like-youre-five-a-smart-five-year-old)
 - [Git Install](#-git-install)
@@ -28,6 +41,33 @@ description: "What version control actually is, installing Git, and the one-time
 
 ---
 
+## 🛟 Set Up Your Safe Sandbox
+
+Every "Try It" and "Your Turn" block in this course — starting right now — assumes you have one
+disposable folder to actually type commands into. Not a real project. Something you can break on
+purpose, over and over, with zero consequences.
+
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF**
+
+```bash
+mkdir ~/git-playground
+cd ~/git-playground
+```
+
+That's it for now — you don't even have `git init` yet, because the very next section is about
+to teach you what that command does and why. Just get the folder made and remember where it is;
+every module from here forward will say "in your playground" and mean this exact folder.
+
+</div>
+
+> [!TIP]
+> Keep this folder for the entire course. Delete everything inside it whenever you want a clean
+> slate — nothing in here should ever be work you actually care about keeping.
+
+---
+
 ## 🏠 Git Home — What Even Is This
 
 **Git** is a program that tracks changes to files over time, so you can:
@@ -156,6 +196,21 @@ git --version
 > added to it. (The other 20% of the time, you'll be Googling PATH environment variables at
 > midnight. We've all been there.)
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF**
+
+```bash
+git --version
+git help -a
+```
+
+**What should I see?** A version number on the first command, and a long alphabetical list of
+every Git subcommand that exists on the second. You don't need to read all of it — just confirm
+it printed something. That list existing at all is the whole point of the next section.
+
+</div>
+
 ---
 
 ## ⚙️ Git Config
@@ -200,30 +255,83 @@ git config user.email "you@company.com"
 > override it with a `--local` (no flag) config inside your work repos specifically — not the
 > other way around, unless you enjoy explaining commit history to HR.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF**
+
+```bash
+git config --global user.name "Your Name"
+git config --global user.email "you@example.com"
+git config --list
+```
+
+**What should I see?** A list of key=value settings, including `user.name=Your Name` and
+`user.email=you@example.com` somewhere in it. If you don't see them, the config didn't save —
+check for a typo in the command before moving on.
+
+</div>
+
+<div class="callout callout-challenge" markdown="1">
+
+**🎯 YOUR TURN**
+
+Without looking at the example above: set your email to one thing **globally**, then go into
+your `~/git-playground` folder and override it to something different, **locally, just for that
+one folder**. Then run `git config user.email` *inside* the playground folder, and run it again
+from somewhere *outside* it. Can you explain out loud why the two commands print different
+answers?
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+git config --global user.email "global@example.com"
+
+cd ~/git-playground
+git config user.email "local-override@example.com"
+
+git config user.email        # prints "local-override@example.com" — local wins here
+cd ..
+git config user.email        # prints "global@example.com" — no local override outside the playground
+```
+
+Local config lives in that specific repo's `.git/config` file and always wins over global when
+both exist — that's the entire mechanism behind "why do my personal commits show my work email"
+bugs: someone set a local override once, forgot about it, and every commit since has used it.
+
+</details>
+
+</div>
+
 ---
 
 ## 🚀 Git Get Started
 
-Time to actually create a repository.
+Time to actually create a repository — in the `~/git-playground` folder you made earlier.
+
+<div class="callout callout-tryit" markdown="1">
 
-### Step by step
+**⌨️ TRY IT YOURSELF**
 
 ```bash
-# 1. Make a new folder and go into it
-mkdir my-first-repo
-cd my-first-repo
+cd ~/git-playground
 
-# 2. Turn it into a Git repository
+# Turn this folder into a Git repository
 git init
-# Initialized empty Git repository in .../my-first-repo/.git/
+# Initialized empty Git repository in .../git-playground/.git/
 
-# 3. Check what Git thinks is going on (you'll run this CONSTANTLY)
+# Check what Git thinks is going on (you'll run this CONSTANTLY)
 git status
 # On branch main
 # No commits yet
 # nothing to commit (create/copy files and use "git add" to track)
 ```
 
+**What should I see?** `git init`'s one-line confirmation, then `git status` reporting a brand
+new, empty repo on branch `main` with nothing to commit yet.
+
+</div>
+
 That `.git` folder `git init` just created is where **all** of Git's magic lives — the entire
 history, every branch, every commit. Delete that folder, and as far as Git is concerned, the
 project's history never existed (the files themselves are untouched — only the tracking is gone).
@@ -234,6 +342,48 @@ project's history never existed (the files themselves are untouched — only the
 > staging, after committing. It always tells you exactly what state you're in and, usually, the
 > exact next command to run.
 
+<div class="callout callout-challenge" markdown="1">
+
+**🎯 YOUR TURN**
+
+Before running anything — predict out loud what `git status` will print if you create a brand
+new empty file right now and run it, **without** running `git add` first. Then actually do it
+and check your prediction.
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+touch notes.txt
+git status
+```
+
+```
+Untracked files:
+  (use "git add <file>..." to include in what will be committed)
+        notes.txt
+```
+
+If you predicted "it'll show up as untracked, not staged" — that's exactly right, and it's the
+single most important mental model from this entire module: Git notices new files immediately,
+but never tracks them until you explicitly say so with `git add`.
+
+</details>
+
+</div>
+
+<div class="callout callout-sdet" markdown="1">
+
+**🧪 SDET SCENARIO**
+
+Every automation repo you'll ever work in — a Playwright suite, a Selenium framework, a Postman
+collection exported to Newman — starts with exactly this: `git init` (or `git clone`, if someone
+already created it), then an immediate `.gitignore` for `node_modules/`, test reports, and
+screenshots nobody wants committed. You'll see the real version of this in
+[Module 05](../05-advanced-git/#-gitignore).
+
+</div>
+
 ---
 
 ## 🆘 Git Help
@@ -283,6 +433,18 @@ If any of those feel shaky, that's completely normal for a first pass — skim b
 click. If you're solid on all three, you're ready for the module where things actually get typed
 into files.
 
+<div class="callout callout-remember" markdown="1">
+
+**🧠 You can now:**
+
+✓ Install Git and verify it from the terminal
+✓ Set your identity globally, and override it locally when you need to
+✓ Turn any folder into a Git repository with `git init`
+✓ Read `git status` and know the difference between "untracked" and "nothing to commit"
+✓ Reach for `git help` instead of leaving the terminal to Google a flag
+
+</div>
+
 ---
 
 <div align="center" markdown="1">

From f4192c2dc047742f1447c93282d5af0ae8cf2d4f Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 10:25:22 +0530
Subject: [PATCH 05/21] docs(module-01): add hands-on practice loop to the core
 module
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Same pattern as Module 00, applied to the module the course itself
calls "the one you'll actually use every single working day":

- Try It blocks after staging, commit, log, tag, stash, branch, and
  merge — every major command now gets run, not just read
- Your Turn challenges with hidden solutions for unstaging a file,
  creating a second branch, and running the full
  branch -> commit -> merge -> delete loop end to end
- A Common Mistake callout on mega-commits, tied forward to why it
  breaks revert/cherry-pick/bisect later in the course
- An SDET scenario for stash (switching to investigate a flaky CI
  test mid-feature) — the single most common real reason to use it
- A Quick Check on fast-forward vs. three-way merge right after the
  Try It that demonstrates a fast-forward live

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 01-git-fundamentals/README.md | 271 ++++++++++++++++++++++++++++++++++
 1 file changed, 271 insertions(+)

diff --git a/01-git-fundamentals/README.md b/01-git-fundamentals/README.md
index 867f36f..e0f2956 100644
--- a/01-git-fundamentals/README.md
+++ b/01-git-fundamentals/README.md
@@ -14,6 +14,22 @@ description: "Staging, commits, branching, and merging — the core loop you'll
 
 </div>
 
+<div class="callout callout-concept" markdown="1">
+
+**By the end of this module you will be able to:**
+
+✓ Explain why Git has a staging area at all, not just "what it does"
+✓ Stage exactly the changes you want, even out of a messy working directory
+✓ Write commit messages your future self will actually thank you for
+✓ Create, switch, and merge branches — the loop you'll run dozens of times a day
+✓ Use `git stash` to context-switch without committing half-finished work
+
+</div>
+
+> [!TIP]
+> Still have `~/git-playground` from [Module 00](../00-introduction/#-set-up-your-safe-sandbox)?
+> Use it. Every Try It block below assumes a real terminal, not just reading along.
+
 ---
 
 ## 📑 In This Module
@@ -51,6 +67,20 @@ Untracked files:
 **Untracked** = "Git sees this file exists, but has zero interest in it until you tell it
 otherwise." This is a feature, not Git being lazy.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "console.log('hello world')" > app.js
+git status
+```
+
+**What should I see?** `app.js` listed under "Untracked files," with Git explicitly suggesting
+`git add` — not an error, just Git telling you exactly what to do next.
+
+</div>
+
 ---
 
 ## 📦 Staging — The Most Important Concept in Git
@@ -118,6 +148,50 @@ git reset file.txt        # UN-stage a file (doesn't touch its actual content 
 > accidentally left a `console.log('here')` debug line in the same file, and want to commit only
 > the fix.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "fix bug" >> login.js
+echo "new feature" >> signup.js
+git add login.js
+git status
+```
+
+**What should I see?** `login.js` under "Changes to be committed," `signup.js` still sitting
+under "Changes not staged for commit" — proof the staging area really does let you pick exactly
+what goes in, even when more than one file changed.
+
+</div>
+
+<div class="callout callout-challenge" markdown="1">
+
+**🎯 YOUR TURN**
+
+You have two modified files: `login.js` and `signup.js`. Stage **only** `signup.js`, confirm it
+with `git status`, then **unstage** it again without touching its actual content. Can you do it
+without looking at the solution first?
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+git add signup.js
+git status                 # signup.js now under "Changes to be committed"
+git reset signup.js
+git status                 # signup.js back under "Changes not staged" — content untouched
+```
+
+`git reset <file>` (no flags, just a filename) only moves a file *out of the box* — it never
+discards the edit itself. That distinction matters a lot more once you hit
+[Module 04](../04-undo-and-recovery/), where `reset` can also discard real work if you're not
+careful with it.
+
+</details>
+
+</div>
+
 ---
 
 ## ✅ Commit
@@ -157,6 +231,32 @@ The convention `type: description` (feat/fix/refactor/docs/test/chore) isn't req
 itself, but it's so widely adopted that `git log --oneline --grep="fix:"` instantly showing every
 bug fix in your project's history is now basically an industry standard trick.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git commit -m "fix: login validation bug"
+git log --oneline -1
+```
+
+**What should I see?** A one-line confirmation from `git commit` (branch, short hash, file stats),
+then that same commit as a single line from `git log`.
+
+</div>
+
+<div class="callout callout-mistake" markdown="1">
+
+**🚨 COMMON MISTAKE**
+
+Committing "today's work" as one giant commit at the end of the day. It feels efficient in the
+moment and is genuinely painful later — `git revert` (Module 04) undoes *one commit*, so a
+50-file mega-commit means reverting the one real bug inside it also reverts 49 unrelated changes
+you didn't want touched. Small, focused commits aren't a style preference; they're what makes
+every later Git operation (revert, cherry-pick, bisect) actually usable.
+
+</div>
+
 ---
 
 ## 📜 History
@@ -183,6 +283,21 @@ without asking anyone or guessing.
 git log -p --follow -- path/to/file.js
 ```
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git log --oneline
+git log --graph --oneline --all
+```
+
+**What should I see?** The same commits, but the second command draws them as a small ASCII
+tree. On one branch with no merges yet it'll look like a straight line — that's expected. It gets
+genuinely useful once branches exist, a few sections from now.
+
+</div>
+
 ---
 
 ## 🏷️ Tagging
@@ -204,6 +319,21 @@ shipped to customers as Version 2.0."** Branches move forward constantly; tags d
 — once set, `v1.0.0` always points at the exact same commit, forever, so you can always answer
 "what code was actually in production on release day" with certainty.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git tag -a v0.1.0 -m "First playground milestone"
+git tag
+git show v0.1.0
+```
+
+**What should I see?** `v0.1.0` listed by `git tag`, and `git show` printing the tag's own
+message plus the full commit it points at.
+
+</div>
+
 ---
 
 ## 🧳 Stash
@@ -232,6 +362,36 @@ git stash push -m "half-done payment form"  # stash WITH a note, for when you'll
 > clearly if this happens, don't panic, it's the same conflict-resolution process as a merge
 > conflict (covered below).
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "half-done work" >> login.js
+git status            # shows login.js modified
+git stash
+git status            # clean — login.js looks untouched
+git stash pop
+git status            # the change is back
+```
+
+**What should I see?** `git status` reporting a clean working directory right after `stash`, then
+the exact same modification reappearing after `pop` — nothing was lost, it was just parked.
+
+</div>
+
+<div class="callout callout-sdet" markdown="1">
+
+**🧪 SDET SCENARIO**
+
+You're mid-way through writing a new Playwright test, it's not passing yet, and a flaky test in
+CI just paged you to investigate *right now*. You can't commit broken, half-finished test code —
+but you also can't lose 40 minutes of locator work. `git stash`, switch branches, debug the
+flaky test, fix it, switch back, `git stash pop`, and you're exactly where you left off. This is
+the single most common real reason test engineers reach for stash.
+
+</div>
+
 ---
 
 ## 🌿 Branch
@@ -275,6 +435,46 @@ git branch -D feature/checkout    # force-delete (no safety check — know what
 > branch per feature, per bug fix, per experiment) in a way that would've been unthinkably slow in
 > older version control systems.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git switch -c feature/login
+git branch
+```
+
+**What should I see?** `feature/login` listed with a `*` next to it, confirming you're now on
+the new branch — `main` is still exactly where you left it.
+
+</div>
+
+<div class="callout callout-challenge" markdown="1">
+
+**🎯 YOUR TURN**
+
+You're currently on `feature/login`. Create a second branch called `feature/payment`, make one
+commit on it (any trivial change), then switch back to `main`. Do NOT look at the solution first.
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+git switch -c feature/payment
+echo "payment stub" >> payment.js
+git add payment.js
+git commit -m "feat: stub out payment module"
+git switch main
+```
+
+Run `git log --oneline --graph --all` afterward — you'll see `main`, `feature/login`, and
+`feature/payment` as three separate pointers, with `feature/payment` one commit ahead of where
+it branched off.
+
+</details>
+
+</div>
+
 ---
 
 ## 🔀 Merge
@@ -314,6 +514,30 @@ one final document (three-way merge) — and if both edited the *exact same sent
 that's a **merge conflict**, which needs a human to decide. (Full conflict-resolution workflow is
 in [Module 05](../05-advanced-git/).)
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git switch main
+git merge feature/login
+git log --oneline --graph --all
+```
+
+**What should I see?** Since `main` hasn't moved since you branched, this is a **fast-forward** —
+no merge commit, `main`'s pointer just catches up to `feature/login`'s latest commit.
+
+</div>
+
+<details>
+<summary>🧠 <strong>Quick Check:</strong> If you'd made a NEW commit directly on <code>main</code> before merging <code>feature/login</code> in, would this still be a fast-forward?</summary>
+
+No — the moment `main` has a commit `feature/login` doesn't know about, Git can't just "catch up"
+a pointer anymore. It has to combine two divergent histories, which means a three-way merge and a
+dedicated merge commit, even if there's no actual conflict between the changes.
+
+</details>
+
 ---
 
 ## 🔁 Workflow — Putting It All Together
@@ -351,6 +575,41 @@ That's genuinely most of Git, in one loop. Everything else in this course is eit
 on this loop (Module 02's GitHub push/pull), a safety net for when it goes wrong (Module 04), or
 a specialization for specific situations (Module 05).
 
+<div class="callout callout-challenge" markdown="1">
+
+**🎯 YOUR TURN — Run the whole loop yourself**
+
+In `~/git-playground`, from `main`:
+
+1. Create a branch called `feature/dark-mode`
+2. Make two separate, small commits on it (anything — even two one-line `echo` changes to
+   different files)
+3. Switch back to `main` and merge it in
+4. Delete the now-merged branch
+
+Do all four steps without looking at the solution first — this is the exact loop you'll run for
+the rest of your career, so it's worth getting your fingers to do it without thinking.
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+git switch -c feature/dark-mode
+echo "dark mode css" >> styles.css
+git add styles.css
+git commit -m "feat: add dark mode styles"
+echo "dark mode toggle" >> settings.js
+git add settings.js
+git commit -m "feat: add dark mode toggle"
+git switch main
+git merge feature/dark-mode
+git branch -d feature/dark-mode
+```
+
+</details>
+
+</div>
+
 ---
 
 ## 🧠 Best Practices
@@ -400,6 +659,18 @@ a specialization for specific situations (Module 05).
 If #3 didn't immediately bring "`git stash`" to mind, that's worth a re-read of that section —
 it's one of the most practically useful commands in this entire module.
 
+<div class="callout callout-remember" markdown="1">
+
+**🧠 You can now:**
+
+✓ Explain why the staging area exists, not just recite what it does
+✓ Stage exactly the changes you want, chunk by chunk if needed
+✓ Write commit messages that'll still make sense to you in six months
+✓ Create, switch, merge, and delete branches — the daily loop
+✓ Shelve half-finished work with `git stash` without committing it
+
+</div>
+
 ---
 
 <div align="center" markdown="1">

From 88298f104293103c4c1f07d04c1a09e1788f54a8 Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 10:26:53 +0530
Subject: [PATCH 06/21] docs(module-02): add hands-on practice loop

Same pattern as Modules 00-01, adapted for the fact that this module
genuinely needs a real GitHub account and an empty repo (said so
explicitly up front, rather than pretending it's as sandboxed as the
first two modules):

- Try It blocks for checking/generating an SSH key, wiring up a real
  remote, pushing, and pushing a feature branch
- A Your Turn that proves fetch vs. pull by editing via GitHub's web
  editor and watching origin/main update before local main does
- An SDET scenario tying GitHub Flow's "CI passes" step to a real
  Playwright/Selenium suite running on every PR

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 02-git-and-github/README.md | 132 ++++++++++++++++++++++++++++++++++++
 1 file changed, 132 insertions(+)

diff --git a/02-git-and-github/README.md b/02-git-and-github/README.md
index d7c3b0f..249d56a 100644
--- a/02-git-and-github/README.md
+++ b/02-git-and-github/README.md
@@ -14,6 +14,24 @@ description: "SSH keys, remotes, push/pull, GitHub Flow, and GitHub Pages — wh
 
 </div>
 
+<div class="callout callout-concept" markdown="1">
+
+**By the end of this module you will be able to:**
+
+✓ Authenticate to GitHub with SSH instead of typing a password every time
+✓ Connect a local repo to a real GitHub repo and push to it
+✓ Explain the actual difference between `fetch` and `pull` — and why cautious people fetch first
+✓ Push a branch and know what happens to it on GitHub's side
+✓ Describe GitHub Flow well enough to follow it on a real team
+
+</div>
+
+> [!NOTE]
+> Unlike Modules 00–01, the Try It blocks below need a **real, free GitHub account** and (for the
+> push/pull ones) an empty repo you created on github.com — this module is specifically about
+> talking to the internet, so there's no way around that. Nothing here touches real production
+> code; create a throwaway repo, same spirit as `~/git-playground`.
+
 ---
 
 ## 📑 In This Module
@@ -88,6 +106,20 @@ ssh -T git@github.com
 > anywhere but your own machine) and `id_ed25519.pub` (public — this is the one that's safe to
 > give to GitHub). Mixing these up is a genuine security incident, not a typo.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF**
+
+```bash
+ls -la ~/.ssh
+```
+
+**What should I see?** If you see `id_ed25519` and `id_ed25519.pub` (or an `id_rsa` pair from an
+older setup), you already have a key and can skip straight to adding the `.pub` one to GitHub.
+If the folder doesn't exist or is empty, run the `ssh-keygen` command above first.
+
+</div>
+
 ---
 
 ## 🔗 Set Remote
@@ -115,6 +147,24 @@ git remote remove origin
 `banana` and Git wouldn't care. Everyone calls it `origin` because everyone else does, which is
 its own kind of consistency worth keeping.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF**
+
+1. On github.com, create a new **empty** repository (no README, no .gitignore — genuinely empty)
+2. Copy its SSH URL (the green "Code" button → SSH tab)
+
+```bash
+cd ~/git-playground
+git remote add origin git@github.com:yourusername/your-repo-name.git
+git remote -v
+```
+
+**What should I see?** Two lines back from `git remote -v` — `origin` listed for both fetch and
+push, pointing at the repo you just created.
+
+</div>
+
 ---
 
 ## ✏️ Edit Code — the GitHub Web Editor
@@ -161,6 +211,20 @@ graph LR
 > teammate's just-pushed work. `git push --force-with-lease` is the safer version: it refuses to
 > push if the remote has changes you haven't seen yet, instead of blindly overwriting.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (using the `origin` you set up above)
+
+```bash
+git push -u origin main
+```
+
+**What should I see?** Git prints upload progress, then confirms `main -> main` with the
+upstream now set. Refresh the repo's page on github.com — your commits from Modules 00–01 are
+now visible there, publicly (or privately, if you made the repo private).
+
+</div>
+
 ---
 
 ## ⬇️ Pull from GitHub
@@ -182,6 +246,33 @@ cabinet, grabbing those new pages, AND immediately filing them into your own not
 right place. `git fetch` alone is just "go look at the cabinet and see what's new" — without
 touching your own notebook yet. Cautious people `fetch` first, review, then `merge` or `pull`.
 
+<div class="callout callout-challenge" markdown="1">
+
+**🎯 YOUR TURN**
+
+Make a small edit directly on GitHub's web editor (press `.` on your repo, edit a file, commit)
+— *without* touching your local clone. Then, locally, run `git fetch origin` followed by
+`git log origin/main`. Can you see that remote commit before running `git pull`? What does that
+prove about what `fetch` actually does?
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+git fetch origin
+git log origin/main -1     # the web-editor commit shows up here already
+git log main -1            # your LOCAL main still doesn't have it
+git pull origin main       # now it does
+```
+
+This proves `fetch` genuinely downloads the data without touching your working branch —
+`origin/main` and `main` are two separate references, and only `merge` (or `pull`, which runs it
+for you) moves your local branch forward.
+
+</details>
+
+</div>
+
 ---
 
 ## 🌿 GitHub Branch — Push/Pull a Branch
@@ -200,6 +291,24 @@ git checkout feature/their-branch
 Once pushed, that branch is visible to your whole team on GitHub — anyone can view its diff,
 comment on it, or open a **pull request** from it (Module 03 goes deep on this).
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF**
+
+```bash
+git checkout -b feature/new-navbar
+echo "navbar stub" >> navbar.js
+git add navbar.js
+git commit -m "feat: stub out new navbar"
+git push -u origin feature/new-navbar
+```
+
+**What should I see?** GitHub's output includes a direct link to open a pull request for the
+branch you just pushed — and refreshing the repo's "Branches" page on github.com shows it listed
+there, separate from `main`.
+
+</div>
+
 ---
 
 ## 🔄 GitHub Flow
@@ -231,6 +340,17 @@ It's deliberately simpler than Git Flow (an older model with `develop`, `release
 branches) — GitHub Flow assumes you can deploy frequently and safely, which most modern teams with
 good CI/CD can.
 
+<div class="callout callout-sdet" markdown="1">
+
+**🧪 SDET SCENARIO**
+
+In a real automation repo, step 4 ("CI passes") is almost always your test suite — a pull request
+against `main` triggers a GitHub Actions run of the full Playwright/Selenium suite automatically
+(Module 05 covers the actual YAML for this). Opening the PR as a Draft early, per step 3, means
+you find out your new test is flaky in CI *before* a reviewer looks at it, not after.
+
+</div>
+
 ---
 
 ## 📄 GitHub Pages
@@ -298,6 +418,18 @@ If #3 surprised you, that's the most commonly-missed point in this whole module
 early (as a Draft) is a feature, not a mistake, because it makes your in-progress work visible for
 early feedback instead of a surprise 400-line diff at the end.
 
+<div class="callout callout-remember" markdown="1">
+
+**🧠 You can now:**
+
+✓ Authenticate to GitHub over SSH instead of a password every time
+✓ Connect a local repo to GitHub and push real commits to it
+✓ Tell `fetch` and `pull` apart, and know why cautious people fetch first
+✓ Push a branch and find it on GitHub, ready for a pull request
+✓ Follow GitHub Flow on a real team without needing it explained again
+
+</div>
+
 ---
 
 <div align="center" markdown="1">

From f2f12fb0cbc2606c7198c825603cf963235becb2 Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 10:27:43 +0530
Subject: [PATCH 07/21] docs(module-03): add hands-on practice loop

- Try It for forking + wiring up origin/upstream remotes
- A Quick Check on the safe-vs-dangerous direction of an origin/
  upstream mix-up
- A real Your Turn: find an actual good-first-issue (or a typo in
  this course's own docs) and open a genuine pull request, not a
  simulated one

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 03-contributing-to-open-source/README.md | 82 ++++++++++++++++++++++++
 1 file changed, 82 insertions(+)

diff --git a/03-contributing-to-open-source/README.md b/03-contributing-to-open-source/README.md
index e861b59..392ba26 100644
--- a/03-contributing-to-open-source/README.md
+++ b/03-contributing-to-open-source/README.md
@@ -14,6 +14,17 @@ description: "Fork, clone, branch, pull request — the exact sequence behind ev
 
 </div>
 
+<div class="callout callout-concept" markdown="1">
+
+**By the end of this module you will be able to:**
+
+✓ Fork a repository and explain what that actually created
+✓ Keep your fork in sync with the original project (`origin` vs. `upstream`)
+✓ Open a pull request that's likely to get merged quickly
+✓ Find a genuinely beginner-friendly issue to contribute to, today
+
+</div>
+
 ---
 
 ## 📑 In This Module
@@ -44,6 +55,18 @@ You do **not** have push access to the original repo — that's the entire point
 how strangers on the internet can propose changes to projects they don't have write access to,
 without any risk to the original.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF**
+
+Pick any small public repo on GitHub (even this `git-tutorials` repo works fine for practice) and
+click **Fork** (top right). That's the whole action — no terminal needed yet.
+
+**What should I see?** A new repo under *your* account, named the same thing, with "forked from"
+and a link back to the original shown right under the repo name.
+
+</div>
+
 ---
 
 ## 📥 Clone from GitHub
@@ -78,6 +101,33 @@ git merge upstream/main
 git push origin main    # update YOUR fork's copy on GitHub too
 ```
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (using the fork you just created)
+
+```bash
+git clone git@github.com:yourusername/your-fork-name.git
+cd your-fork-name
+git remote add upstream <the-original-repo's-SSH-url>
+git remote -v
+```
+
+**What should I see?** `origin` pointing at your fork, `upstream` pointing at the original — four
+lines total from `git remote -v` (fetch + push for each).
+
+</div>
+
+<details>
+<summary>🧠 <strong>Quick Check:</strong> You accidentally run <code>git push upstream main</code> instead of <code>git push origin main</code>. What happens?</summary>
+
+It fails — you don't have write access to `upstream` (the original repo), which is the entire
+point of the fork workflow. This is a genuinely safe mistake to make: Git will refuse with a
+permission error, not silently do something destructive. The more dangerous version of this
+mix-up is the opposite direction — accidentally *pulling* from `origin` when you meant
+`upstream`, which doesn't error, it just silently leaves you out of sync with the real project.
+
+</details>
+
 ---
 
 ## 📬 Send a Pull Request
@@ -125,6 +175,27 @@ Ran the corrected command on a fresh clone — build script now completes succes
 > harder, and sits unreviewed far longer. If you notice something else worth fixing, that's a
 > **second** PR, not scope creep on the first one.
 
+<div class="callout callout-challenge" markdown="1">
+
+**🎯 YOUR TURN — Your first real contribution**
+
+Using the fork you set up above (any public repo, including this one): find one genuinely small,
+real thing worth fixing — a typo, a broken link, an outdated command — make the fix on a properly
+named branch, push it to `origin`, and open the pull request. This is a real PR, not a drill.
+
+<details>
+<summary>💡 Not sure where to find something to fix?</summary>
+
+Search GitHub for issues labeled `good first issue` across any language you're comfortable in
+(or check [goodfirstissue.dev](https://goodfirstissue.dev/)). If genuinely nothing jumps out, even
+fixing a typo in this course's own docs and opening a PR against
+[ghanendra-sdet/git-tutorials](https://github.com/ghanendra-sdet/git-tutorials) counts — the
+mechanism is identical regardless of project size.
+
+</details>
+
+</div>
+
 ---
 
 ## 🔁 The Full Contribution Flow, End to End
@@ -182,6 +253,17 @@ from a one-line typo fix to a massive feature added to a project with a million
 Opening a new PR for review feedback is a surprisingly common beginner instinct, and it's
 unnecessary extra noise for the maintainer.)
 
+<div class="callout callout-remember" markdown="1">
+
+**🧠 You can now:**
+
+✓ Fork a repo and explain exactly what that did and didn't give you access to
+✓ Keep a fork in sync with the real project via `upstream`
+✓ Open a pull request that's actually likely to get reviewed and merged quickly
+✓ Find a real `good first issue` and make your first genuine open-source contribution
+
+</div>
+
 ---
 
 <div align="center" markdown="1">

From 72fcb2dcbf274e336fccdd1f07b1b328bccb8ab2 Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 10:31:16 +0530
Subject: [PATCH 08/21] docs(module-04): add Try Its and rebuild into 7
 Disaster Labs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The biggest structural change so far. Previously this module had 3
loosely-formatted "Recovery Scenarios" at the end; reformatted and
expanded into the full "Disaster Labs" structure the course roadmap
promises, with the root README's "Practice Labs" button now pointing
at a real section:

  Lab 1 — Wrong Branch (existing scenario, now hands-on)
  Lab 2 — Lost Commit (existing scenario, now hands-on, with an
          explicit callout on the one thing reflog can't recover)
  Lab 3 — Merge Conflict (new to this module; cross-links Module 05
          for full marker-by-marker mechanics rather than duplicating)
  Lab 4 — Detached HEAD (genuinely new content — this concept didn't
          exist anywhere in the course before)
  Lab 5 — Bad Commit Message (the Amend section's realistic framing)
  Lab 6 — Wrong Commit, Wrong Branch (cherry-pick for this exact
          purpose; cross-links Module 05's fuller treatment)
  Lab 7 — Production Hotfix (new; branches from a tag instead of
          main's tip specifically so unfinished work never ships)

Also added Try It blocks for revert, reset (--soft/--mixed/--hard run
back to back so the difference is visible, not just described),
amend (corrected an earlier draft that wrongly implied the hash stays
the same), rebase (fixed a sequencing bug in an earlier draft where
the branches wouldn't have actually diverged), and reflog.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 04-undo-and-recovery/README.md | 422 +++++++++++++++++++++++++++++++--
 1 file changed, 404 insertions(+), 18 deletions(-)

diff --git a/04-undo-and-recovery/README.md b/04-undo-and-recovery/README.md
index 857648b..e4c9634 100644
--- a/04-undo-and-recovery/README.md
+++ b/04-undo-and-recovery/README.md
@@ -14,6 +14,23 @@ description: "Revert, reset, amend, rebase, and reflog — plus hands-on disaste
 
 </div>
 
+<div class="callout callout-concept" markdown="1">
+
+**By the end of this module you will be able to:**
+
+✓ Choose correctly between `revert` and `reset` based on whether history is shared
+✓ Explain what `--soft`/`--mixed`/`--hard` actually do to your changes, not just recite the flags
+✓ Fix the last commit cleanly with `--amend`, before it's pushed
+✓ Use `reflog` to recover a commit you were sure was gone forever
+✓ Walk through 7 realistic Git disasters and fix every one of them yourself
+
+</div>
+
+> [!CAUTION]
+> This module teaches commands that can discard real work. Do every Try It and Lab below in
+> `~/git-playground` (from [Module 00](../00-introduction/#-set-up-your-safe-sandbox)), never in
+> a repo you actually care about, until the commands are second nature.
+
 ---
 
 ## 📑 In This Module
@@ -23,7 +40,7 @@ description: "Revert, reset, amend, rebase, and reflog — plus hands-on disaste
 - [Amend](#-amend)
 - [Rebase](#-rebase)
 - [Reflog — The Undo Button for Your Undo Button](#-reflog--the-undo-button-for-your-undo-button)
-- [Recovery Scenarios, Worked](#-recovery-scenarios-worked)
+- [💥 Disaster Labs](#-disaster-labs)
 - [Glossary (Module 04 Terms)](#-glossary-module-04-terms)
 
 ---
@@ -53,6 +70,24 @@ graph LR
 **This is why `revert` is the safe choice for anything already pushed and shared** — it doesn't
 rewrite history other people may have already pulled, so nobody's local copy gets confused.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "a deliberate bug" >> app.js
+git add app.js
+git commit -m "feat: add a feature (with a bug)"
+git log --oneline -1          # note this commit's hash
+git revert --no-edit HEAD
+git log --oneline -3
+```
+
+**What should I see?** A brand new commit on top, undoing the previous one — the "buggy" line is
+gone from `app.js`, but both commits are still visible in `git log`. Nothing was erased.
+
+</div>
+
 ---
 
 ## ⏮️ Reset — Rewrite History Locally
@@ -101,6 +136,32 @@ graph TB
 | Safe on a pushed/shared branch? | ✅ Yes | ❌ No — never reset a branch others have pulled |
 | Safe on your own local, unpushed work? | Overkill, but fine | ✅ Yes — this is exactly what it's for |
 
+<div class="danger-badge">⚠️ Destructive command</div>
+
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground` — this one discards changes on purpose)
+
+```bash
+echo "temporary experiment" >> app.js && git add app.js && git commit -m "wip: experiment"
+git reset --soft HEAD~1
+git status            # staged, ready to re-commit — nothing lost
+
+echo "temporary experiment" >> app.js && git add app.js && git commit -m "wip: experiment"
+git reset --mixed HEAD~1
+git status            # the change exists but is UNSTAGED — still nothing lost
+
+echo "temporary experiment" >> app.js && git add app.js && git commit -m "wip: experiment"
+git reset --hard HEAD~1
+git status            # clean — the change is genuinely gone from app.js
+```
+
+**What should I see?** The exact same commit, undone three different ways — `git status` showing
+staged, then unstaged, then nothing at all. That progression *is* the whole lesson: same command,
+one flag, three completely different outcomes for your actual work.
+
+</div>
+
 ---
 
 ## 🔧 Amend
@@ -120,6 +181,25 @@ item, and reseals it as if it was always right. This **only** works cleanly on t
 and **only** before you've pushed it — amending a commit that's already been pushed and pulled by
 someone else creates the exact "rewriting shared history" problem `revert` exists to avoid.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git commit --allow-empty -m "fx: typo in this very message"
+git log --oneline -1          # note this hash
+git commit --amend -m "fix: corrected commit message"
+git log --oneline -1          # the message is fixed...
+git log --oneline -2          # ...but there's still only ONE commit here, not two
+```
+
+**What should I see?** The commit message corrected, and a *different* hash than before —
+`--amend` doesn't edit the old commit in place, it replaces it with a brand new commit object and
+moves the branch pointer to it. `git log --oneline -2` confirms there's still exactly one commit
+here, not a correction stacked on top of the original.
+
+</div>
+
 ---
 
 ## 🌱 Rebase
@@ -162,6 +242,30 @@ graph TB
 > commits, their history and yours now permanently disagree, and reconciling that is a genuinely
 > painful mess. Rebase your own local, not-yet-shared work freely — never rebase `main`.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git switch main
+git switch -c feature/rebase-practice
+echo "feature work" >> feature.js && git add feature.js && git commit -m "feat: feature work"
+
+git switch main
+echo "main moved forward" >> readme.md && git add readme.md && git commit -m "docs: update readme"
+
+git log --oneline --graph --all     # two diverged branches, same starting point
+git switch feature/rebase-practice
+git rebase main                     # replay feature's commit on top of main's latest
+git log --oneline --graph --all     # now a straight line — no merge commit
+```
+
+**What should I see?** Before the rebase, `--graph` shows two separate lines diverging. After,
+one straight line — `feature/rebase-practice`'s commit now sits directly on top of `main`'s, as
+if it had been written after it, not alongside it.
+
+</div>
+
 ---
 
 ## 🕵️ Reflog — The Undo Button for Your Undo Button
@@ -194,36 +298,294 @@ actually gone. It's sitting in Git's internal object database, unreferenced by a
 > reachable commits, 30 for unreachable) — but that's an enormous safety window for "I just did
 > this 30 seconds ago."
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "important work" >> app.js && git add app.js && git commit -m "feat: important work"
+git log --oneline -1              # note this hash
+
+git reset --hard HEAD~1           # "lose" the commit
+git log --oneline -1              # it's really gone from here
+
+git reflog                        # ...but it's right there in the reflog
+git branch recovered HEAD@{1}     # bring it back as a new branch, without touching current HEAD
+git log --oneline recovered -1    # the "lost" commit, fully intact
+```
+
+**What should I see?** The commit missing from `git log` after the reset, then reappearing the
+moment you check `reflog` and recover it onto a new branch — proof that "gone" and "unreferenced"
+aren't the same thing in Git.
+
+</div>
+
 ---
 
-## 🚑 Recovery Scenarios, Worked
+## 💥 Disaster Labs
+
+Everything above was one concept at a time. Real Git panic is never that clean — it's always
+"wait, which command do I even start with." These seven labs are realistic, worked disasters.
+Read the scenario, try to fix it yourself first, then check the solution.
 
-**"I committed to the wrong branch"**
+### 💥 Lab 1 — Wrong Branch
+
+You meant to be on `feature/checkout`, but you committed directly to `main` instead.
+
+<div class="callout callout-tryit" markdown="1">
+
+**Set up the disaster:**
 ```bash
-git log --oneline -1              # note the commit hash
-git reset --hard HEAD~1           # remove it from the wrong branch
-git checkout correct-branch
-git cherry-pick <that-commit-hash>  # apply it to the right one
+git switch main
+echo "this should NOT be on main" >> checkout.js
+git add checkout.js
+git commit -m "feat: checkout logic"
 ```
 
-**"I deleted a branch and need it back"**
+**🎯 Your task:** move that commit off `main` and onto a new `feature/checkout` branch, leaving
+`main` exactly as it was before.
+
+<details>
+<summary>💡 Show solution</summary>
+
 ```bash
-git reflog                        # find the last commit hash the deleted branch pointed to
-git branch recovered-branch <hash>
+git log --oneline -1                    # note the commit hash
+git reset --hard HEAD~1                 # remove it from main
+git switch -c feature/checkout
+git cherry-pick <that-commit-hash>      # bring it onto the new branch
 ```
 
-**"I ran `reset --hard` and lost uncommitted work"**
+`main` is back to clean, and `feature/checkout` has exactly the one commit that belonged there.
+
+</details>
+
+</div>
+
+### 💥 Lab 2 — Lost Commit
+
+You've already done this one above in the Reflog Try It — this lab is the same mechanism, framed
+as a cold-open disaster instead of a guided walkthrough.
+
+<div class="callout callout-tryit" markdown="1">
+
+**Set up the disaster:**
 ```bash
-# Unfortunately: reflog only helps with COMMITTED work.
-# Uncommitted changes discarded by --hard are NOT in reflog — this is the one true "gone" scenario.
-# This is exactly why "commit often, even messy WIP commits" beats "wait until it's perfect."
+echo "three hours of work" >> big-feature.js && git add big-feature.js
+git commit -m "feat: three hours of work"
+git reset --hard HEAD~1
 ```
 
+**🎯 Your task:** get that commit back, without undoing anything else.
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+git reflog
+git branch recovered HEAD@{1}
+```
+
+</details>
+
+</div>
+
 > [!WARNING]
-> That last scenario is the one real gap in Git's safety net: **reflog protects commits, not
-> uncommitted changes.** This is the strongest possible argument for committing early and often —
-> even an ugly `"wip: half done, don't judge"` commit is fully recoverable. Uncommitted work that
-> gets discarded is genuinely gone.
+> **This lab only works because the work was committed first.** If the three hours of work had
+> never been committed — just sitting in the working directory when `reset --hard` ran — reflog
+> cannot help you; uncommitted changes aren't tracked by it at all. That's the one real gap in
+> Git's entire safety net, and it's the single strongest argument for committing early and often,
+> even an ugly `"wip: half done, don't judge"` commit.
+
+### 💥 Lab 3 — Merge Conflict
+
+Create conflicting changes on two branches and resolve them by hand. The full marker-by-marker
+mechanics live in [Module 05](../05-advanced-git/#-merge-conflicts-properly) — this lab
+is the hands-on version.
+
+<div class="callout callout-tryit" markdown="1">
+
+**Set up the disaster:**
+```bash
+git switch main
+echo "const rate = 0.10;" > pricing.js && git add pricing.js && git commit -m "feat: add pricing"
+
+git switch -c feature/pricing-update
+echo "const rate = 0.15;" > pricing.js && git add pricing.js && git commit -m "feat: raise rate"
+
+git switch main
+echo "const rate = 0.12;" > pricing.js && git add pricing.js && git commit -m "feat: adjust rate"
+git merge feature/pricing-update
+```
+
+**🎯 Your task:** Git will stop with a conflict in `pricing.js`. Open it, resolve it to whatever
+value you decide is correct, then complete the merge.
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+# Edit pricing.js by hand — remove the <<<<<<<, =======, >>>>>>> markers,
+# keep whichever line (or a new one) is actually correct
+git add pricing.js
+git commit
+```
+
+Changed your mind entirely? `git merge --abort` backs out to exactly how things were before you
+started the merge — no harm done.
+
+</details>
+
+</div>
+
+### 💥 Lab 4 — Detached HEAD
+
+```bash
+git checkout <some-old-commit-hash>
+```
+
+Git will print a long, slightly alarming note about being in **"detached HEAD"** state.
+
+<div class="callout callout-concept" markdown="1">
+
+**💡 CONCEPT**
+
+Normally `HEAD` points at a *branch*, which points at a commit. Checking out a specific commit
+(or tag) directly skips the branch entirely — `HEAD` now points **straight at a commit**, with
+no branch name attached. You can look around freely, but any new commit you make here has no
+branch pointing at it, and is one `git switch` away from becoming an orphaned, reflog-only commit.
+
+</div>
+
+<div class="callout callout-tryit" markdown="1">
+
+**Set up the scenario:**
+```bash
+git switch main
+git log --oneline -3                    # pick a commit a couple back
+git checkout <that-older-commit-hash>   # "You are in 'detached HEAD' state"
+```
+
+**🎯 Your task:** did you lose any work? Make one commit here out of curiosity, then get safely
+back to `main` — with or without keeping that commit.
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+echo "experiment" >> scratch.js && git add scratch.js && git commit -m "wip: detached experiment"
+
+# To KEEP it: turn this detached commit into a real branch before leaving
+git switch -c rescued-from-detached
+
+# To DISCARD it and just go back:
+git switch main
+```
+
+Nothing was ever truly at risk — the commit you made while detached is sitting in the reflog
+either way, exactly like Lab 2, for as long as the reflog retention window lasts.
+
+</details>
+
+</div>
+
+### 💥 Lab 5 — Bad Commit Message
+
+You already practiced the mechanism in the Amend Try It above — this is the realistic version of
+*when* you'd reach for it.
+
+<div class="callout callout-tryit" markdown="1">
+
+**Set up the disaster:**
+```bash
+git commit --allow-empty -m "fix bug"
+```
+
+**🎯 Your task:** that message tells nobody anything. Fix it — but only because you **haven't
+pushed it yet**.
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+git commit --amend -m "fix: prevent duplicate charge on double-click checkout"
+```
+
+</details>
+
+</div>
+
+### 💥 Lab 6 — Wrong Commit on the Wrong Branch
+
+A teammate's fix landed on `main`, but a `release/v1.2` branch — already out and in use — also
+needs that exact fix, without pulling in anything else `main` has picked up since.
+
+<div class="callout callout-tryit" markdown="1">
+
+**Set up the scenario:**
+```bash
+git switch main
+echo "critical fix" >> auth.js && git add auth.js && git commit -m "fix: critical auth bug"
+git tag v1.2-base                       # pretend this is where release/v1.2 branched from, earlier
+
+git switch -c release/v1.2 v1.2-base    # the release branch, WITHOUT the fix above yet
+```
+
+**🎯 Your task:** get just that one fix onto `release/v1.2`, without merging all of `main`.
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+git log main --oneline -1               # find the fix commit's hash
+git switch release/v1.2
+git cherry-pick <that-commit-hash>
+```
+
+Full cherry-pick mechanics (and the patch-file alternative for when you can't push directly) are
+in [Module 05](../05-advanced-git/#-cherry-pick--patch).
+
+</details>
+
+</div>
+
+### 💥 Lab 7 — Production Hotfix
+
+The realistic, end-to-end version of everything above: `v1.0.0` is live in production. A critical
+bug is found. `main` has unrelated, half-finished work on it that absolutely cannot ship yet.
+
+<div class="callout callout-tryit" markdown="1">
+
+**Set up the scenario:**
+```bash
+git switch main
+git tag v1.0.0                                    # pretend this is the live production release
+echo "unfinished feature" >> big-feature.js && git add big-feature.js
+git commit -m "wip: half-finished feature, not ready to ship"
+```
+
+**🎯 Your task:** fix the production bug based on the `v1.0.0` tag specifically — not on `main`,
+which now has unshippable work on it — then get the fix both released and merged back into
+`main`, without shipping the unfinished feature.
+
+<details>
+<summary>💡 Show solution</summary>
+
+```bash
+git switch -c hotfix/critical-bug v1.0.0     # branch from the TAG, not main
+echo "fix applied" >> auth.js && git add auth.js
+git commit -m "fix: critical production bug"
+
+git tag v1.0.1 hotfix/critical-bug           # the new, fixed release
+git switch main
+git merge hotfix/critical-bug                # bring the fix into main too, unfinished work untouched
+```
+
+`main`'s half-finished feature was never touched — the hotfix branch came from the tag, not from
+`main`'s current tip, so the only thing that reached production was the fix itself.
+
+</details>
+
+</div>
 
 ---
 
@@ -237,6 +599,7 @@ git branch recovered-branch <hash>
 | **Rebase** | Replay commits on top of a new base, producing linear history — only safe on unshared branches |
 | **Reflog** | A log of every place `HEAD` has ever pointed, including "lost" commits — Git's real safety net |
 | **Cherry-pick** | Apply one specific commit from anywhere onto your current branch |
+| **Detached HEAD** | `HEAD` pointing directly at a commit instead of at a branch — new commits here aren't on any branch until you make one |
 
 ---
 
@@ -251,6 +614,29 @@ If #2 didn't immediately bring "uncommitted changes, protected by committing oft
 that's the single most practically important lesson in this entire module — worth re-reading the
 Reflog section above.
 
+<details>
+<summary>🧠 <strong>Quick Check:</strong> You run <code>git checkout <old-commit-hash></code> directly and Git warns you about "detached HEAD." Have you broken something?</summary>
+
+No — you're just looking at history from a point that has no branch name attached to it. You can
+look around freely. The only thing to be careful about is making NEW commits while detached and
+then switching away without turning them into a real branch first (`git switch -c some-name`) —
+otherwise that commit becomes reachable only through the reflog, like any other "lost" commit.
+
+</details>
+
+<div class="callout callout-remember" markdown="1">
+
+**🧠 You can now:**
+
+✓ Choose `revert` vs. `reset` correctly based on whether history is shared
+✓ Explain what `--soft`/`--mixed`/`--hard` each actually do to your changes
+✓ Fix a commit message or forgotten file with `--amend`, before pushing
+✓ Recover a commit you were sure was gone forever, using `reflog`
+✓ Walk through a wrong branch, a lost commit, a merge conflict, a detached HEAD, a bad commit
+  message, a misplaced commit, and a production hotfix — and fix every one of them
+
+</div>
+
 ---
 
 <div align="center" markdown="1">

From a32df79c305c3126b1314699b1c132ea683b4121 Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 10:33:40 +0530
Subject: [PATCH 09/21] docs(module-05): add git bisect, Try Its, SDET-flavored
 CI example
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- New section: git bisect — this concept didn't exist anywhere in
  the course before, despite being an explicit course differentiator.
  Covers the manual good/bad loop and git bisect run for automated,
  test-driven regression hunting, with an SDET scenario tying it
  directly to an actual failing-test investigation
- Try It blocks for .gitignore, cherry-pick, and a real pre-commit
  hook that actually blocks a commit (and can be flipped to allow it)
- The CI/CD example YAML now runs an actual Playwright suite instead
  of a generic "npm test" placeholder, with an SDET callout on why
  CI-only flakiness is usually a real environment difference, not a
  ghost
- A Quick Check on reading merge-conflict markers correctly, plus a
  pointer to Module 04's new hands-on conflict lab

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 05-advanced-git/README.md | 201 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 197 insertions(+), 4 deletions(-)

diff --git a/05-advanced-git/README.md b/05-advanced-git/README.md
index cd90475..f757c26 100644
--- a/05-advanced-git/README.md
+++ b/05-advanced-git/README.md
@@ -14,6 +14,18 @@ description: ".gitignore/.gitattributes, LFS, signed commits, cherry-picking, me
 
 </div>
 
+<div class="callout callout-concept" markdown="1">
+
+**By the end of this module you will be able to:**
+
+✓ Keep junk and secrets out of a repo with `.gitignore` — and know its one real limitation
+✓ Apply one specific commit anywhere you need it with cherry-pick
+✓ Resolve a real merge conflict by hand, calmly
+✓ Hunt down exactly which commit introduced a regression, using `git bisect`
+✓ Explain what a pre-commit hook checks vs. what CI checks, and why teams use both
+
+</div>
+
 ---
 
 ## 📑 In This Module
@@ -24,6 +36,7 @@ description: ".gitignore/.gitattributes, LFS, signed commits, cherry-picking, me
 - [Signing Commits & Tags](#-signing-commits--tags)
 - [Cherry-pick & Patch](#-cherry-pick--patch)
 - [Merge Conflicts, Properly](#-merge-conflicts-properly)
+- [🔍 git bisect — Finding the Commit That Broke Things](#-git-bisect--finding-the-commit-that-broke-things)
 - [Git in CI/CD](#-git-in-cicd)
 - [Git Hooks](#-git-hooks)
 - [Submodules](#-submodules)
@@ -67,6 +80,23 @@ git commit -m "chore: stop tracking .env, add to gitignore"
 > not just removing the file. [gitignore.io](https://www.toptal.com/developers/gitignore) generates
 > solid starter files for any language/framework combination.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+echo "node_modules/" > .gitignore
+echo ".env" >> .gitignore
+mkdir node_modules && touch node_modules/some-package.js
+touch .env
+git status
+```
+
+**What should I see?** Neither `node_modules/` nor `.env` show up anywhere in `git status` —
+not even as untracked. `.gitignore` is working.
+
+</div>
+
 ---
 
 ## 🏷️ .gitattributes
@@ -160,6 +190,27 @@ git checkout release/v1.2
 git cherry-pick <hash-of-the-fix-commit-on-main>
 ```
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+git switch main
+git switch -c release/v1.2              # pretend this is an earlier release branch
+git switch main
+echo "shared fix" >> shared.js && git add shared.js && git commit -m "fix: shared bug"
+git log --oneline -1                    # note this hash
+
+git switch release/v1.2
+git cherry-pick <that-commit-hash>
+git log --oneline -1                    # the fix, now here too — with a NEW hash
+```
+
+**What should I see?** The exact same change, now on `release/v1.2`, but as a brand new commit
+with its own hash — cherry-pick copies the *change*, not the original commit object.
+
+</div>
+
 **Patch** — export changes as a portable file, apply them elsewhere (useful when you can't push
 directly, e.g. emailing a fix to an offline system):
 
@@ -219,6 +270,98 @@ do, and writing the version that's genuinely correct.
 > conflicting changes. A branch merged after 2 days has far fewer conflicts than the same branch
 > merged after 3 weeks.
 
+<details>
+<summary>🧠 <strong>Quick Check:</strong> In a conflict block, which side is <code>HEAD</code> and which is "incoming"?</summary>
+
+```
+<<<<<<< HEAD
+const discountRate = 0.10;
+=======
+const discountRate = 0.15;
+>>>>>>> feature/pricing-update
+```
+
+Everything between `<<<<<<< HEAD` and `=======` is **your current branch's** version (the one
+you're merging *into*). Everything between `=======` and `>>>>>>> branch-name` is the **incoming**
+branch's version (the one you're merging *in*). Here, `0.10` is what's on your current branch;
+`0.15` is what `feature/pricing-update` wants to change it to.
+
+</details>
+
+> [!TIP]
+> Want to actually resolve one hands-on instead of just reading markers? [Module 04's Disaster
+> Lab 3](../04-undo-and-recovery/#-lab-3--merge-conflict) sets up a real conflict and walks
+> through fixing it.
+
+---
+
+## 🔍 git bisect — Finding the Commit That Broke Things
+
+A regression test started failing. You know it passed a week ago, across maybe 40 commits since.
+Reading every diff by hand would work — eventually — but there's a faster, structured way:
+**binary search through your own commit history.**
+
+```bash
+git bisect start
+git bisect bad                          # the current commit is confirmed broken
+git bisect good <hash-of-a-known-good-commit>
+
+# Git checks out a commit exactly halfway between good and bad.
+# Run your test / reproduce the bug, then tell Git the result:
+git bisect good      # this commit is fine — the bug is somewhere later
+# or
+git bisect bad       # this commit already has the bug — it's somewhere earlier
+
+# Git keeps halving the range automatically. Repeat until it reports:
+# "<hash> is the first bad commit"
+
+git bisect reset     # done — return to your original HEAD
+```
+
+### 🎭 The real-life example
+
+It's the "guess a number between 1 and 100" game, except the number is "which of these 40
+commits broke the checkout flow," and each guess costs you running the test suite once instead
+of just saying a number. 40 commits, checked one at a time, is 40 checks worst case. Bisected,
+it's **at most ~6** (log₂ 40) — Git throws away half the remaining suspects every single round.
+
+<div class="callout callout-sdet" markdown="1">
+
+**🧪 SDET SCENARIO**
+
+Your regression suite flags that `test_checkout_total_with_discount` started failing sometime in
+the last 40 commits, but nobody noticed exactly when because it only runs nightly. Instead of
+checking out commits one at a time and re-running the full suite by hand, point bisect at the
+test itself and let it fully automate the search:
+
+```bash
+git bisect start HEAD <known-good-commit>
+git bisect run npm test -- --grep "checkout_total_with_discount"
+```
+
+`git bisect run` checks out each candidate commit, runs that exact command, and uses its **exit
+code** to decide good/bad automatically — 0 means good, nonzero means bad. It reports the exact
+first broken commit without you touching a checkout command yourself. This is one of the
+highest-leverage automation-adjacent Git skills a test engineer can have: it turns "something
+broke somewhere in the last two weeks" from a multi-hour manual hunt into one unattended command.
+
+</div>
+
+> [!TIP]
+> `git bisect` needs a command (or your own judgment) that can cleanly say "good" or "bad" for
+> any commit in the range — which is exactly what an automated test assertion already does. This
+> is why bisect and test automation pair so naturally: the test *is* the oracle bisect needs.
+
+<details>
+<summary>🧠 <strong>Quick Check:</strong> Why does <code>git bisect run</code> need a command, not just a test file path?</summary>
+
+Because bisect decides good/bad purely from the command's **exit code** — it needs something it
+can actually execute and check the result of, not a file to read. `npm test -- --grep "..."` (or
+`pytest -k "..."`, or any equivalent) is a command that exits 0 on pass and nonzero on failure,
+which is exactly the signal bisect's automated loop depends on.
+
+</details>
+
 ---
 
 ## ⚙️ Git in CI/CD
@@ -227,7 +370,7 @@ Git isn't just for humans typing commands — CI/CD pipelines (GitHub Actions, J
 entirely around Git events.
 
 ```yaml
-# .github/workflows/test.yml — a real, minimal example
+# .github/workflows/test.yml — a real, minimal example for a Playwright test suite
 name: Run Tests
 on:
   push:
@@ -241,7 +384,8 @@ jobs:
     steps:
       - uses: actions/checkout@v4    # this step IS a git clone, done for you
       - run: npm install
-      - run: npm test
+      - run: npx playwright install --with-deps
+      - run: npx playwright test
 ```
 
 ### 🎭 The real-life example
@@ -250,6 +394,19 @@ CI/CD is an automatic inspector standing at the shared filing cabinet, who reads
 the moment it's filed (`git push`) and immediately checks it for problems — without waiting for a
 human to ask. `on: push` / `on: pull_request` are literally Git events triggering that inspection.
 
+<div class="callout callout-sdet" markdown="1">
+
+**🧪 SDET SCENARIO**
+
+This is the exact mechanism behind "every pull request automatically runs the test suite" —
+which is what Module 02's GitHub Flow called "CI passes" without showing the YAML behind it.
+`actions/checkout@v4` is doing a `git clone` of the exact commit the PR is built on, and the
+suite then runs against that precise snapshot — which is also why a flaky test that only fails
+in CI, not locally, is so often a real environment/data difference, not a ghost: it's running
+against genuinely different, freshly-cloned code every time.
+
+</div>
+
 ---
 
 ## 🪝 Git Hooks
@@ -283,6 +440,28 @@ before allowing a push).
 > [Husky](https://typicode.github.io/husky/) solve this by making hooks a normal, committed,
 > shareable part of the project instead.
 
+<div class="callout callout-tryit" markdown="1">
+
+**⌨️ TRY IT YOURSELF** (in `~/git-playground`)
+
+```bash
+cat > .git/hooks/pre-commit << 'EOF'
+#!/bin/sh
+echo "🪝 pre-commit hook running..."
+exit 1
+EOF
+chmod +x .git/hooks/pre-commit
+
+echo "test" >> app.js && git add app.js
+git commit -m "this should get blocked"
+```
+
+**What should I see?** The commit is refused — `exit 1` tells Git the hook failed, so the commit
+never happens, even though you ran `git commit` correctly. Change `exit 1` to `exit 0` and try
+again to see it succeed instead.
+
+</div>
+
 ---
 
 ## 🧩 Submodules
@@ -341,6 +520,7 @@ told yet.
 | **Signed commit** | A commit cryptographically verified to actually be from you |
 | **Cherry-pick** | Apply one specific commit from any branch onto your current one |
 | **Merge conflict** | When Git can't automatically reconcile two changes to the same lines — needs human resolution |
+| **`git bisect`** | Binary search through commit history to find exactly which commit introduced a regression |
 | **Git hook** | A script that runs automatically at a specific point in Git's workflow (e.g. before a commit) |
 | **Submodule** | A Git repository nested inside another, tracked as a reference to a specific commit |
 
@@ -350,13 +530,26 @@ told yet.
 
 1. Why doesn't adding a file to `.gitignore` remove it from history if it was already committed?
 2. When would you reach for `cherry-pick` instead of a full `merge`?
-3. What's the actual difference between what a `pre-commit` hook checks vs. what CI checks — and
+3. Why does `git bisect` need a pass/fail signal (like a test's exit code) to actually automate?
+4. What's the actual difference between what a `pre-commit` hook checks vs. what CI checks — and
    why do teams often use both, not just one?
 
-(#3: a `pre-commit` hook is fast, local, and optional (someone COULD skip it with `--no-verify`)
+(#4: a `pre-commit` hook is fast, local, and optional (someone COULD skip it with `--no-verify`)
 — CI is slower, remote, and mandatory. Teams use both: hooks for a fast local safety net, CI as
 the actual, unskippable gate.)
 
+<div class="callout callout-remember" markdown="1">
+
+**🧠 You can now:**
+
+✓ Keep junk and secrets out of a repo with `.gitignore`, and know what it can't undo
+✓ Apply one specific commit anywhere you need it with cherry-pick
+✓ Read merge conflict markers and resolve a conflict calmly
+✓ Hunt down the exact commit that broke something with `git bisect` — manually or automated
+✓ Tell a local hook apart from a CI check, and know why real teams use both
+
+</div>
+
 ---
 
 <div align="center" markdown="1">

From c9c4d7b7c46725430917d08ea9f8dd5dbee213da Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 10:34:09 +0530
Subject: [PATCH 10/21] docs(module-06): add bisect and detached HEAD to the
 review pass

Module 06 is a review of earlier modules, so this adds coverage for
the two concepts Module 05/04 just gained (bisect, detached HEAD)
that weren't in the checklist or exam-style questions yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 06-certification-prep/README.md | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/06-certification-prep/README.md b/06-certification-prep/README.md
index f705ec9..e5de5c0 100644
--- a/06-certification-prep/README.md
+++ b/06-certification-prep/README.md
@@ -99,8 +99,14 @@ module before moving on — don't just re-read, actually re-explain it to yourse
 **Advanced ([Module 05](../05-advanced-git/))**
 - [ ] `.gitignore` vs. `.gitattributes` — different jobs entirely
 - [ ] What a merge conflict actually is, and how to resolve one calmly
+- [ ] How `git bisect` finds a regression, and why it needs a pass/fail signal to automate
 - [ ] The difference between a local hook and a CI check
 
+**Disaster Labs ([Module 04](../04-undo-and-recovery/#-disaster-labs))**
+- [ ] Moving a commit off the wrong branch with `cherry-pick`
+- [ ] What "detached HEAD" actually means, and why it isn't automatically dangerous
+- [ ] Why a production hotfix branches from a tag, not from `main`'s current tip
+
 ---
 
 ## ⚡ Rapid-Fire Concept Check
@@ -151,6 +157,21 @@ you use, and why?**
 > created — Git just moves `main`'s pointer forward to match, since there's no divergent history
 > to actually combine.
 
+**Q: A regression test started failing sometime in the last 30 commits, but nobody knows which
+one. Walk through how you'd find it.**
+> `git bisect start`, mark the current commit `bad` and a known-working older commit `good`. Git
+> checks out the midpoint; run the test, report `good` or `bad`, and Git halves the remaining
+> range each time — at most ~5 checks for 30 commits, instead of up to 30. If the check is itself
+> a test command, `git bisect run <command>` automates the entire loop using the command's exit
+> code, with no manual checkout steps at all.
+
+**Q: You run `git checkout` on an old commit hash directly and Git warns about "detached HEAD."
+Is this a problem?**
+> Not by itself — you're just viewing history with no branch name attached to `HEAD`. The only
+> real risk is making a new commit here and switching away without turning it into a real branch
+> first (`git switch -c name`); until then it's one `git switch` away from being reachable only
+> through the reflog.
+
 ---
 
 ## 🗣️ The "Explain It Out Loud" Test

From c89f3ea3ba6d7f8eeee96894e01ab1fc898a365f Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 10:35:17 +0530
Subject: [PATCH 11/21] docs(module-07): rebuild capstone into the full Git
 Mastery Challenge

Previous capstone was a flat 10-step list with no reveal progression
and no bisect/reflog coverage. Rebuilt per the course's own stated
philosophy (struggle first, then hints, then partial, then complete,
then explanation):

- Scenario given first, hints hidden separately from the solution
- Added rebase-before-PR, reflog recovery, and git bisect as explicit
  steps (the original 10 steps predated this branch's bisect/detached
  HEAD additions)
- A dedicated "why these 10 steps" explanation tying each one back to
  the module that taught it
- Added 2 quiz questions (detached HEAD, bisect run) to the existing
  20-question bank

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 07-exercises-and-study-plan/README.md | 148 ++++++++++++++++++++++----
 1 file changed, 129 insertions(+), 19 deletions(-)

diff --git a/07-exercises-and-study-plan/README.md b/07-exercises-and-study-plan/README.md
index 19d528d..de8c637 100644
--- a/07-exercises-and-study-plan/README.md
+++ b/07-exercises-and-study-plan/README.md
@@ -14,6 +14,16 @@ description: "Progressive exercises, a capstone Git Mastery Challenge, and a 20-
 
 </div>
 
+<div class="callout callout-concept" markdown="1">
+
+**By the end of this module you will be able to:**
+
+✓ Run the full branch → commit → PR → merge → recover → bisect loop from memory, not notes
+✓ Self-assess exactly which modules need a second pass, using the quiz bank
+✓ Point at a real, well-organized Git history as proof you know this, not just a certificate
+
+</div>
+
 ---
 
 ## 📑 In This Module
@@ -21,7 +31,7 @@ description: "Progressive exercises, a capstone Git Mastery Challenge, and a 20-
 - [Syllabus — The Whole Course, One Page](#-syllabus--the-whole-course-one-page)
 - [Study Plan — Suggested Pacing](#-study-plan--suggested-pacing)
 - [Exercises — Progressive, By Module](#-exercises--progressive-by-module)
-- [Capstone Exercise — One Continuous Scenario](#-capstone-exercise--one-continuous-scenario)
+- [🏆 Git Mastery Challenge (Capstone)](#-git-mastery-challenge-capstone)
 - [Quiz Bank](#-quiz-bank)
 - [You're Done — Now What](#-youre-done--now-what)
 
@@ -118,27 +128,125 @@ gantt
 
 ---
 
-## 🎯 Capstone Exercise — One Continuous Scenario
+## 🏆 Git Mastery Challenge (Capstone)
+
+This is the final "prove you can actually use Git" exercise — one continuous scenario using
+everything from Modules 00–05 together, the single best way to confirm the concepts connected
+into one coherent mental model instead of staying isolated facts.
+
+<div class="callout callout-challenge" markdown="1">
+
+**🎯 THE SCENARIO**
+
+You're working on a small automation project. Starting from a fresh GitHub repo, you need to:
+
+1. Set up `.gitignore` properly from the start
+2. Create a feature branch, make commits, push it
+3. Open a real Pull Request and merge it via GitHub
+4. Deliberately create a merge conflict between two branches, and resolve it
+5. Rebase a local feature branch onto `main` before opening its PR
+6. Make a commit with a typo in the message, `--amend` it before pushing
+7. Push a bad commit, then `revert` it cleanly on a branch that's already shared
+8. Deliberately "lose" a commit with `reset --hard`, then recover it with `reflog`
+9. Use `git bisect` to identify exactly which of several commits introduced a deliberately
+   planted bug
+10. Tag a release, add a `pre-commit` hook, and write a clean, genuinely good README
+
+Don't look at the hints yet. Try the whole thing from memory first — that's the actual point.
+
+</div>
+
+<details>
+<summary>💡 Stuck? Hints (not the solution)</summary>
+
+- Steps 1–4 are the GitHub Flow loop from Module 02, plus the conflict lab from Module 04
+- Step 5: rebase your feature branch *before* opening the PR, not after — that's the whole reason
+  teams rebase local branches instead of `main`
+- Step 8 only works if the "lost" commit was actually committed first — `reflog` can't rescue
+  uncommitted changes, by design
+- Step 9: plant a bug in one commit among several on purpose, mark the current state `bad`, an
+  earlier known-good commit `good`, and either bisect manually or write one script you can hand
+  to `git bisect run`
+
+</details>
+
+<details>
+<summary>💡 Partial solution — steps 1–4</summary>
+
+```bash
+git init my-automation-project && cd my-automation-project
+echo "node_modules/" > .gitignore && echo ".env" >> .gitignore
+git add .gitignore && git commit -m "chore: initial gitignore"
+git remote add origin <your-empty-github-repo-url>
+git push -u origin main
+
+git switch -c feature/first-change
+echo "feature work" >> feature.js && git add feature.js
+git commit -m "feat: first change"
+git push -u origin feature/first-change
+# Open the PR on GitHub, merge it, then locally:
+git switch main && git pull
+```
+
+</details>
+
+<details>
+<summary>💡 Complete solution — all 10 steps</summary>
+
+Steps 5–10, continuing from the state above:
+
+```bash
+# 5. Rebase before opening a PR
+git switch -c feature/second-change
+echo "more work" >> feature2.js && git add feature2.js && git commit -m "feat: second change"
+git fetch origin && git rebase origin/main
+
+# 6. Amend a typo'd message before pushing
+git commit --allow-empty -m "fx: typo"
+git commit --amend -m "fix: corrected message"
+
+# 7. Revert a bad commit already shared
+git push -u origin feature/second-change
+echo "oops" >> feature2.js && git add feature2.js && git commit -m "feat: broken change" && git push
+git revert --no-edit HEAD && git push
+
+# 8. Lose and recover a commit
+echo "important" >> feature2.js && git add feature2.js && git commit -m "feat: important work"
+git reset --hard HEAD~1
+git reflog
+git branch recovered HEAD@{1}
+
+# 9. Bisect a planted regression (simplified — exit code stands in for a real test)
+git bisect start
+git bisect bad HEAD
+git bisect good <some-earlier-good-commit>
+# Git checks out a midpoint each round — inspect it, then:
+git bisect good   # or: git bisect bad
+# ...until Git reports the first bad commit, then:
+git bisect reset
+
+# 10. Tag, hook, README
+git tag -a v1.0.0 -m "First release"
+echo '#!/bin/sh\necho "checking..."' > .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
+# Write the README by hand — this part doesn't have a shortcut
+```
+
+</details>
+
+<details>
+<summary>💡 Explanation — why these 10 steps specifically</summary>
 
-Do this as one continuous exercise, using everything from Modules 00–05 together — this is the
-single best way to confirm the concepts actually connected into one coherent mental model, not
-just isolated facts:
+Every step maps to a real failure mode this course spent a whole module on: steps 1–4 are the
+daily loop (Modules 01–02), step 5 is the "clean up before review" habit senior engineers expect,
+steps 6–8 are Module 04's safety net exercised for real, step 9 is the single highest-leverage
+debugging skill this course teaches, and step 10 is "does your own repo practice what this course
+preaches" — the same bar [Module 04's Disaster Labs](../04-undo-and-recovery/#-disaster-labs) and
+this very repository hold themselves to.
 
-1. Create a new GitHub repo for a small project (anything — a script, a simple app, doesn't
-   matter)
-2. Set up `.gitignore` properly from the start
-3. Work in feature branches for every change — never commit directly to `main`
-4. Push every branch, open a real Pull Request for each one (even solo, reviewing your own PR is
-   good practice), merge via GitHub
-5. Deliberately create one merge conflict between two branches and resolve it
-6. Make one commit with a typo in the message, `--amend` it before pushing
-7. Push a bad commit, then `revert` it cleanly
-8. Tag a "release" with an annotated tag
-9. Add a `pre-commit` hook that checks something trivial
-10. Write a clean, genuinely good README for the finished project
+</details>
 
-If you can do all 10 steps without needing to look anything up beyond a quick syntax check, you've
-completed this course in the way that actually matters — not "read about Git" but "can use Git."
+If you can do all 10 steps — even needing the hints, not the full solution — you've completed
+this course in the way that actually matters: not "read about Git" but "can use Git."
 
 ---
 
@@ -166,6 +274,8 @@ Quick self-test — cover the answers, go through all 20, then check yourself.
 18. What's the actual job of `.gitignore` vs `.gitattributes`?
 19. What are the three sections inside a merge conflict's `<<<<<<<`/`=======`/`>>>>>>>` markers?
 20. What's the difference between a Git hook and a CI/CD check?
+21. What does "detached HEAD" actually mean, and is it dangerous by itself?
+22. Why does `git bisect run` need a command with an exit code, not just a file to look at?
 
 **Answers:** every single one of these is directly covered in Modules 00–05 — if any felt shaky,
 that's your specific, targeted review list. Don't re-read whole modules; jump straight to the

From 6a2989cb7166fe85501bd8c06bdecd10534fcd9b Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 10:36:54 +0530
Subject: [PATCH 12/21] chore: make the repo practice what Module 03 teaches
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Module 03 now explicitly invites readers to open a real PR against
this repo as practice — this backs that invitation with the actual
mechanism:

- CONTRIBUTING.md: the fork/branch/PR flow specific to this repo,
  plus what makes a good contribution here (preserve the teaching
  philosophy, hands-on over read-only, dummy data only)
- CODE_OF_CONDUCT.md: short, specific to a learning-resource context
- .github/ISSUE_TEMPLATE/: bug-or-broken-content and content-suggestion
- .github/pull_request_template.md
- .github/scripts/check_docs.py: formalizes the exact fence-balance /
  broken-link / unbalanced-div check I've been running by hand
  throughout this branch, now runnable by anyone locally
- .github/workflows/validate-docs.yml: runs that same script in CI on
  every push/PR — this repo had zero CI before this commit

Verified: the script correctly catches a real broken link I
introduced in CONTRIBUTING.md's first draft (a forward-reference to
this exact workflow file, before it existed) and reports clean now;
the workflow YAML parses correctly via Ruby's YAML parser (no
jekyll/actions runner available locally to go further than that).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 .../ISSUE_TEMPLATE/bug-or-broken-content.md   | 15 +++++
 .github/ISSUE_TEMPLATE/content-suggestion.md  | 15 +++++
 .github/pull_request_template.md              | 14 ++++
 .github/scripts/check_docs.py                 | 67 +++++++++++++++++++
 .github/workflows/validate-docs.yml           | 14 ++++
 CODE_OF_CONDUCT.md                            | 18 +++++
 CONTRIBUTING.md                               | 58 ++++++++++++++++
 7 files changed, 201 insertions(+)
 create mode 100644 .github/ISSUE_TEMPLATE/bug-or-broken-content.md
 create mode 100644 .github/ISSUE_TEMPLATE/content-suggestion.md
 create mode 100644 .github/pull_request_template.md
 create mode 100644 .github/scripts/check_docs.py
 create mode 100644 .github/workflows/validate-docs.yml
 create mode 100644 CODE_OF_CONDUCT.md
 create mode 100644 CONTRIBUTING.md

diff --git a/.github/ISSUE_TEMPLATE/bug-or-broken-content.md b/.github/ISSUE_TEMPLATE/bug-or-broken-content.md
new file mode 100644
index 0000000..d65de01
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-or-broken-content.md
@@ -0,0 +1,15 @@
+---
+name: Bug / Broken Content
+about: Report a broken link, a wrong command, or an outdated explanation
+title: "[Module 0X] Short description"
+labels: bug
+---
+
+**Where**
+Which module/file, and roughly which section (a heading or a direct link is perfect)?
+
+**What's wrong**
+What does the course currently say, and why is it wrong or broken?
+
+**What it should say instead**
+If you know the fix, say it here — if not, that's fine too.
diff --git a/.github/ISSUE_TEMPLATE/content-suggestion.md b/.github/ISSUE_TEMPLATE/content-suggestion.md
new file mode 100644
index 0000000..3c29f21
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/content-suggestion.md
@@ -0,0 +1,15 @@
+---
+name: Content Suggestion
+about: Suggest a missing concept, a clearer explanation, or a new Try It / lab
+title: "[Suggestion] Short description"
+labels: enhancement
+---
+
+**What's missing or could be clearer**
+A concept, command, or scenario you think belongs in the course.
+
+**Where it would fit**
+Which existing module, if any? If it's genuinely new ground, say that instead.
+
+**Why it's worth adding**
+What would a learner be able to do afterward that they can't today?
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..601ede9
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,14 @@
+## What
+
+<!-- What changed, in one or two sentences. -->
+
+## Why
+
+<!-- What was wrong, missing, or unclear before this change. -->
+
+## Checklist
+
+- [ ] Ran `python3 .github/scripts/check_docs.py` locally — no issues
+- [ ] Kept existing examples/analogies/labs intact (or has a good reason not to)
+- [ ] Used dummy data only — no real names, emails, credentials, or URLs
+- [ ] New commands/concepts include a `callout-tryit` block the reader can actually run
diff --git a/.github/scripts/check_docs.py b/.github/scripts/check_docs.py
new file mode 100644
index 0000000..43253fd
--- /dev/null
+++ b/.github/scripts/check_docs.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+"""Validate this repo's own Markdown: balanced code fences, resolvable
+relative links, and balanced custom HTML components (<div>/<details>).
+Used locally and by .github/workflows/validate-docs.yml in CI.
+"""
+import os
+import re
+import sys
+import glob
+
+REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+
+def check_file(path):
+    problems = []
+    with open(path, encoding="utf-8") as fh:
+        content = fh.read()
+
+    fence_count = len(re.findall(r"^```", content, re.MULTILINE))
+    if fence_count % 2 != 0:
+        problems.append(f"Unbalanced code fences ({fence_count} found)")
+
+    div_open = len(re.findall(r"<div\b", content))
+    div_close = content.count("</div>")
+    if div_open != div_close:
+        problems.append(f"Unbalanced <div> tags ({div_open} open, {div_close} close)")
+
+    details_open = content.count("<details>")
+    details_close = content.count("</details>")
+    if details_open != details_close:
+        problems.append(f"Unbalanced <details> tags ({details_open} open, {details_close} close)")
+
+    for match in re.finditer(r"\]\(([^)]+)\)", content):
+        link = match.group(1)
+        if link.startswith(("http://", "https://", "mailto:", "#")):
+            continue
+        path_part = link.split("#")[0]
+        if not path_part:
+            continue
+        resolved = os.path.normpath(os.path.join(os.path.dirname(path), path_part))
+        if not os.path.exists(resolved):
+            problems.append(f"Broken relative link: {link}")
+
+    return problems
+
+
+def main():
+    md_files = glob.glob(os.path.join(REPO_ROOT, "**", "*.md"), recursive=True)
+    all_problems = {}
+    for f in md_files:
+        problems = check_file(f)
+        if problems:
+            all_problems[os.path.relpath(f, REPO_ROOT)] = problems
+
+    if not all_problems:
+        print(f"OK — {len(md_files)} Markdown files checked, no issues found.")
+        return 0
+
+    for f, problems in sorted(all_problems.items()):
+        for p in problems:
+            print(f"{f}: {p}")
+    print(f"\n{sum(len(p) for p in all_problems.values())} issue(s) in {len(all_problems)} file(s).")
+    return 1
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/.github/workflows/validate-docs.yml b/.github/workflows/validate-docs.yml
new file mode 100644
index 0000000..1305d9d
--- /dev/null
+++ b/.github/workflows/validate-docs.yml
@@ -0,0 +1,14 @@
+name: Validate Docs
+
+on:
+  push:
+    branches: [main]
+  pull_request:
+    branches: [main]
+
+jobs:
+  check-docs:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - run: python3 .github/scripts/check_docs.py
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..9bbd820
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,18 @@
+---
+title: "Code of Conduct"
+description: "A short, genuine code of conduct for contributing to GIT Tutorials."
+---
+
+# Code of Conduct
+
+This is a learning resource. People contributing here are, by definition, often still learning —
+including the beginner fixing their first-ever typo PR.
+
+- Be patient with questions that seem basic. Everyone in this course's own target audience was
+  once the person who didn't know what a staging area was.
+- Critique the content, not the person. "This command is wrong" is useful; anything aimed at the
+  contributor instead of the contribution isn't.
+- No harassment, discrimination, or personal attacks, in issues, PRs, or comments — no exceptions.
+
+Violations can be reported by opening an issue or contacting the maintainer directly through
+GitHub. Reports are taken seriously and handled privately.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..cd8a46b
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,58 @@
+---
+title: "Contributing"
+description: "How to contribute to GIT Tutorials — the exact fork/clone/branch/PR flow this course itself teaches in Module 03."
+---
+
+# Contributing to GIT Tutorials
+
+This course explicitly invites you to practice [Module 03](./03-contributing-to-open-source/)
+on this very repo — a typo fix, a broken link, an outdated command, or a clearer explanation are
+all genuinely welcome. This file is the specific version of the generic flow that module teaches.
+
+## The flow
+
+1. **Fork** this repo (top right on GitHub)
+2. **Clone your fork**, then add this repo as `upstream`:
+   ```bash
+   git clone git@github.com:yourusername/git-tutorials.git
+   cd git-tutorials
+   git remote add upstream git@github.com:ghanendra-sdet/git-tutorials.git
+   ```
+3. **Branch** for your specific change — never commit directly to `main`:
+   ```bash
+   git switch -c fix/broken-link-in-module-04
+   ```
+4. **Make the change.** Keep it small and focused — one fix per PR, per
+   [Module 03](./03-contributing-to-open-source/#-send-a-pull-request)'s own advice.
+5. **Check it** before pushing — see Content Checks below.
+6. **Push to your fork, open a PR** against `ghanendra-sdet/git-tutorials` `main`.
+
+## Content checks before opening a PR
+
+This repo validates two things automatically in CI on every PR (see
+[`.github/workflows/validate-docs.yml`](./.github/workflows/validate-docs.yml)):
+
+- Every fenced code block (` ``` `) has a matching close
+- Every relative Markdown link resolves to a real file
+
+You can run the same check locally before pushing:
+
+```bash
+python3 .github/scripts/check_docs.py
+```
+
+## What makes a good contribution here specifically
+
+- **Preserve the teaching philosophy.** Every section pairs a real-life analogy with a runnable
+  command — if you're adding a new concept, keep that pairing, not just the command.
+- **Hands-on over read-only.** If you're adding a new command or concept, a `callout-tryit` block
+  the reader can actually run beats another paragraph of explanation.
+- **Dummy data only.** Every example uses placeholder names/emails/URLs — never anything that
+  looks like a real credential or real personal data.
+- **Don't remove existing examples, analogies, or labs** to make room for new ones unless they're
+  factually wrong — this course's own ground rule for itself.
+
+## Reporting an issue instead of fixing it yourself
+
+That's genuinely fine too — open an issue using the templates under **New Issue**. A clearly
+described problem is still a real contribution.

From e38d21b2279e3bc22c46c6f2a2fa2adbd50ee357 Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 13:21:04 +0530
Subject: [PATCH 13/21] chore: gitignore local Jekyll build output

Needed this to actually test the branch locally with a real Jekyll
server (_site/ was showing as untracked). GitHub Pages builds the
site itself in CI, so this output never needed to be committed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 .gitignore | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 .gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0a4b93a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+# Jekyll build output (local preview only — GitHub Pages builds this itself)
+_site/
+.jekyll-cache/
+.jekyll-metadata
+.sass-cache/

From df075a4e9ce425b27a4fcb429619d7d4373a0542 Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Mon, 5 Oct 2026 13:26:19 +0530
Subject: [PATCH 14/21] fix: add _config.yml and restore the head-custom.html
 include
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Found while actually running this branch locally for the first time
(previous validation was curl-against-production + static analysis
only, since the system Ruby was too old to run Jekyll at all).

Two real, separate issues this surfaced:

1. No _config.yml existed. Jekyll doesn't apply a layout to a page
   unless told to, so any page WITH front matter (every page now has
   some, added for SEO) rendered as a bare content fragment locally —
   no header, no CSS, no nav. Added defaults: to force layout: default
   site-wide, plus jekyll-readme-index + readme_index.with_frontmatter
   (the plugin otherwise skips README files that have front matter,
   which is now all of them).

2. _includes/head-custom.html — the file this whole design-system
   branch builds on — was never actually being included by minima
   locally. Confirmed via curl that it DOES run on the live production
   site today, so GitHub Pages' build is doing this via its own
   supported-theme customization behavior (or a different pinned
   minima version) rather than minima's current published gem, which
   doesn't have the include line. Added an explicit local override of
   minima's head.html (copied from the installed gem, one line added)
   so this doesn't depend on that implicit behavior — matches GitHub's
   own documented pattern for this exact customization point.

Verified locally end-to-end after both fixes: per-page <title> tags
differ correctly, custom.css/custom.js/mermaid now load, README files
serve as directory indexes, and the doc-validation script still
passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
 _config.yml         | 22 ++++++++++++++++++++++
 _includes/head.html | 12 ++++++++++++
 2 files changed, 34 insertions(+)
 create mode 100644 _config.yml
 create mode 100644 _includes/head.html

diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..e244a0a
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1,22 @@
+theme: minima
+markdown: kramdown
+
+plugins:
+  - jekyll-readme-index
+
+# Every README.md in this repo has front matter (added for SEO titles/
+# descriptions) — the plugin ignores README files with front matter
+# unless this is explicitly enabled.
+readme_index:
+  with_frontmatter: true
+
+defaults:
+  - scope:
+      path: ""
+    values:
+      layout: "default"
+
+exclude:
+  - Gemfile
+  - Gemfile.lock
+  - vendor
diff --git a/_includes/head.html b/_includes/head.html
new file mode 100644
index 0000000..ceb73c8
--- /dev/null
+++ b/_includes/head.html
@@ -0,0 +1,12 @@
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  {%- seo -%}
+  <link rel="stylesheet" href="{{ "/assets/main.css" | relative_url }}">
+  {%- feed_meta -%}
+  {%- if jekyll.environment == 'production' and site.google_analytics -%}
+    {%- include google-analytics.html -%}
+  {%- endif -%}
+  {%- include head-custom.html -%}
+</head>

From e18bafa81b3adba9183b47a9262d04d7eb585d77 Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Tue, 6 Oct 2026 00:33:05 +0530
Subject: [PATCH 15/21] fix: restore jekyll-relative-links plugin

Lost when I switched off the github-pages gem earlier (its old
pinned Liquid version was incompatible with modern Ruby). Without
it, any .md-style link in markdown content 404s on the built site
instead of resolving to the real .html output - confirmed this broke
the cheatsheet's own cross-link to itself.
---
 _config.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/_config.yml b/_config.yml
index e244a0a..f2994d7 100644
--- a/_config.yml
+++ b/_config.yml
@@ -3,6 +3,7 @@ markdown: kramdown
 
 plugins:
   - jekyll-readme-index
+  - jekyll-relative-links
 
 # Every README.md in this repo has front matter (added for SEO titles/
 # descriptions) — the plugin ignores README files with front matter

From 8db4191dd07b99fb6c98480242f1db5e9b0a0976 Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Tue, 6 Oct 2026 00:37:15 +0530
Subject: [PATCH 16/21] feat: rebuild design system to match the dark
 cheatsheet look

Replaces minima's default theme chrome entirely - custom header/
footer layout, dark-first zinc/emerald/amber/rose palette matching
resources/interactive-cheatsheet.html's existing look instead of
inheriting minima's plain blog styling. Also moves the 'On this
page' TOC into a sticky left sidebar on desktop instead of an inline
box that pushed content down, and adds scroll-padding-top/scroll-
margin-top so jumping to a heading no longer hides it behind the
sticky header.
---
 _includes/head.html   |   1 -
 _layouts/default.html |  43 +++
 assets/css/custom.css | 648 ++++++++++++++++++++++++++++--------------
 3 files changed, 472 insertions(+), 220 deletions(-)
 create mode 100644 _layouts/default.html

diff --git a/_includes/head.html b/_includes/head.html
index ceb73c8..4268e38 100644
--- a/_includes/head.html
+++ b/_includes/head.html
@@ -3,7 +3,6 @@
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   {%- seo -%}
-  <link rel="stylesheet" href="{{ "/assets/main.css" | relative_url }}">
   {%- feed_meta -%}
   {%- if jekyll.environment == 'production' and site.google_analytics -%}
     {%- include google-analytics.html -%}
diff --git a/_layouts/default.html b/_layouts/default.html
new file mode 100644
index 0000000..53886f6
--- /dev/null
+++ b/_layouts/default.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="{{ page.lang | default: site.lang | default: "en" }}">
+
+  {%- include head.html -%}
+
+  <body data-baseurl="{{ site.baseurl }}">
+    <div class="site-shell">
+      <header class="site-header">
+        <div class="site-header-inner">
+          <a class="site-logo" href="{{ "/" | relative_url }}">
+            🐙 GIT Tutorials
+          </a>
+          <nav class="site-nav" aria-label="Primary">
+            <a href="{{ "/resources/interactive-cheatsheet.html" | relative_url }}">Cheatsheet</a>
+            <a href="{{ "/resources/command-recall-drill.html" | relative_url }}">Drill</a>
+            <a href="https://github.com/ghanendra-sdet/git-tutorials" target="_blank" rel="noopener noreferrer">GitHub</a>
+          </nav>
+        </div>
+      </header>
+
+      <main class="page-wrap" aria-label="Content">
+        <div class="page-grid">
+          <aside class="toc-sidebar" id="toc-sidebar-mount" aria-label="Table of contents"></aside>
+          <div class="page-content">
+            {{ content }}
+          </div>
+        </div>
+      </main>
+
+      <footer class="site-footer">
+        <div class="footer-links">
+          <a href="{{ "/" | relative_url }}">🏠 Course Home</a>
+          <a href="{{ "/resources/cheatsheet.md" | relative_url }}">⚡ Cheat Sheet</a>
+          <a href="{{ "/CONTRIBUTING.md" | relative_url }}">🤝 Contributing</a>
+          <a href="https://github.com/ghanendra-sdet/git-tutorials" target="_blank" rel="noopener noreferrer">📦 GitHub</a>
+          <a href="#top">↑ Back to top</a>
+        </div>
+        GIT Tutorials — by Ghanendra Yadav — MIT Licensed
+      </footer>
+    </div>
+  </body>
+
+</html>
diff --git a/assets/css/custom.css b/assets/css/custom.css
index b1444bf..0831934 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -1,173 +1,262 @@
 /* ==========================================================================
-   GIT Tutorials — Design System
-   Modern + Technical + Friendly + Educational. Not a marketing site.
-   Light/dark tokens, callout components, nav chrome, accessibility.
+   GIT Tutorials — Design System v2
+   Matches resources/interactive-cheatsheet.html's existing look (dark-first,
+   zinc/emerald/amber/rose, rounded cards, generous spacing) instead of
+   minima's default theme. This file now owns ALL visual styling — minima's
+   own compiled CSS is no longer linked (see _layouts/default.html).
    ========================================================================== */
 
 /* -------------------------------------------------------------------------
-   1. Color tokens — light (default) and dark
+   1. Color tokens — dark is the primary, designed palette. Light is a
+   deliberately-designed complement, not an inversion.
    ------------------------------------------------------------------------- */
 :root {
-  --color-primary: #2563eb;
-  --color-primary-strong: #1d4ed8;
-  --color-success: #16a34a;
-  --color-success-bg: #d4edda;
-  --color-warning: #d97706;
-  --color-warning-bg: #fff3cd;
-  --color-danger: #dc2626;
-  --color-danger-bg: #ffe2e2;
-  --color-info: #0891b2;
-  --color-info-bg: #cfe2ff;
-  --color-purple: #7c3aed;
-  --color-purple-bg: #ede9fe;
-
-  --color-bg: #ffffff;
-  --color-surface: #f6f8fa;
-  --color-surface-raised: #ffffff;
-  --color-text: #1f2328;
-  --color-muted: #57606a;
-  --color-border: #d0d7de;
-  --color-code-bg: #f6f8fa;
-  --color-link: #0969da;
+  --zinc-50:  #fafafa;
+  --zinc-100: #f4f4f5;
+  --zinc-200: #e4e4e7;
+  --zinc-300: #d4d4d8;
+  --zinc-400: #a1a1aa;
+  --zinc-500: #71717a;
+  --zinc-600: #52525b;
+  --zinc-700: #3f3f46;
+  --zinc-800: #27272a;
+  --zinc-900: #18181b;
+  --zinc-950: #09090b;
+
+  --emerald-400: #34d399;
+  --emerald-500: #10b981;
+  --emerald-600: #059669;
+  --amber-400:   #fbbf24;
+  --amber-500:   #f59e0b;
+  --amber-600:   #d97706;
+  --rose-400:    #fb7185;
+  --rose-500:    #f43f5e;
+  --rose-600:    #e11d48;
+  --sky-400:     #38bdf8;
+  --sky-500:     #0ea5e9;
+  --sky-600:     #0284c7;
+  --violet-400:  #a78bfa;
+  --violet-500:  #8b5cf6;
+  --violet-600:  #7c3aed;
 
   --radius-md: 10px;
   --radius-lg: 14px;
-  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
-  --shadow-md: 0 4px 14px rgba(0, 0, 0, 0.08);
-}
-
-@media (prefers-color-scheme: dark) {
-  :root:not([data-theme="light"]) {
-    --color-bg: #0d1117;
-    --color-surface: #161b22;
-    --color-surface-raised: #1c222b;
-    --color-text: #e6edf3;
-    --color-muted: #9198a1;
-    --color-border: #30363d;
-    --color-code-bg: #161b22;
-    --color-link: #58a6ff;
-
-    --color-success-bg: #10351f;
-    --color-warning-bg: #3a2c05;
-    --color-danger-bg: #3a0d0d;
-    --color-info-bg: #0d2a3d;
-    --color-purple-bg: #251a40;
-    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
-    --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.5);
-  }
+  --radius-xl: 18px;
+  --radius-full: 999px;
+  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
 }
 
+/* Dark (default) */
+:root,
 :root[data-theme="dark"] {
-  --color-bg: #0d1117;
-  --color-surface: #161b22;
-  --color-surface-raised: #1c222b;
-  --color-text: #e6edf3;
-  --color-muted: #9198a1;
-  --color-border: #30363d;
-  --color-code-bg: #161b22;
-  --color-link: #58a6ff;
-
-  --color-success-bg: #10351f;
-  --color-warning-bg: #3a2c05;
-  --color-danger-bg: #3a0d0d;
-  --color-info-bg: #0d2a3d;
-  --color-purple-bg: #251a40;
+  --color-bg: var(--zinc-950);
+  --color-bg-elevated: var(--zinc-900);
+  --color-surface: rgba(39, 39, 42, 0.6);   /* zinc-800/60 */
+  --color-surface-hover: var(--zinc-900);
+  --color-border: var(--zinc-800);
+  --color-border-strong: var(--zinc-700);
+  --color-text: var(--zinc-200);
+  --color-text-strong: #ffffff;
+  --color-muted: var(--zinc-400);
+  --color-muted-2: var(--zinc-500);
+  --color-link: var(--emerald-400);
+  --color-link-hover: var(--emerald-500);
+  --color-code-bg: var(--zinc-950);
+  --color-primary: var(--emerald-500);
+  --color-primary-text: var(--zinc-950);
   --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
-  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.5);
+  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.5);
+  --scrollbar-track: var(--zinc-900);
+  --scrollbar-thumb: var(--zinc-700);
+}
+
+/* Light — only when explicitly chosen (manual toggle) or system prefers it
+   AND the user hasn't manually picked dark */
+@media (prefers-color-scheme: light) {
+  :root:not([data-theme="dark"]) {
+    --color-bg: #ffffff;
+    --color-bg-elevated: var(--zinc-50);
+    --color-surface: var(--zinc-50);
+    --color-surface-hover: var(--zinc-100);
+    --color-border: var(--zinc-200);
+    --color-border-strong: var(--zinc-300);
+    --color-text: var(--zinc-800);
+    --color-text-strong: var(--zinc-950);
+    --color-muted: var(--zinc-600);
+    --color-muted-2: var(--zinc-500);
+    --color-link: var(--emerald-600);
+    --color-link-hover: var(--emerald-500);
+    --color-code-bg: var(--zinc-50);
+    --color-primary: var(--emerald-600);
+    --color-primary-text: #ffffff;
+    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
+    --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.08);
+    --scrollbar-track: var(--zinc-100);
+    --scrollbar-thumb: var(--zinc-300);
+  }
+}
+
+:root[data-theme="light"] {
+  --color-bg: #ffffff;
+  --color-bg-elevated: var(--zinc-50);
+  --color-surface: var(--zinc-50);
+  --color-surface-hover: var(--zinc-100);
+  --color-border: var(--zinc-200);
+  --color-border-strong: var(--zinc-300);
+  --color-text: var(--zinc-800);
+  --color-text-strong: var(--zinc-950);
+  --color-muted: var(--zinc-600);
+  --color-muted-2: var(--zinc-500);
+  --color-link: var(--emerald-600);
+  --color-link-hover: var(--emerald-500);
+  --color-code-bg: var(--zinc-50);
+  --color-primary: var(--emerald-600);
+  --color-primary-text: #ffffff;
+  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
+  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.08);
+  --scrollbar-track: var(--zinc-100);
+  --scrollbar-thumb: var(--zinc-300);
 }
 
 /* -------------------------------------------------------------------------
-   2. Base — apply tokens to the minima-rendered page without fighting it
+   2. Reset + base (minima's CSS is no longer loaded — this owns everything)
    ------------------------------------------------------------------------- */
+*, *::before, *::after { box-sizing: border-box; }
+html {
+  -webkit-text-size-adjust: 100%;
+  /* Sticky header is ~64px tall — without this, jumping to a #heading
+     (TOC links, "On this page", same-page anchors) lands the heading
+     flush under the header, hiding it. This offsets every anchor jump
+     so the target lands visibly below the header instead. */
+  scroll-padding-top: 5.5rem;
+}
+@media (max-width: 640px) {
+  html { scroll-padding-top: 4.5rem; }
+}
+
 body {
+  margin: 0;
   background-color: var(--color-bg);
   color: var(--color-text);
+  font-family: var(--font-sans);
+  font-size: 16px;
+  line-height: 1.65;
+  -webkit-font-smoothing: antialiased;
   transition: background-color 0.15s ease, color 0.15s ease;
 }
 
-.page-content, .wrapper { background-color: var(--color-bg); }
+::-webkit-scrollbar { width: 10px; height: 10px; }
+::-webkit-scrollbar-track { background: var(--scrollbar-track); }
+::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: var(--radius-full); }
 
-a { color: var(--color-link); }
+img { max-width: 100%; height: auto; }
 
-pre, code { background-color: var(--color-code-bg) !important; }
+a {
+  color: var(--color-link);
+  text-decoration: none;
+}
+a:hover { color: var(--color-link-hover); text-decoration: underline; }
 
-table { border-color: var(--color-border); }
-table th, table td { border-color: var(--color-border) !important; color: var(--color-text); }
-table th { background-color: var(--color-surface) !important; }
-table tr:nth-child(2n) { background-color: var(--color-surface) !important; }
+h1, h2, h3, h4, h5, h6 {
+  color: var(--color-text-strong);
+  font-weight: 700;
+  line-height: 1.25;
+  margin: 1.6em 0 0.6em;
+  scroll-margin-top: 5.5rem; /* belt-and-suspenders with html's scroll-padding-top */
+}
+@media (max-width: 640px) {
+  h1, h2, h3, h4, h5, h6 { scroll-margin-top: 4.5rem; }
+}
+h1 { font-size: 1.9rem; }
+h2 { font-size: 1.5rem; border-bottom: 1px solid var(--color-border); padding-bottom: 0.4em; }
+h3 { font-size: 1.2rem; }
+h4 { font-size: 1.05rem; }
 
-hr { border-color: var(--color-border); }
+p { margin: 0.9em 0; }
+ul, ol { padding-left: 1.4em; }
+li { margin: 0.35em 0; }
 
-.site-header, .site-footer {
-  background-color: var(--color-surface);
-  border-color: var(--color-border) !important;
-}
-.site-title, .site-title:visited { color: var(--color-text) !important; }
-.site-nav .page-link { color: var(--color-text) !important; }
+strong { color: var(--color-text-strong); font-weight: 700; }
 
-details summary { cursor: pointer; }
+hr {
+  border: none;
+  border-top: 1px solid var(--color-border);
+  margin: 2.2em 0;
+}
 
-/* -------------------------------------------------------------------------
-   3. Callout components
-   One visual language for every colored box in the course. Icon + label are
-   never the only signal — each type also has a distinct text label.
-   ------------------------------------------------------------------------- */
-.callout {
-  margin: 1.25em 0;
-  padding: 1em 1.1em;
-  border-radius: var(--radius-md);
+table {
+  width: 100%;
+  border-collapse: collapse;
+  margin: 1.2em 0;
+  font-size: 0.92rem;
+  display: block;
+  overflow-x: auto;
+}
+th, td {
   border: 1px solid var(--color-border);
-  border-left-width: 4px;
-  background: var(--color-surface);
-  box-shadow: var(--shadow-sm);
+  padding: 0.55em 0.8em;
+  text-align: left;
+  vertical-align: top;
 }
-.callout > :first-child { margin-top: 0; }
-.callout > :last-child { margin-bottom: 0; }
-.callout-label {
-  display: flex;
-  align-items: center;
-  gap: 0.5em;
+th {
+  background: var(--color-bg-elevated);
+  color: var(--color-text-strong);
   font-weight: 700;
-  font-size: 0.78rem;
-  letter-spacing: 0.04em;
-  text-transform: uppercase;
-  margin-bottom: 0.55em;
-}
-
-.callout-concept   { border-left-color: var(--color-info);    background: var(--color-info-bg); }
-.callout-important { border-left-color: var(--color-danger);  background: var(--color-danger-bg); }
-.callout-protip     { border-left-color: var(--color-primary); background: var(--color-info-bg); }
-.callout-mistake   { border-left-color: var(--color-warning); background: var(--color-warning-bg); }
-.callout-tryit      { border-left-color: var(--color-success); background: var(--color-success-bg); }
-.callout-challenge { border-left-color: var(--color-purple);  background: var(--color-purple-bg); }
-.callout-remember  { border-left-color: var(--color-purple);  background: var(--color-purple-bg); }
-.callout-recovery  { border-left-color: var(--color-success); background: var(--color-success-bg); }
-.callout-realworld { border-left-color: var(--color-muted);   background: var(--color-surface); }
-.callout-sdet      { border-left-color: var(--color-primary); background: var(--color-info-bg); }
-
-/* Upgraded native GitHub alerts ([!NOTE] etc.) — same visual family */
-.gfm-alert { border-left-width: 4px; }
-.gfm-alert-note      { border-left-color: var(--color-info);    background: var(--color-info-bg); }
-.gfm-alert-tip       { border-left-color: var(--color-success); background: var(--color-success-bg); }
-.gfm-alert-important { border-left-color: var(--color-purple);  background: var(--color-purple-bg); }
-.gfm-alert-warning   { border-left-color: var(--color-warning); background: var(--color-warning-bg); }
-.gfm-alert-caution    { border-left-color: var(--color-danger); background: var(--color-danger-bg); }
+}
+tr:nth-child(2n) td { background: rgba(255, 255, 255, 0.015); }
+
+blockquote {
+  margin: 1.2em 0;
+  padding: 0.8em 1.1em;
+  border-left: 3px solid var(--color-border-strong);
+  background: var(--color-surface);
+  border-radius: 0 var(--radius-md) var(--radius-md) 0;
+  color: var(--color-muted);
+}
+blockquote p { margin: 0.4em 0; }
 
 /* -------------------------------------------------------------------------
-   4. Code blocks — copy button
+   3. Code
    ------------------------------------------------------------------------- */
+code {
+  font-family: var(--font-mono);
+  font-size: 0.86em;
+  background: var(--color-surface);
+  border: 1px solid var(--color-border);
+  border-radius: 5px;
+  padding: 0.15em 0.4em;
+}
+
+pre {
+  background: var(--color-code-bg);
+  border: 1px solid var(--color-border);
+  border-radius: var(--radius-lg);
+  padding: 1em 1.2em;
+  overflow-x: auto;
+  font-size: 0.86rem;
+  line-height: 1.6;
+}
+pre code {
+  background: none;
+  border: none;
+  padding: 0;
+  font-size: 1em;
+}
+pre.mermaid { background: transparent; border: none; padding: 0; overflow-x: visible; }
+
 .code-block-wrap { position: relative; }
 .copy-btn {
   position: absolute;
-  top: 0.5em;
-  right: 0.5em;
+  top: 0.6em;
+  right: 0.6em;
   z-index: 2;
   font-size: 0.72rem;
   line-height: 1;
-  padding: 0.35em 0.6em;
-  border-radius: 6px;
+  padding: 0.4em 0.7em;
+  border-radius: var(--radius-full);
   border: 1px solid var(--color-border);
-  background: var(--color-surface-raised);
+  background: var(--color-bg-elevated);
   color: var(--color-muted);
   cursor: pointer;
   opacity: 0;
@@ -175,37 +264,162 @@ details summary { cursor: pointer; }
 }
 .code-block-wrap:hover .copy-btn,
 .code-block-wrap:focus-within .copy-btn,
-.copy-btn:focus-visible {
-  opacity: 1;
-}
-.copy-btn:hover { color: var(--color-primary); border-color: var(--color-primary); }
-.copy-btn[data-copied="true"] { color: var(--color-success); border-color: var(--color-success); opacity: 1; }
-
-@media (max-width: 600px) {
-  /* Touch devices have no hover — keep the button always visible */
-  .copy-btn { opacity: 1; }
-}
+.copy-btn:focus-visible { opacity: 1; }
+.copy-btn:hover { color: var(--color-link); border-color: var(--color-link); }
+.copy-btn[data-copied="true"] { color: var(--emerald-400); border-color: var(--emerald-500); opacity: 1; }
+@media (max-width: 600px) { .copy-btn { opacity: 1; } }
 
 /* -------------------------------------------------------------------------
-   5. Destructive-command warning badge
+   4. Callout components — the course's visual language
    ------------------------------------------------------------------------- */
+.callout {
+  margin: 1.4em 0;
+  padding: 1.1em 1.25em;
+  border-radius: var(--radius-lg);
+  border: 1px solid var(--color-border);
+  border-left-width: 4px;
+  background: var(--color-surface);
+  box-shadow: var(--shadow-sm);
+}
+.callout > :first-child { margin-top: 0; }
+.callout > :last-child { margin-bottom: 0; }
+.callout-label {
+  display: flex;
+  align-items: center;
+  gap: 0.5em;
+  font-weight: 700;
+  font-size: 0.74rem;
+  letter-spacing: 0.06em;
+  text-transform: uppercase;
+  margin-bottom: 0.6em;
+  color: var(--color-text-strong);
+}
+
+.callout-concept   { border-left-color: var(--sky-400); }
+.callout-important { border-left-color: var(--rose-500); }
+.callout-protip     { border-left-color: var(--emerald-400); }
+.callout-mistake   { border-left-color: var(--amber-400); }
+.callout-tryit      { border-left-color: var(--emerald-500); }
+.callout-challenge { border-left-color: var(--violet-400); }
+.callout-remember  { border-left-color: var(--violet-400); }
+.callout-recovery  { border-left-color: var(--emerald-400); }
+.callout-realworld { border-left-color: var(--zinc-500); }
+.callout-sdet      { border-left-color: var(--sky-400); }
+
+.gfm-alert-note      { border-left-color: var(--sky-400); }
+.gfm-alert-tip       { border-left-color: var(--emerald-400); }
+.gfm-alert-important { border-left-color: var(--violet-400); }
+.gfm-alert-warning   { border-left-color: var(--amber-400); }
+.gfm-alert-caution    { border-left-color: var(--rose-500); }
+
 .danger-badge {
   display: inline-flex;
   align-items: center;
   gap: 0.4em;
-  font-size: 0.75rem;
+  font-size: 0.72rem;
   font-weight: 700;
-  letter-spacing: 0.03em;
+  letter-spacing: 0.04em;
   text-transform: uppercase;
-  color: #fff;
-  background: var(--color-danger);
-  padding: 0.25em 0.6em;
-  border-radius: 999px;
-  margin-bottom: 0.5em;
+  color: var(--zinc-950);
+  background: var(--rose-500);
+  padding: 0.3em 0.75em;
+  border-radius: var(--radius-full);
+  margin-bottom: 0.6em;
 }
 
+details {
+  margin: 1em 0;
+  border: 1px solid var(--color-border);
+  border-radius: var(--radius-md);
+  padding: 0.7em 1em;
+  background: var(--color-surface);
+}
+details summary {
+  cursor: pointer;
+  font-weight: 600;
+  color: var(--color-text-strong);
+}
+details[open] summary { margin-bottom: 0.6em; }
+
+/* Badges (shields.io etc.) sit awkwardly on dark bg at full brightness —
+   soften very slightly and keep them from stretching the layout */
+img[src*="shields.io"] { vertical-align: middle; }
+
 /* -------------------------------------------------------------------------
-   6. Site chrome — theme toggle, back-to-top, breadcrumbs, TOC
+   5. Site shell — header, footer, content width
+   ------------------------------------------------------------------------- */
+.site-shell {
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.site-header {
+  position: sticky;
+  top: 0;
+  z-index: 30;
+  border-bottom: 1px solid var(--color-border);
+  background: color-mix(in srgb, var(--color-bg) 82%, transparent);
+  backdrop-filter: blur(10px);
+  -webkit-backdrop-filter: blur(10px);
+}
+.site-header-inner {
+  max-width: 1000px;
+  margin: 0 auto;
+  padding: 1em 1.25em;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 1em;
+}
+.site-logo {
+  display: flex;
+  align-items: baseline;
+  gap: 0.5em;
+  font-weight: 800;
+  font-size: 1.05rem;
+  color: var(--color-text-strong) !important;
+  text-decoration: none !important;
+}
+.site-logo .tag { font-size: 0.7rem; font-weight: 600; color: var(--color-muted); letter-spacing: 0.04em; }
+.site-nav {
+  display: flex;
+  align-items: center;
+  gap: 1.1em;
+  font-size: 0.85rem;
+}
+.site-nav a { color: var(--color-muted); font-weight: 500; }
+.site-nav a:hover { color: var(--color-text-strong); text-decoration: none; }
+
+.page-wrap {
+  flex: 1;
+  max-width: 1200px;
+  width: 100%;
+  margin: 0 auto;
+  padding: 2.2em 1.25em 4em;
+}
+
+.page-grid {
+  display: grid;
+  grid-template-columns: 1fr;
+  gap: 2.2em;
+  align-items: start;
+}
+.page-content { min-width: 0; } /* lets pre/table overflow-x scroll instead of blowing out the grid */
+
+.site-footer {
+  border-top: 1px solid var(--color-border);
+  padding: 2.4em 1.25em;
+  text-align: center;
+  font-size: 0.82rem;
+  color: var(--color-muted-2);
+}
+.site-footer a { color: var(--color-muted); }
+.site-footer a:hover { color: var(--color-link); }
+.footer-links { display: flex; justify-content: center; gap: 1.2em; margin-bottom: 0.8em; flex-wrap: wrap; }
+
+/* -------------------------------------------------------------------------
+   6. Nav chrome — breadcrumbs, TOC, back-to-top, theme toggle
    ------------------------------------------------------------------------- */
 .chrome-btn {
   position: fixed;
@@ -215,19 +429,18 @@ details summary { cursor: pointer; }
   display: flex;
   align-items: center;
   justify-content: center;
-  border-radius: 999px;
+  border-radius: var(--radius-full);
   border: 1px solid var(--color-border);
-  background: var(--color-surface-raised);
+  background: var(--color-bg-elevated);
   color: var(--color-text);
   box-shadow: var(--shadow-md);
   cursor: pointer;
   font-size: 1.05rem;
-  transition: transform 0.15s ease, opacity 0.15s ease;
+  transition: transform 0.15s ease, opacity 0.15s ease, border-color 0.15s ease;
 }
-.chrome-btn:hover { transform: translateY(-2px); }
+.chrome-btn:hover { transform: translateY(-2px); border-color: var(--color-border-strong); }
 
 #theme-toggle-btn { bottom: 1.25rem; right: 1.25rem; }
-
 #back-to-top-btn {
   bottom: 1.25rem;
   right: 4.25rem;
@@ -235,128 +448,125 @@ details summary { cursor: pointer; }
   pointer-events: none;
   transform: translateY(8px);
 }
-#back-to-top-btn.visible {
-  opacity: 1;
-  pointer-events: auto;
-  transform: translateY(0);
-}
+#back-to-top-btn.visible { opacity: 1; pointer-events: auto; transform: translateY(0); }
 
 .breadcrumb-nav {
-  font-size: 0.85rem;
-  color: var(--color-muted);
-  margin: 0 0 1.25em 0;
+  font-size: 0.82rem;
+  color: var(--color-muted-2);
+  margin: 0 0 1.4em 0;
   display: flex;
   flex-wrap: wrap;
-  gap: 0.35em;
+  gap: 0.4em;
   align-items: center;
 }
-.breadcrumb-nav a { color: var(--color-muted); text-decoration: none; }
-.breadcrumb-nav a:hover { color: var(--color-link); text-decoration: underline; }
-.breadcrumb-nav .sep { opacity: 0.6; }
-.breadcrumb-nav .current { color: var(--color-text); font-weight: 600; }
+.breadcrumb-nav a { color: var(--color-muted-2); }
+.breadcrumb-nav a:hover { color: var(--color-link); }
+.breadcrumb-nav .sep { opacity: 0.5; }
+.breadcrumb-nav .current { color: var(--color-muted); font-weight: 600; }
+
+.toc-sidebar:empty { display: none; }
 
 .toc-box {
   background: var(--color-surface);
   border: 1px solid var(--color-border);
-  border-radius: var(--radius-md);
-  padding: 0.9em 1.1em;
-  margin: 1.5em 0;
-  font-size: 0.88rem;
+  border-radius: var(--radius-lg);
+  padding: 1em 1.2em;
+  margin: 0 0 1.6em 0;
+  font-size: 0.86rem;
 }
 .toc-box summary {
   font-weight: 700;
-  font-size: 0.78rem;
-  letter-spacing: 0.04em;
+  font-size: 0.74rem;
+  letter-spacing: 0.06em;
   text-transform: uppercase;
   color: var(--color-muted);
 }
-.toc-box ul { margin: 0.75em 0 0 0; padding-left: 1.1em; }
-.toc-box li { margin: 0.3em 0; }
-.toc-box a { color: var(--color-text); text-decoration: none; }
-.toc-box a:hover { color: var(--color-link); text-decoration: underline; }
-.toc-box a.active { color: var(--color-primary); font-weight: 600; }
-
-@media (min-width: 1100px) {
-  .toc-box[data-sticky="true"] {
+.toc-box ul { margin: 0.8em 0 0 0; padding-left: 0; list-style: none; }
+.toc-box li { margin: 0.4em 0; }
+.toc-box a { color: var(--color-text); }
+.toc-box a:hover { color: var(--color-link); }
+.toc-box a.active { color: var(--emerald-400); font-weight: 600; }
+
+/* Mobile/narrow: sidebar stacks above the content, collapsible */
+.toc-sidebar { margin: 0; }
+
+@media (min-width: 1000px) {
+  /* Desktop: real left sidebar, sticky, own grid column */
+  .page-grid:has(.toc-sidebar:not(:empty)) {
+    grid-template-columns: 240px minmax(0, 1fr);
+  }
+  .toc-sidebar {
     position: sticky;
-    top: 1.5rem;
-    max-height: calc(100vh - 3rem);
+    top: 5.5rem;
+    max-height: calc(100vh - 8rem);
     overflow-y: auto;
   }
+  .toc-sidebar .toc-box { margin: 0; }
+  /* On desktop it's a persistent sidebar, not a disclosure widget */
+  .toc-sidebar .toc-box summary { pointer-events: none; }
+  .toc-sidebar .toc-box summary::-webkit-details-marker { display: none; }
+  .toc-sidebar .toc-box summary { list-style: none; }
 }
 
 /* -------------------------------------------------------------------------
-   7. Progress tracker (home page)
+   7. Progress tracker
    ------------------------------------------------------------------------- */
-.progress-tracker { margin: 1.5em 0; }
+.progress-tracker { margin: 1.6em 0; }
 .progress-row {
   display: grid;
   grid-template-columns: 1fr auto;
   align-items: center;
   gap: 0.6em;
-  margin: 0.5em 0;
+  margin: 0.6em 0;
   font-size: 0.85rem;
 }
 .progress-row .progress-label { display: flex; align-items: center; gap: 0.5em; }
-.progress-row .progress-label a { color: var(--color-text); text-decoration: none; }
-.progress-row .progress-label a:hover { text-decoration: underline; }
+.progress-row .progress-label a { color: var(--color-text); }
 .progress-track {
   grid-column: 1 / -1;
   height: 8px;
-  border-radius: 999px;
+  border-radius: var(--radius-full);
   background: var(--color-border);
   overflow: hidden;
 }
-.progress-fill {
-  height: 100%;
-  background: var(--color-success);
-  border-radius: 999px;
-  transition: width 0.25s ease;
-}
-.progress-check { color: var(--color-success); font-weight: 700; }
+.progress-fill { height: 100%; background: var(--emerald-500); border-radius: var(--radius-full); transition: width 0.25s ease; }
+.progress-check { color: var(--emerald-400); font-weight: 700; }
 .progress-reset-btn {
-  font-size: 0.75rem;
+  font-size: 0.74rem;
   color: var(--color-muted);
   background: none;
   border: 1px solid var(--color-border);
-  border-radius: 6px;
-  padding: 0.3em 0.7em;
+  border-radius: var(--radius-full);
+  padding: 0.35em 0.8em;
   cursor: pointer;
 }
-.progress-reset-btn:hover { color: var(--color-danger); border-color: var(--color-danger); }
+.progress-reset-btn:hover { color: var(--rose-400); border-color: var(--rose-500); }
 
 /* -------------------------------------------------------------------------
-   8. Accessibility
+   8. Buttons used in content (e.g. action-row links on the homepage)
    ------------------------------------------------------------------------- */
-a:focus-visible,
-button:focus-visible,
-summary:focus-visible,
-input:focus-visible {
-  outline: 2px solid var(--color-primary);
+.btn-row { display: flex; flex-wrap: wrap; gap: 0.6em; justify-content: center; }
+
+/* -------------------------------------------------------------------------
+   9. Accessibility + motion
+   ------------------------------------------------------------------------- */
+a:focus-visible, button:focus-visible, summary:focus-visible, input:focus-visible {
+  outline: 2px solid var(--emerald-400);
   outline-offset: 2px;
 }
-
 @media (prefers-reduced-motion: reduce) {
   * { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; }
 }
-
-.visually-hidden {
-  position: absolute;
-  width: 1px; height: 1px;
-  overflow: hidden;
-  clip: rect(0 0 0 0);
-  white-space: nowrap;
-}
+.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
 
 /* -------------------------------------------------------------------------
-   9. Mobile
+   10. Mobile
    ------------------------------------------------------------------------- */
-@media (max-width: 600px) {
+@media (max-width: 640px) {
+  body { font-size: 15.5px; }
+  .page-wrap { padding: 1.6em 1em 3em; }
+  .site-nav { gap: 0.8em; font-size: 0.78rem; }
   #back-to-top-btn { right: 1.25rem; bottom: 4.25rem; }
   .chrome-btn { width: 38px; height: 38px; font-size: 0.95rem; }
+  table { font-size: 0.82rem; }
 }
-
-/* Mermaid diagrams readable in dark mode (mermaid itself is re-initialized
-   with the matching theme by custom.js — this just prevents a white flash
-   while it loads) */
-pre.mermaid { background: transparent; }

From 2f296dbe2a641b52ea0dbe00aaa596e6ed0aac0c Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Tue, 6 Oct 2026 00:39:04 +0530
Subject: [PATCH 17/21] fix: breadcrumb/progress-tracker/external-link baseurl
 detection

All three guessed the site's baseurl by blindly dropping the first
URL path segment, assuming the site always lives under a /reponame/
prefix. Breaks the moment it's served from root (e.g. local jekyll
serve with no --baseurl), which is exactly how the breadcrumb's home
link ended up pointing at /resources/ instead of /. Now reads the
real Jekyll-rendered baseurl from a data attribute instead of
guessing. Also updates initTOC() to mount into the new sidebar
container from the previous commit's layout change.
---
 assets/js/custom.js | 50 ++++++++++++++++++++++++++++-----------------
 1 file changed, 31 insertions(+), 19 deletions(-)

diff --git a/assets/js/custom.js b/assets/js/custom.js
index f62c972..923dd85 100644
--- a/assets/js/custom.js
+++ b/assets/js/custom.js
@@ -10,6 +10,20 @@
   var prefersReducedMotion = window.matchMedia &&
     window.matchMedia("(prefers-reduced-motion: reduce)").matches;
 
+  // The real Jekyll baseurl ("" locally, "/git-tutorials" on GitHub Pages),
+  // rendered server-side into a data attribute — never guessed from the URL.
+  // Guessing "drop the first path segment" breaks the moment the site isn't
+  // served under a subpath, which is exactly what a local `jekyll serve`
+  // without --baseurl does.
+  function getBaseUrl() {
+    var raw = (document.body.dataset.baseurl || "").replace(/\/$/, "");
+    return raw;
+  }
+  function stripBaseUrl(pathname) {
+    var base = getBaseUrl();
+    return base && pathname.indexOf(base) === 0 ? pathname.slice(base.length) : pathname;
+  }
+
   /* ========================================================================
      1. THEME — light / dark / system, persisted, manual toggle
      ======================================================================== */
@@ -198,20 +212,21 @@
         Git Tutorials -> Module 01 - Git Fundamentals
      ======================================================================== */
   function titleFromSlug(slug) {
-    var withoutNum = slug.replace(/^\d+-/, "");
+    var withoutExt = slug.replace(/\.(html?|md)$/i, "");
+    var withoutNum = withoutExt.replace(/^\d+-/, "");
     var words = withoutNum.split("-").map(function (w) {
       return w.charAt(0).toUpperCase() + w.slice(1);
     });
-    var numMatch = /^(\d+)-/.exec(slug);
+    var numMatch = /^(\d+)-/.exec(withoutExt);
     return (numMatch ? numMatch[1] + " · " : "") + words.join(" ");
   }
 
   function initBreadcrumbs() {
     var h1 = document.querySelector(".page-content h1, main h1");
     if (!h1) return;
-    var parts = window.location.pathname.split("/").filter(Boolean);
-    // parts[0] is the repo name on Pages (e.g. "git-tutorials") — drop it.
-    var segments = parts.slice(1).filter(function (p) { return p !== "index.html"; });
+    var pathname = stripBaseUrl(window.location.pathname);
+    var parts = pathname.split("/").filter(Boolean);
+    var segments = parts.filter(function (p) { return p !== "index.html"; });
     if (segments.length === 0) return; // already on the home page
 
     var nav = document.createElement("nav");
@@ -219,7 +234,7 @@
     nav.setAttribute("aria-label", "Breadcrumb");
 
     var home = document.createElement("a");
-    home.href = "/" + parts[0] + "/";
+    home.href = getBaseUrl() + "/";
     home.textContent = "🏠 Git Tutorials";
     nav.appendChild(home);
 
@@ -237,7 +252,7 @@
         nav.appendChild(current);
       } else {
         var link = document.createElement("a");
-        link.href = "/" + parts.slice(0, i + 2).join("/") + "/";
+        link.href = getBaseUrl() + "/" + parts.slice(0, i + 1).join("/") + "/";
         link.textContent = titleFromSlug(seg);
         nav.appendChild(link);
       }
@@ -250,14 +265,14 @@
      6. AUTO TABLE OF CONTENTS for long pages
      ======================================================================== */
   function initTOC() {
+    var mount = document.getElementById("toc-sidebar-mount");
     var content = document.querySelector(".page-content") || document.querySelector("main");
-    if (!content) return;
+    if (!mount || !content) return;
     var headings = Array.prototype.slice.call(content.querySelectorAll("h2, h3"));
-    if (headings.length < 4) return; // short pages don't need one
+    if (headings.length < 4) return; // short pages don't need one — mount stays empty, CSS hides it
 
     var details = document.createElement("details");
     details.className = "toc-box";
-    details.setAttribute("data-sticky", "true");
     details.open = true;
     var summary = document.createElement("summary");
     summary.textContent = "On this page";
@@ -277,11 +292,7 @@
       linkById[h.id] = a;
     });
     details.appendChild(list);
-
-    var h1 = content.querySelector("h1");
-    var insertBefore = h1 && h1.nextElementSibling;
-    if (insertBefore) insertBefore.parentNode.insertBefore(details, insertBefore);
-    else content.insertBefore(details, content.firstChild);
+    mount.appendChild(details);
 
     if ("IntersectionObserver" in window) {
       var observer = new IntersectionObserver(function (entries) {
@@ -300,13 +311,14 @@
         [carried over from the previous head-custom.html inline script]
      ======================================================================== */
   function externalLinksNewTab() {
-    var repoBase = location.pathname.split("/").slice(0, 2).join("/");
+    var repoBase = getBaseUrl();
     document.querySelectorAll("a[href]").forEach(function (a) {
       var href = a.getAttribute("href");
       if (!/^https?:\/\//i.test(href)) return;
       try {
         var url = new URL(href, location.href);
-        var isSameRepo = url.origin === location.origin && url.pathname.indexOf(repoBase + "/") === 0;
+        var isSameRepo = url.origin === location.origin &&
+          (repoBase ? url.pathname.indexOf(repoBase + "/") === 0 : true);
         if (!isSameRepo) {
           a.setAttribute("target", "_blank");
           a.setAttribute("rel", "noopener noreferrer");
@@ -364,8 +376,8 @@
       this.write(data);
     },
     recordCurrentPage: function () {
-      var parts = location.pathname.split("/").filter(Boolean);
-      var slug = parts[1];
+      var parts = stripBaseUrl(location.pathname).split("/").filter(Boolean);
+      var slug = parts[0];
       if (slug && MODULES.some(function (m) { return m.slug === slug; })) {
         this.markVisited(slug);
       }

From cec7d9c7bd3d7884430168a82665fd69e026664c Mon Sep 17 00:00:00 2001
From: ghanendra-sdet <ghanendrablr@gmail.com>
Date: Tue, 6 Oct 2026 00:44:25 +0530
Subject: [PATCH 18/21] fix: add light mode to interactive cheatsheet, fix dead
 link

Page was dark-only with every color hardcoded. Retrofitted with
Tailwind's dark: variant system plus a theme toggle that reads/
writes the same localStorage key as the rest of the site, so theme
choice carries over between pages. Also made the canvas-drawn
sandbox graph colors theme-aware since those are set via JS, not
CSS. Separately fixed the 'Flat reference' button linking to
./cheatsheet.md, which 404s on the built site - the real output is
cheatsheet.html.
---
 resources/interactive-cheatsheet.html | 182 +++++++++++++++++---------
 1 file changed, 120 insertions(+), 62 deletions(-)

diff --git a/resources/interactive-cheatsheet.html b/resources/interactive-cheatsheet.html
index fbea7d5..d13a930 100644
--- a/resources/interactive-cheatsheet.html
+++ b/resources/interactive-cheatsheet.html
@@ -6,33 +6,54 @@
 <title>Interactive Cheatsheet Matrix — GIT Tutorials
 
 
+
+
 
 
-
+
 
   
-  
+
-

+

Interactive Cheatsheet Matrix

-

+

Every command from the course, filterable by how much you're panicking.

- +
@@ -43,12 +64,12 @@

- 🔎 + 🔎
@@ -56,82 +77,82 @@

-

Panic Level

+

Panic Level

-

Action Type

+

Action Type

-

+

- -
-

+
+

Visual Git Tree Sandbox

-

+

A tiny, fake, entirely consequence-free Git repo. Click the buttons, watch the tree change. Nothing here can actually break — that's the whole point.

-
- +
+
-

- On main · 1 commit +

+ On main · 1 commit

-