Connect a folder to a repository in the main
📦 GitHub
✨ The Prompt Phrase
What happened? Practice mode is in the link. ~What happened? Practice mode is in the link.~
💻 Code Preview
📦 All-in-One Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GitHub Connection Mastery — Interactive LMS</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-deep: #060614;
--accent-purple: #a855f7;
--accent-cyan: #06b6d4;
--accent-pink: #ec4899;
--accent-green: #10b981;
--accent-amber: #f59e0b;
}
* { -webkit-font-smoothing: antialiased; }
html { scroll-behavior: smooth; }
body {
font-family: 'Inter', system-ui, sans-serif;
background: var(--bg-deep);
color: #e5e7eb;
overflow-x: hidden;
min-height: 100vh;
}
.font-mono { font-family: 'JetBrains Mono', monospace; }
/* Animated background */
.bg-animated {
position: fixed;
inset: 0;
z-index: -2;
background: radial-gradient(ellipse at top left, rgba(168,85,247,0.18), transparent 50%),
radial-gradient(ellipse at bottom right, rgba(6,182,212,0.18), transparent 50%),
radial-gradient(ellipse at center, rgba(236,72,153,0.08), transparent 70%),
#060614;
}
.bg-grid {
position: fixed;
inset: 0;
z-index: -1;
background-image:
linear-gradient(rgba(168,85,247,0.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(168,85,247,0.06) 1px, transparent 1px);
background-size: 48px 48px;
mask-image: radial-gradient(ellipse at center, black 30%, transparent 80%);
}
/* Floating orbs */
.orb {
position: fixed;
border-radius: 50%;
filter: blur(80px);
opacity: 0.45;
z-index: -1;
animation: float 20s ease-in-out infinite;
}
.orb-1 { width: 380px; height: 380px; background: #a855f7; top: -100px; left: -100px; }
.orb-2 { width: 320px; height: 320px; background: #06b6d4; top: 40%; right: -80px; animation-delay: -7s; }
.orb-3 { width: 280px; height: 280px; background: #ec4899; bottom: -50px; left: 30%; animation-delay: -14s; }
@keyframes float {
0%, 100% { transform: translate(0, 0) scale(1); }
33% { transform: translate(40px, -30px) scale(1.08); }
66% { transform: translate(-30px, 40px) scale(0.95); }
}
/* Glass card */
.glass {
background: rgba(20, 18, 40, 0.55);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(168,85,247,0.18);
box-shadow: 0 20px 60px -20px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05);
}
.glass-strong {
background: rgba(15, 12, 30, 0.78);
backdrop-filter: blur(20px);
border: 1px solid rgba(168,85,247,0.28);
}
/* Gradient text */
.gradient-text {
background: linear-gradient(135deg, #a855f7 0%, #ec4899 50%, #06b6d4 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.gradient-text-cool {
background: linear-gradient(135deg, #06b6d4 0%, #a855f7 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Buttons */
.btn-primary {
background: linear-gradient(135deg, #a855f7 0%, #ec4899 100%);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 10px 30px -10px rgba(168,85,247,0.6);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 20px 40px -10px rgba(168,85,247,0.8);
}
.btn-primary:active { transform: translateY(0); }
.btn-ghost {
background: rgba(168,85,247,0.08);
border: 1px solid rgba(168,85,247,0.25);
transition: all 0.25s ease;
}
.btn-ghost:hover {
background: rgba(168,85,247,0.18);
border-color: rgba(168,85,247,0.5);
transform: translateY(-1px);
}
/* Tab styling */
.tab-active {
background: linear-gradient(135deg, rgba(168,85,247,0.25), rgba(236,72,153,0.25));
border-color: rgba(168,85,247,0.6) !important;
box-shadow: 0 8px 24px -8px rgba(168,85,247,0.4);
}
.tab-active .tab-icon { color: #ec4899; }
/* Code block */
.code-block {
background: linear-gradient(135deg, #0d0d1f 0%, #14102a 100%);
border: 1px solid rgba(168,85,247,0.22);
border-radius: 14px;
position: relative;
overflow: hidden;
}
.code-block::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(168,85,247,0.6), transparent);
}
.code-text {
font-family: 'JetBrains Mono', monospace;
color: #e5e7eb;
font-size: 14px;
line-height: 1.7;
}
.code-comment { color: #6b7280; }
.code-keyword { color: #c084fc; }
.code-string { color: #5eead4; }
.code-prompt { color: #ec4899; font-weight: 600; }
/* Copy button feedback */
.copy-btn {
transition: all 0.25s ease;
}
.copy-btn.copied {
background: linear-gradient(135deg, #10b981, #059669) !important;
border-color: #10b981 !important;
}
/* Lesson cards */
.lesson-card {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.lesson-card:hover {
transform: translateY(-4px);
border-color: rgba(168,85,247,0.5) !important;
box-shadow: 0 25px 60px -15px rgba(168,85,247,0.3);
}
.lesson-card.completed {
border-color: rgba(16,185,129,0.4) !important;
background: linear-gradient(135deg, rgba(16,185,129,0.08), rgba(20,18,40,0.6));
}
/* Step number badge */
.step-badge {
background: linear-gradient(135deg, #a855f7, #ec4899);
box-shadow: 0 8px 24px -8px rgba(168,85,247,0.6);
}
.step-badge.completed {
background: linear-gradient(135deg, #10b981, #059669);
box-shadow: 0 8px 24px -8px rgba(16,185,129,0.6);
}
/* Progress bar */
.progress-bar {
background: linear-gradient(90deg, #a855f7, #ec4899, #06b6d4);
background-size: 200% 100%;
animation: shimmer 3s linear infinite;
transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes shimmer {
0% { background-position: 0% 50%; }
100% { background-position: 200% 50%; }
}
/* Terminal */
.terminal {
background: #0a0a14;
border: 1px solid rgba(168,85,247,0.3);
border-radius: 14px;
overflow: hidden;
box-shadow: 0 30px 80px -20px rgba(0,0,0,0.7);
}
.terminal-header {
background: linear-gradient(135deg, #1a1530, #16122b);
padding: 10px 16px;
border-bottom: 1px solid rgba(168,85,247,0.2);
display: flex;
align-items: center;
gap: 8px;
}
.terminal-dot { width: 12px; height: 12px; border-radius: 50%; }
.terminal-body {
padding: 16px;
font-family: 'JetBrains Mono', monospace;
font-size: 13.5px;
line-height: 1.7;
min-height: 360px;
max-height: 520px;
overflow-y: auto;
}
.terminal-body::-webkit-scrollbar { width: 8px; }
.terminal-body::-webkit-scrollbar-track { background: #0a0a14; }
.terminal-body::-webkit-scrollbar-thumb { background: rgba(168,85,247,0.3); border-radius: 4px; }
.terminal-input {
background: transparent;
border: none;
outline: none;
color: #e5e7eb;
font-family: 'JetBrains Mono', monospace;
font-size: 13.5px;
flex: 1;
caret-color: #ec4899;
}
.terminal-prompt {
color: #5eead4;
font-weight: 500;
}
.terminal-line { white-space: pre-wrap; word-break: break-word; }
.terminal-error { color: #f87171; }
.terminal-success { color: #34d399; }
.terminal-info { color: #93c5fd; }
.terminal-dim { color: #6b7280; }
/* Cursor blink */
.cursor-blink {
display: inline-block;
width: 8px;
height: 16px;
background: #ec4899;
animation: blink 1s steps(1) infinite;
vertical-align: middle;
margin-left: 2px;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
/* Slide animations */
.fade-in {
animation: fadeIn 0.6s ease-out forwards;
opacity: 0;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.slide-in-right {
animation: slideInRight 0.5s ease-out forwards;
opacity: 0;
}
@keyframes slideInRight {
from { opacity: 0; transform: translateX(40px); }
to { opacity: 1; transform: translateX(0); }
}
/* Tab content */
.tab-content { display: none; }
.tab-content.active { display: block; animation: fadeIn 0.5s ease-out; }
/* Confetti / celebration */
@keyframes confetti-fall {
to { transform: translateY(100vh) rotate(720deg); opacity: 0; }
}
.confetti {
position: fixed;
width: 10px; height: 10px;
top: -20px;
z-index: 100;
animation: confetti-fall 3s linear forwards;
}
/* Glow animation for active items */
.glow-pulse {
animation: glowPulse 2.5s ease-in-out infinite;
}
@keyframes glowPulse {
0%, 100% { box-shadow: 0 0 20px rgba(168,85,247,0.3); }
50% { box-shadow: 0 0 40px rgba(168,85,247,0.6); }
}
/* Checkmark draw animation */
@keyframes checkDraw {
from { stroke-dashoffset: 50; }
to { stroke-dashoffset: 0; }
}
.check-anim {
stroke-dasharray: 50;
animation: checkDraw 0.6s ease-out forwards;
}
/* Hint pulse */
.hint-pulse {
animation: hintPulse 2s ease-in-out infinite;
}
@keyframes hintPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.04); }
}
/* Custom scrollbar global */
::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-track { background: #0a0814; }
::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #a855f7, #ec4899);
border-radius: 6px;
}
/* Difficulty badges */
.badge-easy { background: rgba(16,185,129,0.15); color: #34d399; border: 1px solid rgba(16,185,129,0.3); }
.badge-medium { background: rgba(245,158,11,0.15); color: #fbbf24; border: 1px solid rgba(245,158,11,0.3); }
.badge-hard { background: rgba(239,68,68,0.15); color: #f87171; border: 1px solid rgba(239,68,68,0.3); }
/* Section header */
.section-icon {
background: linear-gradient(135deg, rgba(168,85,247,0.2), rgba(236,72,153,0.2));
border: 1px solid rgba(168,85,247,0.3);
}
/* Toast notification */
.toast {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 1000;
padding: 14px 20px;
border-radius: 12px;
background: linear-gradient(135deg, #10b981, #059669);
color: white;
font-weight: 600;
box-shadow: 0 20px 50px -10px rgba(16,185,129,0.4);
transform: translateY(100px);
opacity: 0;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 10px;
}
.toast.show { transform: translateY(0); opacity: 1; }
/* Detail accordion */
.detail-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.detail-content.open { max-height: 4000px; }
/* Hover lift */
.hover-lift { transition: transform 0.3s ease, box-shadow 0.3s ease; }
.hover-lift:hover { transform: translateY(-3px); }
</style>
</head>
<body>
<!-- Background layers -->
<div class="bg-animated"></div>
<div class="bg-grid"></div>
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="orb orb-3"></div>
<!-- Sticky progress bar -->
<div class="fixed top-0 left-0 right-0 z-50 glass-strong border-b border-purple-500/20">
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-3 flex items-center justify-between gap-4">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center shadow-lg shadow-purple-500/40">
<i data-lucide="git-branch" class="w-5 h-5 text-white"></i>
</div>
<div class="hidden sm:block">
<div class="text-sm font-bold text-white">GitHub Connection Mastery</div>
<div class="text-[11px] text-purple-300/70">Interactive Learning Platform</div>
</div>
</div>
<div class="flex-1 max-w-md">
<div class="flex items-center justify-between mb-1">
<span class="text-xs text-gray-400">Course Progress</span>
<span class="text-xs font-bold text-white" id="progressText">0%</span>
</div>
<div class="h-2 bg-purple-900/40 rounded-full overflow-hidden">
<div class="progress-bar h-full rounded-full" id="progressBar" style="width: 0%"></div>
</div>
</div>
<button id="resetProgress" class="btn-ghost px-3 py-2 rounded-lg text-xs font-medium flex items-center gap-1.5" title="Reset Progress">
<i data-lucide="rotate-ccw" class="w-3.5 h-3.5"></i>
<span class="hidden sm:inline">Reset</span>
</button>
</div>
</div>
<!-- Hero Section -->
<header class="relative pt-28 pb-12 px-4 sm:px-6">
<div class="max-w-6xl mx-auto text-center">
<div class="inline-flex items-center gap-2 px-4 py-1.5 rounded-full glass mb-6 fade-in">
<span class="w-2 h-2 rounded-full bg-green-400 animate-pulse"></span>
<span class="text-xs font-medium text-purple-200">Live Interactive Course • 5 Lessons</span>
</div>
<h1 class="text-4xl sm:text-6xl md:text-7xl font-black tracking-tight mb-5 fade-in" style="animation-delay: 0.1s">
<span class="gradient-text">Master GitHub</span><br>
<span class="text-white">Connection</span>
</h1>
<p class="text-lg sm:text-xl text-gray-300 max-w-3xl mx-auto mb-8 leading-relaxed fade-in" style="animation-delay: 0.2s">
Learn how to connect a local folder to a GitHub repository through three powerful modes:
<span class="text-purple-300 font-semibold">quick copy</span>,
<span class="text-pink-300 font-semibold">guided lessons</span>, and a
<span class="text-cyan-300 font-semibold">real terminal simulator</span>.
</p>
<div class="flex flex-wrap items-center justify-center gap-3 fade-in" style="animation-delay: 0.3s">
<div class="glass px-4 py-2 rounded-full flex items-center gap-2">
<i data-lucide="zap" class="w-4 h-4 text-yellow-400"></i>
<span class="text-sm">5 Steps</span>
</div>
<div class="glass px-4 py-2 rounded-full flex items-center gap-2">
<i data-lucide="clock" class="w-4 h-4 text-cyan-400"></i>
<span class="text-sm">~10 min</span>
</div>
<div class="glass px-4 py-2 rounded-full flex items-center gap-2">
<i data-lucide="terminal" class="w-4 h-4 text-purple-400"></i>
<span class="text-sm">Real Terminal Simulation</span>
</div>
<div class="glass px-4 py-2 rounded-full flex items-center gap-2">
<i data-lucide="award" class="w-4 h-4 text-pink-400"></i>
<span class="text-sm">Certificate</span>
</div>
</div>
</div>
</header>
<!-- Tab Navigation -->
<nav class="sticky top-[68px] z-40 px-4 sm:px-6 mb-8">
<div class="max-w-5xl mx-auto">
<div class="glass-strong rounded-2xl p-2 grid grid-cols-3 gap-2">
<button class="tab-btn tab-active group relative px-4 py-3 rounded-xl border border-transparent transition-all flex flex-col sm:flex-row items-center justify-center gap-2" data-tab="quick">
<i data-lucide="zap" class="tab-icon w-5 h-5 text-purple-300"></i>
<div class="text-left">
<div class="text-sm font-bold text-white">Quick Copy</div>
<div class="text-[10px] text-purple-300/70 hidden sm:block">Fast reference</div>
</div>
</button>
<button class="tab-btn group relative px-4 py-3 rounded-xl border border-transparent transition-all flex flex-col sm:flex-row items-center justify-center gap-2" data-tab="course">
<i data-lucide="graduation-cap" class="tab-icon w-5 h-5 text-purple-300"></i>
<div class="text-left">
<div class="text-sm font-bold text-white">Training Course</div>
<div class="text-[10px] text-purple-300/70 hidden sm:block">Step-by-step</div>
</div>
</button>
<button class="tab-btn group relative px-4 py-3 rounded-xl border border-transparent transition-all flex flex-col sm:flex-row items-center justify-center gap-2" data-tab="terminal">
<i data-lucide="terminal" class="tab-icon w-5 h-5 text-purple-300"></i>
<div class="text-left">
<div class="text-sm font-bold text-white">Live Terminal</div>
<div class="text-[10px] text-purple-300/70 hidden sm:block">Practice & simulate</div>
</div>
</button>
</div>
</div>
</nav>
<!-- Main content -->
<main class="px-4 sm:px-6 pb-20">
<div class="max-w-6xl mx-auto">
<!-- ============================================ -->
<!-- TAB 1: QUICK COPY -->
<!-- ============================================ -->
<section class="tab-content active" data-tab-content="quick">
<!-- Section header -->
<div class="flex items-center gap-4 mb-6">
<div class="section-icon w-12 h-12 rounded-xl flex items-center justify-center">
<i data-lucide="zap" class="w-6 h-6 text-purple-300"></i>
</div>
<div>
<h2 class="text-2xl sm:text-3xl font-black text-white">Quick Reference</h2>
<p class="text-sm text-gray-400">Copy individual commands or grab them all at once</p>
</div>
</div>
<!-- All-in-one block -->
<div class="glass rounded-2xl p-5 sm:p-7 mb-8 relative overflow-hidden">
<div class="absolute top-0 right-0 w-64 h-64 bg-gradient-to-br from-purple-500/20 to-pink-500/20 blur-3xl rounded-full"></div>
<div class="relative">
<div class="flex items-start justify-between gap-4 mb-5 flex-wrap">
<div>
<div class="flex items-center gap-2 mb-2">
<span class="px-2.5 py-1 rounded-md bg-gradient-to-r from-purple-500 to-pink-500 text-white text-[10px] font-bold tracking-wider">ALL-IN-ONE</span>
<span class="text-xs text-gray-400">Copy and paste the full sequence</span>
</div>
<h3 class="text-xl font-bold text-white">Complete Connection Script</h3>
</div>
<button class="copy-btn btn-primary px-5 py-2.5 rounded-xl font-bold text-white flex items-center gap-2 text-sm" data-copy-all>
<i data-lucide="copy" class="w-4 h-4"></i>
<span>Copy All Commands</span>
</button>
</div>
<div class="code-block p-5">
<pre class="code-text"><span class="code-comment"># Step 1 — Navigate into your folder</span>
cd c:\laragon\NewConectWWW
<span class="code-comment"># Step 2 — Initialize Git in this folder</span>
git init
<span class="code-comment"># Step 3 — Connect to your GitHub repository</span>
git remote add origin https://github.com/victorystreamlines/www.git
<span class="code-comment"># Step 4 — Fetch all data from GitHub</span>
git fetch origin
<span class="code-comment"># Step 5 — Override local files with GitHub's content</span>
git reset --hard origin/main</pre>
</div>
</div>
</div>
<!-- Individual commands grid -->
<h3 class="text-lg font-bold text-white mb-4 flex items-center gap-2">
<i data-lucide="list-ordered" class="w-5 h-5 text-purple-400"></i>
Individual Commands
</h3>
<div class="grid gap-4 sm:grid-cols-2" id="quickCommandsGrid"></div>
<!-- Pro tip -->
<div class="mt-8 glass rounded-2xl p-5 border-l-4 border-yellow-500/60 flex items-start gap-4">
<div class="w-10 h-10 rounded-lg bg-yellow-500/20 flex items-center justify-center flex-shrink-0">
<i data-lucide="lightbulb" class="w-5 h-5 text-yellow-400"></i>
</div>
<div>
<h4 class="font-bold text-white mb-1">Pro Tip</h4>
<p class="text-sm text-gray-300">If you want to <strong class="text-yellow-300">understand</strong> what each command does before running it, switch to the <strong>Training Course</strong> tab. If you want to <strong class="text-cyan-300">practice safely</strong> first, try the <strong>Live Terminal</strong> simulator.</p>
</div>
</div>
</section>
<!-- ============================================ -->
<!-- TAB 2: TRAINING COURSE -->
<!-- ============================================ -->
<section class="tab-content" data-tab-content="course">
<!-- Section header -->
<div class="flex items-center gap-4 mb-6">
<div class="section-icon w-12 h-12 rounded-xl flex items-center justify-center">
<i data-lucide="graduation-cap" class="w-6 h-6 text-purple-300"></i>
</div>
<div>
<h2 class="text-2xl sm:text-3xl font-black text-white">Training Course</h2>
<p class="text-sm text-gray-400">5 lessons • Click any lesson to expand and learn in depth</p>
</div>
</div>
<!-- Course path visualization -->
<div class="glass rounded-2xl p-5 mb-8 overflow-x-auto">
<div class="flex items-center justify-between min-w-[640px]" id="coursePath"></div>
</div>
<!-- Lessons -->
<div class="space-y-4" id="lessonsList"></div>
<!-- Completion certificate (hidden until 100%) -->
<div id="certificateSection" class="mt-10 hidden">
<div class="glass-strong rounded-3xl p-8 sm:p-12 text-center relative overflow-hidden border-2 border-yellow-500/40">
<div class="absolute inset-0 bg-gradient-to-br from-yellow-500/10 via-pink-500/10 to-purple-500/10"></div>
<div class="relative">
<div class="w-20 h-20 mx-auto mb-5 rounded-2xl bg-gradient-to-br from-yellow-400 via-pink-500 to-purple-500 flex items-center justify-center shadow-2xl">
<i data-lucide="award" class="w-10 h-10 text-white"></i>
</div>
<h3 class="text-3xl sm:text-4xl font-black gradient-text mb-3">Course Completed!</h3>
<p class="text-gray-300 mb-6">You've mastered connecting a local folder to GitHub. Time to build something great!</p>
<div class="flex flex-wrap items-center justify-center gap-3">
<button class="btn-primary px-6 py-3 rounded-xl font-bold text-white flex items-center gap-2" onclick="celebrateAgain()">
<i data-lucide="party-popper" class="w-5 h-5"></i>
Celebrate
</button>
<button class="btn-ghost px-6 py-3 rounded-xl font-bold text-white flex items-center gap-2" onclick="switchTab('terminal')">
<i data-lucide="terminal" class="w-5 h-5"></i>
Practice in Terminal
</button>
</div>
</div>
</div>
</div>
</section>
<!-- ============================================ -->
<!-- TAB 3: LIVE TERMINAL -->
<!-- ============================================ -->
<section class="tab-content" data-tab-content="terminal">
<!-- Section header -->
<div class="flex items-center gap-4 mb-6">
<div class="section-icon w-12 h-12 rounded-xl flex items-center justify-center">
<i data-lucide="terminal" class="w-6 h-6 text-purple-300"></i>
</div>
<div>
<h2 class="text-2xl sm:text-3xl font-black text-white">Live Terminal Simulator</h2>
<p class="text-sm text-gray-400">Practice the exact commands and see realistic Git responses</p>
</div>
</div>
<div class="grid lg:grid-cols-3 gap-6">
<!-- Terminal -->
<div class="lg:col-span-2">
<div class="terminal">
<div class="terminal-header">
<span class="terminal-dot bg-red-500"></span>
<span class="terminal-dot bg-yellow-500"></span>
<span class="terminal-dot bg-green-500"></span>
<span class="ml-3 text-xs text-gray-400 font-mono">PowerShell — c:\laragon\NewConectWWW</span>
<button class="ml-auto text-gray-400 hover:text-white text-xs flex items-center gap-1" onclick="clearTerminal()">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
Clear
</button>
</div>
<div class="terminal-body" id="terminalBody">
<div class="terminal-line terminal-info">Welcome to the GitHub Connection Practice Terminal!</div>
<div class="terminal-line terminal-dim">Type <span class="terminal-success">help</span> to see available commands, or <span class="terminal-success">hint</span> for the next step.</div>
<div class="terminal-line terminal-dim">─────────────────────────────────────────────────────────</div>
<div class="terminal-line"> </div>
</div>
<div class="flex items-center px-4 py-3 border-t border-purple-500/20 bg-[#0a0a14]">
<span class="terminal-prompt mr-2 font-mono text-sm" id="terminalPrompt">PS C:\laragon\NewConectWWW></span>
<input type="text" class="terminal-input" id="terminalInput" autocomplete="off" spellcheck="false" placeholder="Type a command..." />
</div>
</div>
</div>
<!-- Side panel: progress + hints -->
<div class="space-y-4">
<!-- Step tracker -->
<div class="glass rounded-2xl p-5">
<h4 class="font-bold text-white mb-4 flex items-center gap-2">
<i data-lucide="target" class="w-5 h-5 text-pink-400"></i>
Your Mission
</h4>
<div class="space-y-2.5" id="terminalSteps"></div>
</div>
<!-- Quick commands -->
<div class="glass rounded-2xl p-5">
<h4 class="font-bold text-white mb-3 flex items-center gap-2">
<i data-lucide="keyboard" class="w-5 h-5 text-cyan-400"></i>
Quick Insert
</h4>
<p class="text-xs text-gray-400 mb-3">Click to inject the command into the terminal</p>
<div class="space-y-2" id="quickInsertButtons"></div>
</div>
<!-- Help -->
<div class="glass rounded-2xl p-5">
<h4 class="font-bold text-white mb-3 flex items-center gap-2">
<i data-lucide="help-circle" class="w-5 h-5 text-yellow-400"></i>
Need Help?
</h4>
<button class="hint-pulse btn-ghost w-full px-4 py-2.5 rounded-lg text-sm font-medium flex items-center justify-center gap-2" onclick="showHint()">
<i data-lucide="sparkles" class="w-4 h-4"></i>
Show Next Hint
</button>
</div>
</div>
</div>
</section>
</div>
</main>
<!-- Footer -->
<footer class="border-t border-purple-500/15 py-8 px-4 sm:px-6">
<div class="max-w-6xl mx-auto text-center">
<p class="text-sm text-gray-500">
Built as an interactive learning aid for connecting local folders to GitHub repositories.
<span class="block mt-2 text-xs">Repository: <span class="font-mono text-purple-300">github.com/victorystreamlines/www</span></span>
</p>
</div>
</footer>
<!-- Toast -->
<div id="toast" class="toast">
<i data-lucide="check-circle-2" class="w-5 h-5"></i>
<span id="toastMsg">Copied!</span>
</div>
<script>
/* ============================================================
DATA — All commands, lessons, and responses
============================================================ */
const COMMANDS = [
{
id: 1,
short: 'Navigate',
title: 'Navigate to your folder',
cmd: 'cd c:\\laragon\\NewConectWWW',
summary: 'Move into the folder where Git will operate.',
icon: 'folder-open',
color: 'from-blue-500 to-cyan-500',
difficulty: 'easy',
duration: '30 sec',
why: 'Git commands operate on the current directory. You must be inside the folder before running any Git command, otherwise Git will not know which project you are working with.',
breakdown: [
{ part: 'cd', meaning: 'Change Directory — a built-in shell command' },
{ part: 'c:\\laragon\\NewConectWWW', meaning: 'The absolute path to your target folder' }
],
expectedOutput: '',
expectedNote: 'No output means success. The prompt will now show the new path.',
commonErrors: [
{ error: 'The system cannot find the path specified.', fix: 'Double-check the spelling and that the folder actually exists.' }
],
analogy: 'Think of it as walking into a room before you start working in it. Git needs to know which "room" (folder) it should manage.'
},
{
id: 2,
short: 'Initialize',
title: 'Initialize a Git repository',
cmd: 'git init',
summary: 'Turn this regular folder into a Git-tracked project.',
icon: 'git-branch-plus',
color: 'from-purple-500 to-pink-500',
difficulty: 'easy',
duration: '20 sec',
why: 'Git needs a hidden control center (the .git folder) to track changes, history, and branches. git init creates that center inside your folder.',
breakdown: [
{ part: 'git', meaning: 'The Git program itself' },
{ part: 'init', meaning: 'Initialize — creates the hidden .git directory' }
],
expectedOutput: 'Initialized empty Git repository in C:/laragon/NewConectWWW/.git/',
expectedNote: 'A hidden .git folder is created. Your folder is now a Git repository.',
commonErrors: [
{ error: 'git: command not found', fix: 'Git is not installed. Download it from git-scm.com.' }
],
analogy: 'Like installing a security camera system in your room — Git can now record every change you make.'
},
{
id: 3,
short: 'Connect',
title: 'Connect to GitHub',
cmd: 'git remote add origin https://github.com/victorystreamlines/www.git',
summary: 'Link your local repo to the remote GitHub repository.',
icon: 'link-2',
color: 'from-pink-500 to-rose-500',
difficulty: 'medium',
duration: '40 sec',
why: 'Your local repo and GitHub are two separate places. This command tells Git: "When I say origin, I mean this URL on GitHub." It is the bridge between local and remote.',
breakdown: [
{ part: 'git remote', meaning: 'The remote management subcommand' },
{ part: 'add', meaning: 'Add a new remote connection' },
{ part: 'origin', meaning: 'A nickname for the remote (convention, can be any name)' },
{ part: 'https://...', meaning: 'The actual URL of your GitHub repository' }
],
expectedOutput: '',
expectedNote: 'No output means success. The connection is now stored.',
commonErrors: [
{ error: 'error: remote origin already exists.', fix: 'Use git remote set-url origin <url> to update it instead, or git remote remove origin first.' }
],
analogy: 'Like saving someone\'s phone number. You give it a name (origin) so you don\'t need to type the long number every time.'
},
{
id: 4,
short: 'Fetch',
title: 'Fetch from GitHub',
cmd: 'git fetch origin',
summary: 'Download all data from GitHub without changing your local files yet.',
icon: 'cloud-download',
color: 'from-cyan-500 to-blue-500',
difficulty: 'medium',
duration: '1 min',
why: 'Before merging or replacing files, Git needs to know what is on GitHub. fetch downloads all branches, commits, and history into Git\'s memory — but it does NOT touch your working files. It is a safe, read-only sync.',
breakdown: [
{ part: 'git fetch', meaning: 'Download remote data into local Git database' },
{ part: 'origin', meaning: 'The remote name we set in step 3' }
],
expectedOutput: 'remote: Enumerating objects: 12, done.\nremote: Counting objects: 100% (12/12), done.\nremote: Compressing objects: 100% (8/8), done.\nremote: Total 12 (delta 1), reused 9 (delta 0), pack-reused 0\nUnpacking objects: 100% (12/12), done.\nFrom https://github.com/victorystreamlines/www\n * [new branch] main -> origin/main',
expectedNote: 'You see object counts and a "[new branch] main -> origin/main" line. The data is now downloaded but your files are unchanged.',
commonErrors: [
{ error: 'fatal: unable to access ... Could not resolve host', fix: 'Check your internet connection.' },
{ error: 'fatal: Authentication failed', fix: 'For private repos, you need to authenticate with a personal access token.' }
],
analogy: 'Like checking what is in someone else\'s notebook without copying it yet. You see what they have, but your own notebook is untouched.'
},
{
id: 5,
short: 'Override',
title: 'Override local with GitHub',
cmd: 'git reset --hard origin/main',
summary: 'Replace ALL local files with what is on GitHub\'s main branch.',
icon: 'refresh-cw',
color: 'from-orange-500 to-red-500',
difficulty: 'hard',
duration: '20 sec',
why: 'After fetching, your files are still local. reset --hard origin/main forces your folder to look exactly like GitHub. Any local file not on GitHub will be deleted, and any file on GitHub will replace your local version.',
breakdown: [
{ part: 'git reset', meaning: 'Move the current branch pointer' },
{ part: '--hard', meaning: 'Also overwrite working files (DESTRUCTIVE!)' },
{ part: 'origin/main', meaning: 'Target: the main branch on GitHub' }
],
expectedOutput: 'HEAD is now at 9b71eab Merge branch \'main\' of https://github.com/victorystreamlines/www',
expectedNote: 'Your folder now perfectly mirrors the GitHub repository. Local-only files are gone.',
commonErrors: [
{ error: 'fatal: ambiguous argument \'origin/main\'', fix: 'Run git fetch origin first, or check if the branch is named master instead of main.' }
],
analogy: 'Like factory-resetting a phone to match a backup exactly. Whatever was on the phone is gone; only the backup remains.',
warning: 'This command is DESTRUCTIVE. Any uncommitted local changes will be lost forever. Only use it when you are sure you want GitHub\'s version to win.'
}
];
/* Terminal command responses (matched flexibly) */
const TERMINAL_RESPONSES = [
{
matchers: [/^cd\s+c:\\laragon\\newconectwww\/?$/i, /^cd\s+["']?c:\\laragon\\newconectwww["']?\/?$/i],
step: 1,
output: '',
promptUpdate: 'PS C:\\laragon\\NewConectWWW>',
successMsg: 'Now inside the project folder.'
},
{
matchers: [/^git\s+init\s*$/i],
step: 2,
output: 'Initialized empty Git repository in C:/laragon/NewConectWWW/.git/',
successMsg: 'Git repository created. The .git folder is now hidden inside.'
},
{
matchers: [
/^git\s+remote\s+add\s+origin\s+https:\/\/github\.com\/victorystreamlines\/www(\.git)?\/?$/i
],
step: 3,
output: '',
successMsg: 'Remote "origin" connected to GitHub.'
},
{
matchers: [/^git\s+fetch\s+origin\s*$/i, /^git\s+fetch\s*$/i],
step: 4,
output: 'remote: Enumerating objects: 12, done.\nremote: Counting objects: 100% (12/12), done.\nremote: Compressing objects: 100% (8/8), done.\nremote: Total 12 (delta 1), reused 9 (delta 0), pack-reused 0\nUnpacking objects: 100% (12/12), done.\nFrom https://github.com/victorystreamlines/www\n * [new branch] main -> origin/main',
successMsg: 'Data fetched. Your local files are still unchanged.'
},
{
matchers: [/^git\s+reset\s+--hard\s+origin\/main\s*$/i],
step: 5,
output: 'HEAD is now at 9b71eab Merge branch \'main\' of https://github.com/victorystreamlines/www',
successMsg: 'Local folder now mirrors GitHub. Mission complete!'
}
];
/* Bonus / utility commands */
const UTIL_COMMANDS = {
'help': () => ({
type: 'info',
output: `Available commands in this simulator:
${color('cd c:\\laragon\\NewConectWWW', 'success')} — Step 1: Navigate
${color('git init', 'success')} — Step 2: Initialize
${color('git remote add origin https://github.com/victorystreamlines/www.git', 'success')} — Step 3: Connect
${color('git fetch origin', 'success')} — Step 4: Fetch
${color('git reset --hard origin/main', 'success')} — Step 5: Override
Utility commands:
${color('hint', 'info')} — Reveal the next command you need to run
${color('status', 'info')} — Show your current progress
${color('clear', 'info')} — Clear the terminal
${color('help', 'info')} — Show this message
${color('git status', 'info')} ${color('git remote -v', 'info')} ${color('git --version', 'info')} — try them out!`
}),
'clear': () => ({ type: 'clear' }),
'cls': () => ({ type: 'clear' }),
'status': () => {
const done = state.terminalProgress;
const next = COMMANDS[done] ? COMMANDS[done].cmd : null;
return {
type: 'info',
output: `Progress: ${done}/5 steps completed.
${next ? 'Next command: ' + color(next, 'success') : color('All steps complete! Mission accomplished.', 'success')}`
};
},
'hint': () => {
const done = state.terminalProgress;
const next = COMMANDS[done];
if (!next) return { type: 'success', output: 'You completed all steps! Try "git status" or just enjoy the moment.' };
return {
type: 'info',
output: `Hint for step ${next.id}: ${color(next.title, 'info')}
${next.summary}
Run: ${color(next.cmd, 'success')}`
};
},
'git --version': () => ({ type: 'info', output: 'git version 2.43.0.windows.1' }),
'git status': () => {
if (state.terminalProgress < 2) return { type: 'error', output: 'fatal: not a git repository (or any of the parent directories): .git' };
if (state.terminalProgress >= 5) return { type: 'info', output: 'On branch main\nYour branch is up to date with \'origin/main\'.\n\nnothing to commit, working tree clean' };
return { type: 'info', output: 'On branch main\n\nNo commits yet\n\nnothing to commit (create/copy files and use "git add" to track)' };
},
'git remote -v': () => {
if (state.terminalProgress < 3) return { type: 'dim', output: '(no remotes configured)' };
return { type: 'info', output: 'origin\thttps://github.com/victorystreamlines/www.git (fetch)\norigin\thttps://github.com/victorystreamlines/www.git (push)' };
},
'ls': () => listFiles(),
'dir': () => listFiles(),
'pwd': () => ({ type: 'info', output: 'C:\\laragon\\NewConectWWW' })
};
function listFiles() {
if (state.terminalProgress < 5) {
return { type: 'info', output: 'newConnect1.html' };
}
return { type: 'info', output: 'FileTwo.html\nHello.txt\nHello1.txt\nfileOne.html\nfileThree.html\nhello2.txt\nnewConnect1.html\nprompt.txt\nreport.html\nreportkuwaiteconomies.html' };
}
function color(text, type) {
const cls = { success: 'terminal-success', error: 'terminal-error', info: 'terminal-info', dim: 'terminal-dim' }[type] || '';
return `<span class="${cls}">${escapeHtml(text)}</span>`;
}
function escapeHtml(s) {
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
}
/* ============================================================
STATE
============================================================ */
const STORAGE_KEY = 'github-mastery-progress-v1';
let state = {
completed: [], // lesson IDs marked complete
terminalProgress: 0, // 0..5 — last terminal step achieved
history: [],
historyIndex: -1
};
function loadState() {
try {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
const parsed = JSON.parse(saved);
state.completed = parsed.completed || [];
state.terminalProgress = parsed.terminalProgress || 0;
}
} catch(e) {}
}
function saveState() {
localStorage.setItem(STORAGE_KEY, JSON.stringify({
completed: state.completed,
terminalProgress: state.terminalProgress
}));
}
/* ============================================================
RENDERING
============================================================ */
function renderQuickGrid() {
const grid = document.getElementById('quickCommandsGrid');
grid.innerHTML = COMMANDS.map((c, i) => `
<div class="glass rounded-2xl p-5 hover-lift fade-in" style="animation-delay: ${i * 0.07}s">
<div class="flex items-start gap-3 mb-3">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br ${c.color} flex items-center justify-center flex-shrink-0 shadow-lg">
<span class="font-black text-white">${c.id}</span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-0.5">
<h4 class="font-bold text-white text-sm">${c.title}</h4>
</div>
<p class="text-xs text-gray-400">${c.summary}</p>
</div>
</div>
<div class="code-block px-3 py-2.5 mb-3 flex items-center gap-2">
<span class="code-prompt">$</span>
<code class="code-text flex-1 text-xs sm:text-sm break-all">${escapeHtml(c.cmd)}</code>
</div>
<button class="copy-btn btn-ghost w-full px-3 py-2 rounded-lg text-xs font-medium flex items-center justify-center gap-2" data-copy="${escapeAttr(c.cmd)}">
<i data-lucide="copy" class="w-3.5 h-3.5"></i>
Copy command
</button>
</div>
`).join('');
}
function escapeAttr(s) { return s.replace(/"/g, '"'); }
function renderCoursePath() {
const path = document.getElementById('coursePath');
path.innerHTML = COMMANDS.map((c, i) => {
const done = state.completed.includes(c.id);
return `
<div class="flex items-center ${i < COMMANDS.length - 1 ? 'flex-1' : ''}">
<div class="flex flex-col items-center gap-1.5">
<div class="step-badge ${done ? 'completed' : ''} w-10 h-10 rounded-full flex items-center justify-center font-black text-white text-sm">
${done ? '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>' : c.id}
</div>
<div class="text-[10px] text-gray-400 font-medium whitespace-nowrap">${c.short}</div>
</div>
${i < COMMANDS.length - 1 ? `<div class="flex-1 h-0.5 mx-2 ${done ? 'bg-green-500/50' : 'bg-purple-500/20'} rounded transition-colors"></div>` : ''}
</div>
`;
}).join('');
}
function renderLessons() {
const list = document.getElementById('lessonsList');
list.innerHTML = COMMANDS.map((c, i) => {
const done = state.completed.includes(c.id);
return `
<article class="lesson-card glass rounded-2xl overflow-hidden ${done ? 'completed' : ''}" data-lesson="${c.id}">
<header class="p-5 sm:p-6 cursor-pointer flex items-start gap-4" onclick="toggleLesson(${c.id})">
<div class="step-badge ${done ? 'completed' : ''} w-12 h-12 rounded-2xl flex items-center justify-center flex-shrink-0">
${done
? '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>'
: `<i data-lucide="${c.icon}" class="w-5 h-5 text-white"></i>`
}
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 mb-1.5 flex-wrap">
<span class="text-[10px] font-bold tracking-wider text-purple-300/80">LESSON ${c.id} OF 5</span>
<span class="px-2 py-0.5 rounded text-[10px] font-bold badge-${c.difficulty}">${c.difficulty.toUpperCase()}</span>
<span class="text-[10px] text-gray-400 flex items-center gap-1"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>${c.duration}</span>
</div>
<h3 class="text-lg sm:text-xl font-bold text-white mb-1">${c.title}</h3>
<p class="text-sm text-gray-300">${c.summary}</p>
</div>
<i data-lucide="chevron-down" class="w-5 h-5 text-gray-400 flex-shrink-0 mt-2 transition-transform" id="chevron-${c.id}"></i>
</header>
<div class="detail-content" id="detail-${c.id}">
<div class="px-5 sm:px-6 pb-6 pt-2 border-t border-purple-500/15">
<!-- Why -->
<div class="mt-5">
<h4 class="text-sm font-bold text-purple-300 uppercase tracking-wider mb-2 flex items-center gap-2">
<i data-lucide="info" class="w-4 h-4"></i>
Why this step matters
</h4>
<p class="text-sm text-gray-300 leading-relaxed">${c.why}</p>
</div>
<!-- Analogy -->
<div class="mt-5 glass rounded-xl p-4 border-l-4 border-cyan-500/50">
<div class="flex items-start gap-3">
<div class="w-8 h-8 rounded-lg bg-cyan-500/15 flex items-center justify-center flex-shrink-0">
<i data-lucide="puzzle" class="w-4 h-4 text-cyan-300"></i>
</div>
<div>
<div class="text-xs font-bold text-cyan-300 mb-1">REAL-WORLD ANALOGY</div>
<p class="text-sm text-gray-300 italic">${c.analogy}</p>
</div>
</div>
</div>
<!-- Command breakdown -->
<div class="mt-5">
<h4 class="text-sm font-bold text-purple-300 uppercase tracking-wider mb-3 flex items-center gap-2">
<i data-lucide="microscope" class="w-4 h-4"></i>
Command Breakdown
</h4>
<div class="space-y-2">
${c.breakdown.map(b => `
<div class="flex items-start gap-3 p-3 rounded-lg bg-purple-900/20 border border-purple-500/10">
<code class="font-mono text-sm text-pink-300 font-bold whitespace-nowrap">${escapeHtml(b.part)}</code>
<span class="text-gray-400">→</span>
<span class="text-sm text-gray-300">${b.meaning}</span>
</div>
`).join('')}
</div>
</div>
<!-- The command -->
<div class="mt-5">
<h4 class="text-sm font-bold text-purple-300 uppercase tracking-wider mb-3 flex items-center gap-2">
<i data-lucide="terminal-square" class="w-4 h-4"></i>
Run This Command
</h4>
<div class="code-block p-4 flex items-center gap-3">
<span class="code-prompt">$</span>
<code class="code-text flex-1 break-all">${escapeHtml(c.cmd)}</code>
<button class="copy-btn btn-ghost px-3 py-1.5 rounded-lg text-xs font-medium flex items-center gap-1.5 flex-shrink-0" data-copy="${escapeAttr(c.cmd)}">
<i data-lucide="copy" class="w-3.5 h-3.5"></i>
Copy
</button>
</div>
</div>
<!-- Expected output -->
<div class="mt-5">
<h4 class="text-sm font-bold text-purple-300 uppercase tracking-wider mb-3 flex items-center gap-2">
<i data-lucide="message-square-text" class="w-4 h-4"></i>
Expected Output
</h4>
<div class="code-block p-4 mb-2">
${c.expectedOutput
? `<pre class="code-text terminal-success">${escapeHtml(c.expectedOutput)}</pre>`
: `<span class="code-comment">(no output — silence means success)</span>`
}
</div>
<p class="text-xs text-gray-400 italic">${c.expectedNote}</p>
</div>
<!-- Common errors -->
${c.commonErrors && c.commonErrors.length ? `
<div class="mt-5">
<h4 class="text-sm font-bold text-purple-300 uppercase tracking-wider mb-3 flex items-center gap-2">
<i data-lucide="alert-triangle" class="w-4 h-4"></i>
Common Issues
</h4>
<div class="space-y-3">
${c.commonErrors.map(e => `
<div class="rounded-xl p-4 bg-red-500/8 border border-red-500/25">
<div class="flex items-start gap-2 mb-2">
<i data-lucide="x-circle" class="w-4 h-4 text-red-400 mt-0.5 flex-shrink-0"></i>
<code class="text-xs font-mono text-red-300">${escapeHtml(e.error)}</code>
</div>
<div class="flex items-start gap-2 pl-6">
<i data-lucide="check-circle-2" class="w-4 h-4 text-green-400 mt-0.5 flex-shrink-0"></i>
<span class="text-xs text-gray-300">${escapeHtml(e.fix)}</span>
</div>
</div>
`).join('')}
</div>
</div>
` : ''}
<!-- Warning -->
${c.warning ? `
<div class="mt-5 rounded-xl p-4 bg-orange-500/10 border-l-4 border-orange-500">
<div class="flex items-start gap-3">
<i data-lucide="alert-octagon" class="w-5 h-5 text-orange-400 flex-shrink-0 mt-0.5"></i>
<div>
<div class="text-xs font-bold text-orange-300 uppercase tracking-wider mb-1">Warning</div>
<p class="text-sm text-gray-200">${c.warning}</p>
</div>
</div>
</div>
` : ''}
<!-- Mark complete -->
<div class="mt-6 flex flex-wrap items-center gap-3">
<button class="btn-primary px-5 py-2.5 rounded-xl font-bold text-white text-sm flex items-center gap-2 ${done ? 'opacity-60' : ''}" onclick="toggleComplete(${c.id})">
<i data-lucide="${done ? 'rotate-ccw' : 'check'}" class="w-4 h-4"></i>
${done ? 'Mark as Incomplete' : 'Mark Lesson Complete'}
</button>
${c.id < COMMANDS.length ? `
<button class="btn-ghost px-5 py-2.5 rounded-xl font-medium text-white text-sm flex items-center gap-2" onclick="toggleLesson(${c.id + 1}); document.querySelector('[data-lesson=\\'${c.id + 1}\\']').scrollIntoView({behavior:'smooth', block:'center'})">
Next lesson
<i data-lucide="arrow-right" class="w-4 h-4"></i>
</button>
` : ''}
</div>
</div>
</div>
</article>
`;
}).join('');
lucide.createIcons();
}
function renderTerminalSteps() {
const wrap = document.getElementById('terminalSteps');
wrap.innerHTML = COMMANDS.map((c, i) => {
const done = i < state.terminalProgress;
const current = i === state.terminalProgress;
return `
<div class="flex items-center gap-3 p-2.5 rounded-lg ${current ? 'bg-purple-500/15 border border-purple-500/30' : ''} transition-all">
<div class="w-7 h-7 rounded-full flex items-center justify-center flex-shrink-0 ${done ? 'bg-green-500/20 text-green-400' : current ? 'bg-purple-500/30 text-purple-200 glow-pulse' : 'bg-gray-700/50 text-gray-500'}">
${done ? '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>' : `<span class="text-xs font-bold">${c.id}</span>`}
</div>
<div class="flex-1 min-w-0">
<div class="text-xs font-semibold ${done ? 'text-green-300 line-through' : current ? 'text-white' : 'text-gray-400'} truncate">${c.short}</div>
<div class="text-[10px] ${done ? 'text-green-400/60' : 'text-gray-500'} truncate">${c.title}</div>
</div>
</div>
`;
}).join('');
}
function renderQuickInsert() {
const wrap = document.getElementById('quickInsertButtons');
wrap.innerHTML = COMMANDS.map(c => `
<button class="btn-ghost w-full text-left px-3 py-2 rounded-lg text-xs flex items-center gap-2 hover:border-pink-500/50 transition-all" onclick="injectCommand('${escapeAttr(c.cmd)}')">
<span class="w-5 h-5 rounded bg-gradient-to-br ${c.color} flex items-center justify-center flex-shrink-0 text-white font-bold text-[10px]">${c.id}</span>
<span class="font-mono text-gray-300 truncate">${escapeHtml(c.cmd)}</span>
</button>
`).join('');
}
/* ============================================================
INTERACTIONS
============================================================ */
function updateProgress() {
const pct = Math.round((state.completed.length / COMMANDS.length) * 100);
document.getElementById('progressBar').style.width = pct + '%';
document.getElementById('progressText').textContent = pct + '%';
if (pct === 100) {
document.getElementById('certificateSection').classList.remove('hidden');
} else {
document.getElementById('certificateSection').classList.add('hidden');
}
}
function toggleLesson(id) {
const detail = document.getElementById('detail-' + id);
const chevron = document.getElementById('chevron-' + id);
if (!detail) return;
const isOpen = detail.classList.toggle('open');
if (chevron) chevron.style.transform = isOpen ? 'rotate(180deg)' : 'rotate(0)';
}
function toggleComplete(id) {
const idx = state.completed.indexOf(id);
if (idx > -1) {
state.completed.splice(idx, 1);
} else {
state.completed.push(id);
showToast('Lesson ' + id + ' completed!', 'success');
if (state.completed.length === COMMANDS.length) {
setTimeout(celebrate, 400);
}
}
saveState();
renderLessons();
renderCoursePath();
updateProgress();
// Re-open the lesson that was toggled
setTimeout(() => {
const detail = document.getElementById('detail-' + id);
if (detail) {
detail.classList.add('open');
const chevron = document.getElementById('chevron-' + id);
if (chevron) chevron.style.transform = 'rotate(180deg)';
}
}, 50);
}
function switchTab(name) {
document.querySelectorAll('.tab-btn').forEach(b => {
b.classList.toggle('tab-active', b.dataset.tab === name);
});
document.querySelectorAll('.tab-content').forEach(c => {
c.classList.toggle('active', c.dataset.tabContent === name);
});
if (name === 'terminal') {
setTimeout(() => document.getElementById('terminalInput').focus(), 100);
}
window.scrollTo({ top: 0, behavior: 'smooth' });
}
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => switchTab(btn.dataset.tab));
});
/* Copy buttons (delegation) */
document.addEventListener('click', (e) => {
const btn = e.target.closest('[data-copy], [data-copy-all]');
if (!btn) return;
let text;
if (btn.hasAttribute('data-copy-all')) {
text = COMMANDS.map(c => '# ' + c.title + '\n' + c.cmd).join('\n\n');
} else {
text = btn.getAttribute('data-copy');
}
navigator.clipboard.writeText(text).then(() => {
btn.classList.add('copied');
const orig = btn.innerHTML;
btn.innerHTML = '<i data-lucide="check" class="w-4 h-4"></i><span>Copied!</span>';
lucide.createIcons();
showToast('Copied to clipboard!', 'success');
setTimeout(() => {
btn.classList.remove('copied');
btn.innerHTML = orig;
lucide.createIcons();
}, 1800);
});
});
/* Reset progress */
document.getElementById('resetProgress').addEventListener('click', () => {
if (confirm('Reset all progress? This cannot be undone.')) {
state.completed = [];
state.terminalProgress = 0;
saveState();
renderLessons();
renderCoursePath();
renderTerminalSteps();
updateProgress();
clearTerminal();
showToast('Progress reset', 'success');
}
});
/* ============================================================
TERMINAL LOGIC
============================================================ */
const termBody = document.getElementById('terminalBody');
const termInput = document.getElementById('terminalInput');
const termPrompt = document.getElementById('terminalPrompt');
function appendLine(html, className = '') {
const div = document.createElement('div');
div.className = 'terminal-line ' + className;
div.innerHTML = html;
termBody.appendChild(div);
termBody.scrollTop = termBody.scrollHeight;
}
function appendCommand(cmd) {
appendLine(`<span class="terminal-prompt">${escapeHtml(termPrompt.textContent)}</span> <span>${escapeHtml(cmd)}</span>`);
}
function clearTerminal() {
termBody.innerHTML = '';
appendLine('<span class="terminal-info">Terminal cleared.</span>');
appendLine('<span class="terminal-dim">Type <span class="terminal-success">help</span> to see available commands.</span>');
appendLine(' ');
}
function injectCommand(cmd) {
switchTab('terminal');
termInput.value = cmd;
termInput.focus();
}
function showHint() {
switchTab('terminal');
const r = UTIL_COMMANDS.hint();
appendLine(`<span class="terminal-prompt">${escapeHtml(termPrompt.textContent)}</span> <span class="terminal-dim">hint</span>`);
appendLine(r.output, 'terminal-info');
appendLine(' ');
}
function processCommand(raw) {
const cmd = raw.trim();
if (!cmd) {
appendLine(' ');
return;
}
// History
state.history.push(cmd);
state.historyIndex = state.history.length;
appendCommand(cmd);
// Utility commands first
const utilKey = Object.keys(UTIL_COMMANDS).find(k => k.toLowerCase() === cmd.toLowerCase());
if (utilKey) {
const result = UTIL_COMMANDS[utilKey]();
if (result.type === 'clear') {
clearTerminal();
return;
}
if (result.output) {
appendLine(result.output, result.type === 'error' ? 'terminal-error' : result.type === 'success' ? 'terminal-success' : result.type === 'dim' ? 'terminal-dim' : 'terminal-info');
}
appendLine(' ');
return;
}
// Step matchers
for (const r of TERMINAL_RESPONSES) {
if (r.matchers.some(m => m.test(cmd))) {
// Order check: only advance if this is the next or earlier step
if (r.step <= state.terminalProgress) {
// Already done — show output again but no progress change
if (r.output) appendLine(r.output, 'terminal-dim');
appendLine(`<span class="terminal-dim">(already executed — no change)</span>`);
appendLine(' ');
return;
}
if (r.step !== state.terminalProgress + 1) {
appendLine(`<span class="terminal-error">Out of order!</span> You should run step ${state.terminalProgress + 1} first.`, 'terminal-error');
appendLine(`<span class="terminal-dim">Type </span><span class="terminal-success">hint</span><span class="terminal-dim"> to see the next command.</span>`);
appendLine(' ');
return;
}
// Success!
if (r.output) appendLine(r.output, 'terminal-success');
if (r.promptUpdate) termPrompt.textContent = r.promptUpdate;
state.terminalProgress = r.step;
saveState();
renderTerminalSteps();
appendLine(`<span class="terminal-info">[Step ${r.step} ✓] ${r.successMsg}</span>`);
if (state.terminalProgress === COMMANDS.length) {
setTimeout(() => {
appendLine(' ');
appendLine('<span class="terminal-success">━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</span>');
appendLine('<span class="terminal-success"> ALL 5 STEPS COMPLETE — Local folder mirrors GitHub!</span>');
appendLine('<span class="terminal-success">━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</span>');
celebrate();
}, 200);
}
appendLine(' ');
return;
}
}
// Unknown command
if (cmd.toLowerCase().startsWith('git ')) {
appendLine(`<span class="terminal-error">Command not recognized in this simulator.</span> Try <span class="terminal-success">help</span> to see what's supported.`, 'terminal-error');
} else if (cmd.toLowerCase().startsWith('cd ')) {
appendLine(`<span class="terminal-error">Path not recognized.</span> The expected path is <span class="terminal-success">c:\\laragon\\NewConectWWW</span>.`, 'terminal-error');
} else {
appendLine(`<span class="terminal-error">'${escapeHtml(cmd.split(' ')[0])}' is not recognized.</span> Type <span class="terminal-success">help</span> for available commands.`, 'terminal-error');
}
appendLine(' ');
}
termInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
const v = termInput.value;
termInput.value = '';
processCommand(v);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (state.history.length === 0) return;
state.historyIndex = Math.max(0, state.historyIndex - 1);
termInput.value = state.history[state.historyIndex] || '';
} else if (e.key === 'ArrowDown') {
e.preventDefault();
state.historyIndex = Math.min(state.history.length, state.historyIndex + 1);
termInput.value = state.history[state.historyIndex] || '';
} else if (e.key === 'Tab') {
e.preventDefault();
// Auto-complete with next expected command
const next = COMMANDS[state.terminalProgress];
if (next) termInput.value = next.cmd;
}
});
/* Click anywhere in terminal area to focus input */
document.querySelector('.terminal').addEventListener('click', (e) => {
if (e.target.closest('button')) return;
termInput.focus();
});
/* ============================================================
TOAST + CELEBRATION
============================================================ */
function showToast(msg, type = 'success') {
const toast = document.getElementById('toast');
document.getElementById('toastMsg').textContent = msg;
toast.classList.add('show');
clearTimeout(toast._t);
toast._t = setTimeout(() => toast.classList.remove('show'), 2400);
}
function celebrate() {
const colors = ['#a855f7', '#ec4899', '#06b6d4', '#10b981', '#f59e0b'];
for (let i = 0; i < 80; i++) {
const c = document.createElement('div');
c.className = 'confetti';
c.style.left = Math.random() * 100 + '%';
c.style.background = colors[Math.floor(Math.random() * colors.length)];
c.style.animationDelay = Math.random() * 0.5 + 's';
c.style.animationDuration = (2 + Math.random() * 2) + 's';
c.style.transform = `rotate(${Math.random() * 360}deg)`;
if (Math.random() > 0.5) c.style.borderRadius = '50%';
document.body.appendChild(c);
setTimeout(() => c.remove(), 4500);
}
}
function celebrateAgain() { celebrate(); }
/* ============================================================
INITIALIZATION
============================================================ */
loadState();
renderQuickGrid();
renderCoursePath();
renderLessons();
renderTerminalSteps();
renderQuickInsert();
updateProgress();
lucide.createIcons();
/* Update terminal prompt if user already advanced past step 1 in past sessions */
if (state.terminalProgress >= 1) {
termPrompt.textContent = 'PS C:\\laragon\\NewConectWWW>';
}
</script>
</body>
</html>
Live Preview