splinky dingus
This commit is contained in:
commit
421f637eba
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
bun.lock
|
||||||
133
App.tsx
Normal file
133
App.tsx
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import React, { useLayoutEffect, useRef } from 'react';
|
||||||
|
import { Sidebar } from './components/Sidebar';
|
||||||
|
import { GUIDE_CONTENT } from './constants';
|
||||||
|
import { ContentRenderer } from './components/ContentRenderer';
|
||||||
|
import { GitBranch, ArrowDown, Leaf } from 'lucide-react';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
|
|
||||||
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const mainRef = useRef<HTMLDivElement>(null);
|
||||||
|
const heroRef = useRef<HTMLDivElement>(null);
|
||||||
|
const titleRef = useRef<HTMLHeadingElement>(null);
|
||||||
|
const subtitleRef = useRef<HTMLParagraphElement>(null);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const ctx = gsap.context(() => {
|
||||||
|
// Hero Animation
|
||||||
|
const tl = gsap.timeline();
|
||||||
|
|
||||||
|
tl.fromTo(titleRef.current,
|
||||||
|
{ y: 50, opacity: 0, rotate: 2 },
|
||||||
|
{ y: 0, opacity: 1, rotate: 0, duration: 1, ease: "power3.out" }
|
||||||
|
)
|
||||||
|
.fromTo(subtitleRef.current,
|
||||||
|
{ y: 20, opacity: 0 },
|
||||||
|
{ y: 0, opacity: 1, duration: 0.8, ease: "power2.out" },
|
||||||
|
"-=0.5"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Section Entry Animations
|
||||||
|
const sections = gsap.utils.toArray<HTMLElement>('.guide-section');
|
||||||
|
sections.forEach(section => {
|
||||||
|
gsap.fromTo(section,
|
||||||
|
{ opacity: 0, y: 50 },
|
||||||
|
{
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
duration: 0.8,
|
||||||
|
ease: "power2.out",
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: section,
|
||||||
|
start: "top 80%",
|
||||||
|
toggleActions: "play none none reverse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, mainRef);
|
||||||
|
|
||||||
|
return () => ctx.revert();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen bg-background text-foreground font-sans selection:bg-primary/20 selection:text-primary">
|
||||||
|
<Sidebar />
|
||||||
|
|
||||||
|
<main ref={mainRef} className="flex-1 lg:ml-72 w-full">
|
||||||
|
{/* Hero Section */}
|
||||||
|
<div ref={heroRef} className="min-h-[80vh] flex flex-col justify-center px-8 md:px-16 lg:px-24 bg-card relative overflow-hidden">
|
||||||
|
{/* Subtle blurred blobs */}
|
||||||
|
<div className="absolute top-0 right-0 w-96 h-96 bg-primary/10 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2 pointer-events-none"></div>
|
||||||
|
<div className="absolute bottom-0 left-0 w-64 h-64 bg-secondary/20 rounded-full blur-3xl translate-y-1/2 -translate-x-1/2 pointer-events-none"></div>
|
||||||
|
|
||||||
|
<div className="max-w-3xl relative z-10">
|
||||||
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 border border-primary/20 text-primary text-sm font-medium mb-6 animate-fade-in">
|
||||||
|
<GitBranch size={16} />
|
||||||
|
<span>Interactive Guide</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 ref={titleRef} className="text-5xl md:text-7xl font-bold tracking-tight text-foreground mb-8">
|
||||||
|
Git Guide <br />
|
||||||
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-primary to-accent">
|
||||||
|
for Sylvi 🌿
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p ref={subtitleRef} className="text-xl text-muted-foreground max-w-2xl leading-relaxed">
|
||||||
|
Welcome! This guide will walk you through version control from the ground up.
|
||||||
|
Think of it as a time machine for your code—save snapshots, experiment freely,
|
||||||
|
and never lose your work again.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-12 flex items-center gap-4 text-muted-foreground text-sm animate-bounce">
|
||||||
|
<ArrowDown size={20} />
|
||||||
|
<span>Scroll to start learning</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content Sections */}
|
||||||
|
<div className="px-6 md:px-16 lg:px-24 py-12 max-w-5xl mx-auto pb-32">
|
||||||
|
{GUIDE_CONTENT.map((section, index) => (
|
||||||
|
<section
|
||||||
|
key={section.id}
|
||||||
|
id={section.id}
|
||||||
|
className="guide-section mb-24 scroll-mt-24"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-4 mb-8">
|
||||||
|
<span className="flex items-center justify-center w-10 h-10 rounded-full bg-foreground text-background font-mono font-bold text-lg shadow-lg">
|
||||||
|
{index + 1}
|
||||||
|
</span>
|
||||||
|
<h2 className="text-3xl font-bold text-foreground">{section.title.split('. ')[1]}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pl-0 md:pl-14">
|
||||||
|
{section.blocks.map((block, i) => (
|
||||||
|
<ContentRenderer key={i} block={block} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{index < GUIDE_CONTENT.length - 1 && (
|
||||||
|
<div className="h-px w-full bg-gradient-to-r from-transparent via-border to-transparent mt-16"></div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="mt-20 pt-10 border-t border-border text-center text-muted-foreground">
|
||||||
|
<p className="flex items-center justify-center gap-2 mb-4">
|
||||||
|
<Leaf className="text-primary" size={20} />
|
||||||
|
<span>Made for Sylvi</span>
|
||||||
|
</p>
|
||||||
|
<p className="text-sm">Remember, Nicholai is just a message away if you get stuck!</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
547
README.md
Normal file
547
README.md
Normal file
@ -0,0 +1,547 @@
|
|||||||
|
# Git Guide for Sylvi 🌿
|
||||||
|
|
||||||
|
Welcome! This guide will walk you through git from the ground up. Git is a version control system that helps you track changes in your projects, collaborate with others, and never lose your work. Think of it as a time machine for your code—you can save snapshots of your project at any point and jump back to them whenever you need.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. What is Git?
|
||||||
|
2. Initial Setup
|
||||||
|
3. Understanding File Types in Git
|
||||||
|
4. Creating Your First Repository
|
||||||
|
5. The Basic Git Workflow
|
||||||
|
6. Cloning Repositories
|
||||||
|
7. Branching & Merging
|
||||||
|
8. Common Pitfalls & How to Fix Them
|
||||||
|
9. Using Gitea's Web Interface
|
||||||
|
10. Quick Reference Cheat Sheet
|
||||||
|
|
||||||
|
## 1. What is Git?
|
||||||
|
|
||||||
|
Git is a **version control system**. Imagine you're working on a game design document, and you want to try out a new idea for a level, but you're not sure if it'll work. With git, you can save your current version, try the new idea, and if it doesn't work out, you can go back to exactly where you were before. No "final_FINAL_v3_actually_final.txt" nonsense.
|
||||||
|
|
||||||
|
Git tracks changes to files over time, creating a history of your project. Every time you "save" (which we call a **commit**), git takes a snapshot of your entire project. You can view any of these snapshots at any time, compare them, or even combine changes from different versions.
|
||||||
|
|
||||||
|
## 2. Initial Setup
|
||||||
|
|
||||||
|
First, let's configure git with your information. Open your terminal and run these commands:
|
||||||
|
```bash
|
||||||
|
# Set your name (this will appear in your commits)
|
||||||
|
git config --global user.name "Sylvi"
|
||||||
|
|
||||||
|
# Set your email (use the email you registered with Gitea)
|
||||||
|
git config --global user.email "your-email@example.com"
|
||||||
|
|
||||||
|
# Set your default text editor (optional, but helpful)
|
||||||
|
git config --global core.editor "nano" # or vim, code, etc.
|
||||||
|
|
||||||
|
# Check your configuration
|
||||||
|
git config --list
|
||||||
|
```
|
||||||
|
|
||||||
|
**💡 Why This Matters**
|
||||||
|
|
||||||
|
Every change you make in git is tagged with your name and email. This helps everyone (including future you) know who made which changes and when.
|
||||||
|
|
||||||
|
## 3. Understanding File Types in Git
|
||||||
|
|
||||||
|
When working with git, you'll encounter a few special files and formats. Let's break them down:
|
||||||
|
|
||||||
|
### .git Directory
|
||||||
|
|
||||||
|
This hidden folder is where git stores all its magic. It contains the entire history of your project, all your commits, branches, and configuration. You'll never need to edit files in here directly—git handles it all. If you delete this folder, you lose all your git history (but not your current files).
|
||||||
|
|
||||||
|
### .gitignore File
|
||||||
|
|
||||||
|
This file tells git which files or folders to ignore. It's super useful for keeping temporary files, build artifacts, or sensitive data out of your repository.
|
||||||
|
```
|
||||||
|
# Example .gitignore file
|
||||||
|
# Ignore all .log files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Ignore the node_modules directory
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Ignore OS-specific files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Ignore your personal config
|
||||||
|
config/secrets.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### README.md
|
||||||
|
|
||||||
|
Not required by git, but it's a convention to include a README file (usually in Markdown format) that explains what your project is, how to use it, and any other important information. This is the first thing people see when they visit your repository.
|
||||||
|
|
||||||
|
### LICENSE
|
||||||
|
|
||||||
|
If you're sharing your code, it's good practice to include a license file that specifies how others can use your work. Common options include MIT, GPL, or Creative Commons licenses.
|
||||||
|
|
||||||
|
### Common File Formats You'll Work With
|
||||||
|
|
||||||
|
- **.md** - Markdown files (like README.md) for documentation
|
||||||
|
- **.html, .css, .js** - Web development files
|
||||||
|
- **.json** - Configuration and data files
|
||||||
|
- **.yml/.yaml** - Configuration files (common in DevOps)
|
||||||
|
- **.txt** - Plain text files
|
||||||
|
- **No extension** - Shell scripts, config files (like .gitignore)
|
||||||
|
|
||||||
|
**Binary Files:** Images (.png, .jpg), compiled code, videos, etc. Git can track these, but it can't show you the differences between versions like it can with text files.
|
||||||
|
|
||||||
|
## 4. Creating Your First Repository
|
||||||
|
|
||||||
|
There are two ways to start with git: create a new repository from scratch, or clone an existing one. Let's start with creating one locally.
|
||||||
|
|
||||||
|
### Method 1: Local Repository (Command Line)
|
||||||
|
```bash
|
||||||
|
# Create a new directory for your project
|
||||||
|
mkdir my-awesome-project
|
||||||
|
cd my-awesome-project
|
||||||
|
|
||||||
|
# Initialize a git repository
|
||||||
|
git init
|
||||||
|
|
||||||
|
# Check the status (you should see "No commits yet")
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! You now have a git repository. The `git init` command created that `.git` folder we talked about earlier.
|
||||||
|
|
||||||
|
### Method 2: Create on Gitea First (Web Interface)
|
||||||
|
|
||||||
|
You can also create a repository through Gitea's web interface, then clone it to your computer. This is often easier when you want to set up a README, license, or .gitignore right away.
|
||||||
|
|
||||||
|
1. Log into your Gitea account
|
||||||
|
2. Click the "+" icon or "New Repository" button
|
||||||
|
3. Give it a name, description, and choose whether it's public or private
|
||||||
|
4. Optionally add a README, .gitignore template, and license
|
||||||
|
5. Click "Create Repository"
|
||||||
|
|
||||||
|
Gitea will then show you the commands to clone it to your computer (we'll cover cloning in section 6).
|
||||||
|
|
||||||
|
### Practice Exercise #1
|
||||||
|
|
||||||
|
Create a new directory called `git-practice` and initialize it as a git repository. Then check its status.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
mkdir git-practice
|
||||||
|
cd git-practice
|
||||||
|
git init
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see a message like "On branch main" (or master) and "No commits yet".
|
||||||
|
|
||||||
|
## 5. The Basic Git Workflow
|
||||||
|
|
||||||
|
The core git workflow has three main stages, and once you understand these, everything else falls into place:
|
||||||
|
|
||||||
|
1. **Working Directory** - Your actual files that you're editing
|
||||||
|
2. **Staging Area** - Files you've marked to be included in the next commit
|
||||||
|
3. **Repository** - The committed history of your project
|
||||||
|
|
||||||
|
Here's the typical flow:
|
||||||
|
|
||||||
|
### Step 1: Make Changes
|
||||||
|
|
||||||
|
Create or edit files in your project.
|
||||||
|
```bash
|
||||||
|
# Create a new file
|
||||||
|
echo "# My Awesome Project" > README.md
|
||||||
|
|
||||||
|
# Check what's changed
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
|
||||||
|
Git will show you that `README.md` is an "untracked file." It knows the file exists, but it's not tracking changes to it yet.
|
||||||
|
|
||||||
|
### Step 2: Stage Changes
|
||||||
|
|
||||||
|
Tell git which changes you want to include in the next commit. This is like packing a box before you ship it.
|
||||||
|
```bash
|
||||||
|
# Stage a specific file
|
||||||
|
git add README.md
|
||||||
|
|
||||||
|
# Or stage all changes at once
|
||||||
|
git add .
|
||||||
|
|
||||||
|
# Check status again
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the file shows up under "Changes to be committed." It's in the staging area.
|
||||||
|
|
||||||
|
### Step 3: Commit Changes
|
||||||
|
|
||||||
|
Create a snapshot of your staged changes with a message describing what you did.
|
||||||
|
```bash
|
||||||
|
# Commit with a message
|
||||||
|
git commit -m "Add README file"
|
||||||
|
|
||||||
|
# View your commit history
|
||||||
|
git log
|
||||||
|
```
|
||||||
|
|
||||||
|
**💡 Writing Good Commit Messages**
|
||||||
|
|
||||||
|
Your commit messages should be clear and descriptive. Think of them as notes to your future self (or your collaborators). Good examples:
|
||||||
|
|
||||||
|
- ✅ "Add player movement controls"
|
||||||
|
- ✅ "Fix collision detection bug in level 3"
|
||||||
|
- ✅ "Update homepage layout with new color scheme"
|
||||||
|
- ❌ "stuff" (too vague)
|
||||||
|
- ❌ "fixed bug" (which bug?)
|
||||||
|
|
||||||
|
### Step 4: Push to Remote
|
||||||
|
|
||||||
|
If you're working with Gitea (or any remote repository), you'll want to push your commits to the server so they're backed up and others can see them.
|
||||||
|
```bash
|
||||||
|
# Push your commits to the remote repository
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
# Or if your default branch is named 'master'
|
||||||
|
git push origin master
|
||||||
|
```
|
||||||
|
|
||||||
|
**Origin** is the name git gives to your remote repository (the one on Gitea). **Main** (or master) is the name of your primary branch.
|
||||||
|
|
||||||
|
**⚠️ First Push Setup**
|
||||||
|
|
||||||
|
The first time you push to a new repository, you might need to set up the connection:
|
||||||
|
```bash
|
||||||
|
# Add your Gitea repository as the remote
|
||||||
|
git remote add origin https://your-gitea-instance.com/username/repo-name.git
|
||||||
|
|
||||||
|
# Set the default upstream branch
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
After this initial setup, you can just use `git push`.
|
||||||
|
|
||||||
|
### Step 5: Pull Updates
|
||||||
|
|
||||||
|
If you're collaborating with others (or working on multiple computers), you'll need to pull changes from the remote repository before you start working.
|
||||||
|
```bash
|
||||||
|
# Fetch and merge changes from the remote
|
||||||
|
git pull origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
This downloads any new commits from Gitea and merges them into your local repository.
|
||||||
|
|
||||||
|
### Practice Exercise #2
|
||||||
|
|
||||||
|
In your `git-practice` directory:
|
||||||
|
|
||||||
|
1. Create a file called `index.html` with some basic HTML
|
||||||
|
2. Stage the file
|
||||||
|
3. Commit it with a descriptive message
|
||||||
|
4. View your commit history
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
# Create the file (you can use your favorite editor instead of echo)
|
||||||
|
echo "<!DOCTYPE html><html><body><h1>Hello!</h1></body></html>" > index.html
|
||||||
|
|
||||||
|
# Stage it
|
||||||
|
git add index.html
|
||||||
|
|
||||||
|
# Commit it
|
||||||
|
git commit -m "Add basic HTML homepage"
|
||||||
|
|
||||||
|
# View history
|
||||||
|
git log
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Cloning Repositories
|
||||||
|
|
||||||
|
Cloning is how you download a copy of an existing repository from Gitea (or GitHub, GitLab, etc.) to your computer. It's like getting a complete copy of someone else's project, including all its history.
|
||||||
|
```bash
|
||||||
|
# Clone a repository
|
||||||
|
git clone https://your-gitea-instance.com/username/repo-name.git
|
||||||
|
|
||||||
|
# Clone into a specific directory name
|
||||||
|
git clone https://your-gitea-instance.com/username/repo-name.git my-local-folder
|
||||||
|
|
||||||
|
# Clone and automatically set up remote tracking
|
||||||
|
git clone https://your-gitea-instance.com/username/repo-name.git
|
||||||
|
cd repo-name
|
||||||
|
git remote -v # Shows your remote connections
|
||||||
|
```
|
||||||
|
|
||||||
|
When you clone, git automatically sets up the remote connection for you (it calls it "origin"), so you can immediately start pushing and pulling.
|
||||||
|
|
||||||
|
**💡 HTTPS vs SSH**
|
||||||
|
|
||||||
|
You'll see two types of URLs for cloning: HTTPS (`https://...`) and SSH (`git@...`). HTTPS is simpler to start with—you'll just need to enter your username and password when pushing. SSH is more secure and convenient once set up, but requires generating and adding SSH keys to Gitea.
|
||||||
|
|
||||||
|
### Practice Exercise #3
|
||||||
|
|
||||||
|
Find a repository on your Gitea instance (maybe one of Nicholai's projects, with permission!) and clone it to your computer. Explore the files and check out the commit history with `git log`.
|
||||||
|
|
||||||
|
## 7. Branching & Merging
|
||||||
|
|
||||||
|
Branches are one of git's most powerful features. They let you work on new features or experiments without affecting the main codebase. Think of them like parallel universes for your project—you can try things out in one branch, and if they don't work, you can just delete that branch without any consequences to your main code.
|
||||||
|
|
||||||
|
### Understanding Branches
|
||||||
|
|
||||||
|
When you create a new repository, you start on a default branch (usually called `main` or `master`). This is your primary timeline. When you create a new branch, you're creating a copy of the current state that you can modify independently.
|
||||||
|
|
||||||
|
### Creating and Switching Branches
|
||||||
|
```bash
|
||||||
|
# Create a new branch
|
||||||
|
git branch new-feature
|
||||||
|
|
||||||
|
# Switch to that branch
|
||||||
|
git checkout new-feature
|
||||||
|
|
||||||
|
# Or create and switch in one command
|
||||||
|
git checkout -b new-feature
|
||||||
|
|
||||||
|
# See all your branches
|
||||||
|
git branch
|
||||||
|
|
||||||
|
# The branch with * is your current branch
|
||||||
|
```
|
||||||
|
|
||||||
|
Now any commits you make will be on the `new-feature` branch, not on `main`.
|
||||||
|
|
||||||
|
### Merging Branches
|
||||||
|
|
||||||
|
Once you're happy with your changes on a branch, you can merge them back into your main branch.
|
||||||
|
```bash
|
||||||
|
# Switch back to main
|
||||||
|
git checkout main
|
||||||
|
|
||||||
|
# Merge the new-feature branch into main
|
||||||
|
git merge new-feature
|
||||||
|
|
||||||
|
# Delete the branch if you're done with it
|
||||||
|
git branch -d new-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
### A Typical Branching Workflow
|
||||||
|
|
||||||
|
1. You're working on `main` and want to add a new feature
|
||||||
|
2. Create a new branch: `git checkout -b add-menu-system`
|
||||||
|
3. Make your changes and commit them to this branch
|
||||||
|
4. Switch back to main: `git checkout main`
|
||||||
|
5. Merge your feature: `git merge add-menu-system`
|
||||||
|
6. Delete the feature branch: `git branch -d add-menu-system`
|
||||||
|
|
||||||
|
**⚠️ Merge Conflicts**
|
||||||
|
|
||||||
|
Sometimes, git can't automatically merge branches because the same lines of code were changed in both branches. This is called a **merge conflict**. Don't panic! Git will mark the conflicting sections in your files, and you'll need to manually choose which changes to keep.
|
||||||
|
|
||||||
|
Files with conflicts will look like this:
|
||||||
|
```
|
||||||
|
<<<<<<< HEAD
|
||||||
|
Your changes on the current branch
|
||||||
|
=======
|
||||||
|
Changes from the branch being merged
|
||||||
|
>>>>>>> new-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit the file to keep what you want, remove the markers, then stage and commit the resolved file.
|
||||||
|
|
||||||
|
### Practice Exercise #4
|
||||||
|
|
||||||
|
In your practice repository:
|
||||||
|
|
||||||
|
1. Create a new branch called `experiment`
|
||||||
|
2. Switch to it and make some changes to your HTML file
|
||||||
|
3. Commit those changes
|
||||||
|
4. Switch back to `main`
|
||||||
|
5. Merge the `experiment` branch into `main`
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
git checkout -b experiment
|
||||||
|
# Edit index.html with your editor
|
||||||
|
git add index.html
|
||||||
|
git commit -m "Try experimental layout"
|
||||||
|
git checkout main
|
||||||
|
git merge experiment
|
||||||
|
git branch -d experiment
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. Common Pitfalls & How to Fix Them
|
||||||
|
|
||||||
|
Everyone makes mistakes with git. Here are some common ones and how to recover from them.
|
||||||
|
|
||||||
|
### Mistake #1: Committed to the Wrong Branch
|
||||||
|
|
||||||
|
If you haven't pushed yet:
|
||||||
|
```bash
|
||||||
|
# Create a new branch with your current changes
|
||||||
|
git branch correct-branch
|
||||||
|
|
||||||
|
# Reset the current branch to the previous commit
|
||||||
|
git reset --hard HEAD~1
|
||||||
|
|
||||||
|
# Switch to the correct branch
|
||||||
|
git checkout correct-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mistake #2: Want to Undo the Last Commit
|
||||||
|
```bash
|
||||||
|
# Undo the last commit but keep the changes
|
||||||
|
git reset --soft HEAD~1
|
||||||
|
|
||||||
|
# Undo the last commit and discard the changes (careful!)
|
||||||
|
git reset --hard HEAD~1
|
||||||
|
|
||||||
|
# Undo the last commit and create a new commit that reverses it
|
||||||
|
git revert HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
`revert` is safer if you've already pushed, as it doesn't rewrite history.
|
||||||
|
|
||||||
|
### Mistake #3: Accidentally Staged the Wrong Files
|
||||||
|
```bash
|
||||||
|
# Unstage a specific file
|
||||||
|
git reset HEAD file-name.txt
|
||||||
|
|
||||||
|
# Unstage all files
|
||||||
|
git reset HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mistake #4: Need to See What Changed
|
||||||
|
```bash
|
||||||
|
# See unstaged changes
|
||||||
|
git diff
|
||||||
|
|
||||||
|
# See staged changes
|
||||||
|
git diff --staged
|
||||||
|
|
||||||
|
# See changes in a specific file
|
||||||
|
git diff file-name.txt
|
||||||
|
|
||||||
|
# See changes between commits
|
||||||
|
git diff commit1 commit2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mistake #5: Merge Conflict Panic
|
||||||
|
|
||||||
|
If you're in the middle of a merge and want to abort:
|
||||||
|
```bash
|
||||||
|
git merge --abort
|
||||||
|
```
|
||||||
|
|
||||||
|
To resolve conflicts properly:
|
||||||
|
|
||||||
|
1. Open the conflicting files
|
||||||
|
2. Look for the conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`)
|
||||||
|
3. Decide which changes to keep and edit the file
|
||||||
|
4. Remove the conflict markers
|
||||||
|
5. Stage the resolved file: `git add file-name.txt`
|
||||||
|
6. Complete the merge: `git commit`
|
||||||
|
|
||||||
|
### Mistake #6: Pushed Sensitive Data (Passwords, Keys, etc.)
|
||||||
|
|
||||||
|
**⚠️ Important Security Warning**
|
||||||
|
|
||||||
|
If you accidentally commit and push sensitive information (passwords, API keys, etc.), removing it from git history isn't enough—you should consider that data compromised. Change the passwords/keys immediately, then remove them from git history.
|
||||||
|
|
||||||
|
This is why .gitignore is so important! Always ignore files with sensitive data.
|
||||||
|
|
||||||
|
## 9. Using Gitea's Web Interface
|
||||||
|
|
||||||
|
While the command line is powerful, Gitea's web interface can be more convenient for certain tasks.
|
||||||
|
|
||||||
|
### Viewing Files and Commits
|
||||||
|
|
||||||
|
Browse your repository's files, view the commit history, and see who changed what. Click on any commit to see the exact changes (the "diff").
|
||||||
|
|
||||||
|
### Creating and Editing Files
|
||||||
|
|
||||||
|
You can create new files or edit existing ones directly in the browser. This is handy for quick fixes or updating documentation.
|
||||||
|
|
||||||
|
### Pull Requests
|
||||||
|
|
||||||
|
If you're collaborating with Nicholai or others, pull requests (PRs) are how you propose changes. You create a branch, push your changes, then open a PR in Gitea to ask for those changes to be merged into the main branch. Others can review your code and suggest changes before merging.
|
||||||
|
|
||||||
|
### Issues
|
||||||
|
|
||||||
|
Gitea has a built-in issue tracker for reporting bugs, requesting features, or discussing ideas. Each issue can have labels, assignees, and comments.
|
||||||
|
|
||||||
|
### Releases
|
||||||
|
|
||||||
|
When your project reaches a milestone, you can create a release—a snapshot of your code at that point, usually with a version number (like v1.0.0).
|
||||||
|
|
||||||
|
**💡 Gitea vs GitHub**
|
||||||
|
|
||||||
|
Gitea is very similar to GitHub—the interface and workflows are almost identical. If you learn one, you'll be comfortable with the other. The main difference is that Gitea is self-hosted (running on Nicholai's server), while GitHub is a cloud service.
|
||||||
|
|
||||||
|
## 10. Quick Reference Cheat Sheet
|
||||||
|
|
||||||
|
### Repository Setup
|
||||||
|
```bash
|
||||||
|
git init # Create a new repository
|
||||||
|
git clone <url> # Clone an existing repository
|
||||||
|
git remote add origin <url> # Connect to a remote repository
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic Workflow
|
||||||
|
```bash
|
||||||
|
git status # Check what's changed
|
||||||
|
git add <file> # Stage a file
|
||||||
|
git add . # Stage all changes
|
||||||
|
git commit -m "message" # Commit staged changes
|
||||||
|
git push origin main # Push to remote
|
||||||
|
git pull origin main # Pull from remote
|
||||||
|
```
|
||||||
|
|
||||||
|
### Branching
|
||||||
|
```bash
|
||||||
|
git branch # List branches
|
||||||
|
git branch <name> # Create a new branch
|
||||||
|
git checkout <branch> # Switch to a branch
|
||||||
|
git checkout -b <branch> # Create and switch to a new branch
|
||||||
|
git merge <branch> # Merge a branch into current branch
|
||||||
|
git branch -d <branch> # Delete a branch
|
||||||
|
```
|
||||||
|
|
||||||
|
### Viewing History
|
||||||
|
```bash
|
||||||
|
git log # View commit history
|
||||||
|
git log --oneline # Compact commit history
|
||||||
|
git diff # See unstaged changes
|
||||||
|
git diff --staged # See staged changes
|
||||||
|
git show <commit> # Show details of a commit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Undoing Changes
|
||||||
|
```bash
|
||||||
|
git reset HEAD <file> # Unstage a file
|
||||||
|
git reset --soft HEAD~1 # Undo last commit, keep changes
|
||||||
|
git reset --hard HEAD~1 # Undo last commit, discard changes
|
||||||
|
git revert <commit> # Create new commit that undoes changes
|
||||||
|
git checkout -- <file> # Discard changes to a file
|
||||||
|
```
|
||||||
|
|
||||||
|
### Helpful Commands
|
||||||
|
```bash
|
||||||
|
git config --list # View your configuration
|
||||||
|
git remote -v # View remote connections
|
||||||
|
git branch -a # List all branches (including remote)
|
||||||
|
git fetch # Download remote changes without merging
|
||||||
|
git clean -fd # Remove untracked files and directories
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Where to Go From Here
|
||||||
|
|
||||||
|
This guide covers the essentials, but git has a lot more to offer. As you get comfortable with these basics, you might want to explore:
|
||||||
|
|
||||||
|
- **Git hooks** - Automate tasks when certain git events happen
|
||||||
|
- **Rebasing** - An alternative to merging that keeps history cleaner
|
||||||
|
- **Stashing** - Temporarily save changes without committing
|
||||||
|
- **Submodules** - Include other git repositories within your project
|
||||||
|
- **Tags** - Mark specific commits as important milestones
|
||||||
|
|
||||||
|
The best way to learn git is to use it. Start with small personal projects, make frequent commits with good messages, and don't be afraid to experiment—that's what branches are for! And remember, Nicholai's just a message away if you get stuck.
|
||||||
|
|
||||||
|
Happy coding! 🌱
|
||||||
|
|
||||||
138
components/ContentRenderer.tsx
Normal file
138
components/ContentRenderer.tsx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ContentBlock } from '../types';
|
||||||
|
import { TerminalBlock } from './TerminalBlock';
|
||||||
|
import { GitWorkflowDiagram } from './GitWorkflowDiagram';
|
||||||
|
import { AlertTriangle, Lightbulb, Info, CheckCircle2, ChevronDown } from 'lucide-react';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
|
||||||
|
interface ContentRendererProps {
|
||||||
|
block: ContentBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Callout: React.FC<{ variant?: string; content: string }> = ({ variant = 'info', content }) => {
|
||||||
|
const getIcon = () => {
|
||||||
|
switch(variant) {
|
||||||
|
case 'warning': return <AlertTriangle className="text-destructive" />;
|
||||||
|
case 'tip': return <Lightbulb className="text-accent" />;
|
||||||
|
case 'success': return <CheckCircle2 className="text-primary" />;
|
||||||
|
default: return <Info className="text-blue-500" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = () => {
|
||||||
|
switch(variant) {
|
||||||
|
case 'warning': return 'bg-destructive/10 border-destructive/20 text-destructive-foreground';
|
||||||
|
case 'tip': return 'bg-accent/10 border-accent/20 text-accent-foreground';
|
||||||
|
case 'success': return 'bg-primary/10 border-primary/20 text-primary-foreground';
|
||||||
|
default: return 'bg-blue-50 border-blue-200 text-blue-900';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adjusting styles to match new variables more closely for text colors where simple bg/text classes might fail due to variable usage
|
||||||
|
// The tailwind classes using vars (like text-primary) work if defined in config, which we did.
|
||||||
|
const getContainerClass = () => {
|
||||||
|
switch(variant) {
|
||||||
|
case 'warning': return 'bg-red-50 border-red-200 text-red-900';
|
||||||
|
case 'tip': return 'bg-amber-50 border-amber-200 text-amber-900';
|
||||||
|
case 'success': return 'bg-green-50 border-green-200 text-green-900';
|
||||||
|
default: return 'bg-blue-50 border-blue-200 text-blue-900';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`flex gap-4 p-4 my-4 rounded-lg border-l-4 ${getContainerClass()}`}>
|
||||||
|
<div className="shrink-0 pt-0.5">{getIcon()}</div>
|
||||||
|
<p className="text-sm md:text-base">{content}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Collapsible: React.FC<{ content: any, variant?: string }> = ({ content, variant }) => {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
const bodyRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!contentRef.current || !bodyRef.current) return;
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
gsap.to(bodyRef.current, {
|
||||||
|
height: contentRef.current.scrollHeight,
|
||||||
|
duration: 0.4,
|
||||||
|
ease: "power2.out",
|
||||||
|
opacity: 1
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
gsap.to(bodyRef.current, {
|
||||||
|
height: 0,
|
||||||
|
duration: 0.3,
|
||||||
|
ease: "power2.in",
|
||||||
|
opacity: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const isPractice = variant === 'success';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`my-4 border rounded-lg overflow-hidden transition-all duration-300 ${isPractice ? 'border-primary/20 shadow-sm' : 'border-border bg-card'}`}>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
className={`w-full flex items-center justify-between p-4 text-left hover:bg-muted/50 transition-colors ${isPractice ? 'bg-primary/5' : ''}`}
|
||||||
|
>
|
||||||
|
<span className={`font-semibold ${isPractice ? 'text-primary' : 'text-card-foreground'}`}>
|
||||||
|
{content.title}
|
||||||
|
</span>
|
||||||
|
<ChevronDown
|
||||||
|
className={`transform transition-transform duration-300 text-muted-foreground ${isOpen ? 'rotate-180' : ''}`}
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div ref={bodyRef} className="h-0 opacity-0 overflow-hidden">
|
||||||
|
<div ref={contentRef} className="p-4 pt-0 border-t border-border">
|
||||||
|
{Array.isArray(content.body) && content.body.map((subBlock: ContentBlock, idx: number) => (
|
||||||
|
<ContentRenderer key={idx} block={subBlock} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ContentRenderer: React.FC<ContentRendererProps> = ({ block }) => {
|
||||||
|
switch (block.type) {
|
||||||
|
case 'paragraph':
|
||||||
|
return <p className="mb-4 text-muted-foreground leading-relaxed text-lg">{block.content as string}</p>;
|
||||||
|
case 'code':
|
||||||
|
return <TerminalBlock code={block.content as string} language={block.language} />;
|
||||||
|
case 'callout':
|
||||||
|
return <Callout variant={block.variant} content={block.content as string} />;
|
||||||
|
case 'list':
|
||||||
|
return (
|
||||||
|
<ul className="list-disc pl-6 mb-4 space-y-2 text-muted-foreground">
|
||||||
|
{(block.content as string[]).map((item, i) => (
|
||||||
|
<li key={i} className="pl-2">{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
case 'ordered-list':
|
||||||
|
return (
|
||||||
|
<ol className="list-decimal pl-6 mb-4 space-y-2 text-muted-foreground">
|
||||||
|
{(block.content as string[]).map((item, i) => (
|
||||||
|
<li key={i} className="pl-2">{item}</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
case 'subheading':
|
||||||
|
return <h3 className="text-xl font-bold text-foreground mt-8 mb-4 flex items-center gap-2">
|
||||||
|
<span className="w-1.5 h-6 bg-primary rounded-full inline-block"></span>
|
||||||
|
{block.content as string}
|
||||||
|
</h3>;
|
||||||
|
case 'collapsible':
|
||||||
|
return <Collapsible content={block.content} variant={block.variant} />;
|
||||||
|
case 'diagram':
|
||||||
|
return <GitWorkflowDiagram />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
183
components/GitWorkflowDiagram.tsx
Normal file
183
components/GitWorkflowDiagram.tsx
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
import { FileText, ArrowRight, Database, Layers, HardDrive, RefreshCw } from 'lucide-react';
|
||||||
|
|
||||||
|
export const GitWorkflowDiagram: React.FC = () => {
|
||||||
|
const [state, setState] = useState<'modified' | 'staged' | 'committed'>('modified');
|
||||||
|
|
||||||
|
// Refs for animation targets
|
||||||
|
const fileRef = useRef<HTMLDivElement>(null);
|
||||||
|
const workingDirRef = useRef<HTMLDivElement>(null);
|
||||||
|
const stagingRef = useRef<HTMLDivElement>(null);
|
||||||
|
const repoRef = useRef<HTMLDivElement>(null);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Initial entrance animation
|
||||||
|
gsap.fromTo(containerRef.current,
|
||||||
|
{ opacity: 0, scale: 0.95 },
|
||||||
|
{ opacity: 1, scale: 1, duration: 0.8, ease: "power2.out" }
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ctx = gsap.context(() => {
|
||||||
|
// Reset file position when state changes, then animate to new spot
|
||||||
|
// Note: In a real complex app we might calculate exact coordinates using getBoundingClientRect
|
||||||
|
// For this demo, we'll rely on the React re-render to place it in the DOM, then animate "from" the previous position if possible,
|
||||||
|
// or just animate "in" to emphasize the move.
|
||||||
|
|
||||||
|
// Simple scale/pop effect on change
|
||||||
|
if (fileRef.current) {
|
||||||
|
gsap.fromTo(fileRef.current,
|
||||||
|
{ scale: 0.5, opacity: 0, y: 20 },
|
||||||
|
{ scale: 1, opacity: 1, y: 0, duration: 0.5, ease: "back.out(1.7)" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, containerRef);
|
||||||
|
|
||||||
|
return () => ctx.revert();
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
setState('modified');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className="my-10 p-4 sm:p-6 bg-card rounded-xl border border-border shadow-sm overflow-hidden">
|
||||||
|
<div className="flex justify-between items-center mb-6 border-b border-border pb-4">
|
||||||
|
<h3 className="font-bold text-lg text-primary">Interactive Workflow</h3>
|
||||||
|
<button
|
||||||
|
onClick={handleReset}
|
||||||
|
className="flex items-center gap-2 text-xs text-muted-foreground hover:text-primary transition-colors"
|
||||||
|
>
|
||||||
|
<RefreshCw size={14} />
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Zones Container */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 relative min-h-[300px]">
|
||||||
|
|
||||||
|
{/* Working Directory Zone */}
|
||||||
|
<div ref={workingDirRef} className={`relative p-4 rounded-lg border-2 transition-colors duration-300 ${state === 'modified' ? 'border-primary bg-primary/5' : 'border-dashed border-muted'}`}>
|
||||||
|
<div className="flex items-center gap-2 mb-4 text-sm font-semibold text-foreground/80">
|
||||||
|
<HardDrive size={18} />
|
||||||
|
<span>Working Directory</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground mb-8">Where you edit files.</p>
|
||||||
|
|
||||||
|
{state === 'modified' && (
|
||||||
|
<div ref={fileRef} className="bg-white p-3 rounded shadow-md border border-gray-200 flex items-center gap-3">
|
||||||
|
<FileText className="text-orange-500" />
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-bold">script.js</div>
|
||||||
|
<div className="text-xs text-orange-600">Modified</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="absolute bottom-4 left-0 w-full flex justify-center">
|
||||||
|
<button
|
||||||
|
disabled={state !== 'modified'}
|
||||||
|
onClick={() => setState('staged')}
|
||||||
|
className={`
|
||||||
|
px-4 py-2 rounded-full text-xs font-bold transition-all
|
||||||
|
${state === 'modified'
|
||||||
|
? 'bg-primary text-primary-foreground hover:bg-primary/90 hover:scale-105 shadow-md cursor-pointer'
|
||||||
|
: 'bg-muted text-muted-foreground cursor-not-allowed opacity-50'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
git add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Staging Area Zone */}
|
||||||
|
<div ref={stagingRef} className={`relative p-4 rounded-lg border-2 transition-colors duration-300 ${state === 'staged' ? 'border-primary bg-primary/5' : 'border-dashed border-muted'}`}>
|
||||||
|
<div className="flex items-center gap-2 mb-4 text-sm font-semibold text-foreground/80">
|
||||||
|
<Layers size={18} />
|
||||||
|
<span>Staging Area</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground mb-8">Files ready to commit.</p>
|
||||||
|
|
||||||
|
{state === 'staged' && (
|
||||||
|
<div ref={fileRef} className="bg-white p-3 rounded shadow-md border border-gray-200 flex items-center gap-3">
|
||||||
|
<FileText className="text-green-500" />
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-bold">script.js</div>
|
||||||
|
<div className="text-xs text-green-600">Staged</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="absolute bottom-4 left-0 w-full flex justify-center">
|
||||||
|
<button
|
||||||
|
disabled={state !== 'staged'}
|
||||||
|
onClick={() => setState('committed')}
|
||||||
|
className={`
|
||||||
|
px-4 py-2 rounded-full text-xs font-bold transition-all
|
||||||
|
${state === 'staged'
|
||||||
|
? 'bg-primary text-primary-foreground hover:bg-primary/90 hover:scale-105 shadow-md cursor-pointer'
|
||||||
|
: 'bg-muted text-muted-foreground cursor-not-allowed opacity-50'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
git commit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Repository Zone */}
|
||||||
|
<div ref={repoRef} className={`relative p-4 rounded-lg border-2 transition-colors duration-300 ${state === 'committed' ? 'border-primary bg-primary/5' : 'border-dashed border-muted'}`}>
|
||||||
|
<div className="flex items-center gap-2 mb-4 text-sm font-semibold text-foreground/80">
|
||||||
|
<Database size={18} />
|
||||||
|
<span>Repository</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground mb-8">Saved history.</p>
|
||||||
|
|
||||||
|
{state === 'committed' && (
|
||||||
|
<div ref={fileRef} className="bg-white p-3 rounded shadow-md border border-gray-200 flex items-center gap-3">
|
||||||
|
<FileText className="text-blue-500" />
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-bold">script.js</div>
|
||||||
|
<div className="text-xs text-blue-600">Committed</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="absolute bottom-4 left-0 w-full flex justify-center">
|
||||||
|
<button
|
||||||
|
disabled={state !== 'committed'}
|
||||||
|
className={`
|
||||||
|
px-4 py-2 rounded-full text-xs font-bold transition-all
|
||||||
|
${state === 'committed'
|
||||||
|
? 'bg-secondary text-secondary-foreground cursor-default'
|
||||||
|
: 'bg-muted text-muted-foreground cursor-not-allowed opacity-50'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Saved!
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Connecting Arrows (Visual only, hidden on mobile for cleaner look) */}
|
||||||
|
<div className="hidden md:block absolute top-1/2 left-[33%] -translate-y-1/2 -translate-x-1/2 z-10 text-muted-foreground/30">
|
||||||
|
<ArrowRight size={24} />
|
||||||
|
</div>
|
||||||
|
<div className="hidden md:block absolute top-1/2 left-[66%] -translate-y-1/2 -translate-x-1/2 z-10 text-muted-foreground/30">
|
||||||
|
<ArrowRight size={24} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 p-3 bg-muted/30 rounded text-xs text-muted-foreground text-center">
|
||||||
|
{state === 'modified' && "You have changed a file. It's in your Working Directory."}
|
||||||
|
{state === 'staged' && "You've added the file to the Staging Area. It's ready to be part of the next snapshot."}
|
||||||
|
{state === 'committed' && "Success! The file is safely stored in the Repository history."}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
99
components/Sidebar.tsx
Normal file
99
components/Sidebar.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { GUIDE_CONTENT } from '../constants';
|
||||||
|
import { Leaf, Menu, X, GitBranch } from 'lucide-react';
|
||||||
|
|
||||||
|
export const Sidebar: React.FC = () => {
|
||||||
|
const [activeId, setActiveId] = useState<string>('');
|
||||||
|
const [isMobileOpen, setIsMobileOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setActiveId(entry.target.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ rootMargin: '-20% 0px -60% 0px' }
|
||||||
|
);
|
||||||
|
|
||||||
|
GUIDE_CONTENT.forEach((section) => {
|
||||||
|
const element = document.getElementById(section.id);
|
||||||
|
if (element) observer.observe(element);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const scrollToSection = (id: string) => {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
setIsMobileOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Mobile Toggle */}
|
||||||
|
<div className="fixed top-4 right-4 z-50 lg:hidden">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsMobileOpen(!isMobileOpen)}
|
||||||
|
className="p-2 bg-card rounded-full shadow-lg border border-border text-foreground"
|
||||||
|
>
|
||||||
|
{isMobileOpen ? <X size={24} /> : <Menu size={24} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sidebar Container */}
|
||||||
|
<nav className={`
|
||||||
|
fixed top-0 left-0 h-full bg-card/95 backdrop-blur-md border-r border-border
|
||||||
|
w-72 transform transition-transform duration-300 z-40
|
||||||
|
${isMobileOpen ? 'translate-x-0' : '-translate-x-full'}
|
||||||
|
lg:translate-x-0
|
||||||
|
`}>
|
||||||
|
<div className="p-6 h-full flex flex-col">
|
||||||
|
<div className="flex items-center gap-3 mb-10 group cursor-default">
|
||||||
|
<div className="p-2 bg-primary/10 rounded-lg group-hover:bg-primary/20 transition-colors">
|
||||||
|
<Leaf className="text-primary" size={24} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="font-bold text-foreground text-lg leading-tight">Git for Sylvi</h1>
|
||||||
|
<span className="text-xs text-muted-foreground font-mono">v1.0.0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto pr-2 custom-scrollbar">
|
||||||
|
<div className="space-y-1">
|
||||||
|
{GUIDE_CONTENT.map((section) => (
|
||||||
|
<button
|
||||||
|
key={section.id}
|
||||||
|
onClick={() => scrollToSection(section.id)}
|
||||||
|
className={`
|
||||||
|
w-full text-left px-4 py-3 rounded-md text-sm transition-all duration-200
|
||||||
|
flex items-center gap-3 group
|
||||||
|
${activeId === section.id
|
||||||
|
? 'bg-primary/10 text-primary font-semibold shadow-sm'
|
||||||
|
: 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<span className={`w-1.5 h-1.5 rounded-full transition-colors ${activeId === section.id ? 'bg-primary' : 'bg-muted-foreground/30 group-hover:bg-muted-foreground'}`}></span>
|
||||||
|
<span className="truncate">{section.title.split('. ')[1]}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 pt-6 border-t border-border">
|
||||||
|
<div className="flex items-center gap-3 text-sm text-muted-foreground">
|
||||||
|
<GitBranch size={16} />
|
||||||
|
<span>Happy Coding! 🌱</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
163
components/TerminalBlock.tsx
Normal file
163
components/TerminalBlock.tsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import React, { useRef, useLayoutEffect, useState } from 'react';
|
||||||
|
import { Copy, Check, Sprout, Leaf, Wind } from 'lucide-react';
|
||||||
|
import gsap from 'gsap';
|
||||||
|
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||||
|
|
||||||
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|
||||||
|
interface TerminalBlockProps {
|
||||||
|
code: string;
|
||||||
|
language?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TerminalBlock: React.FC<TerminalBlockProps> = ({ code, language = 'bash' }) => {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const copyBtnRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
const handleCopy = () => {
|
||||||
|
navigator.clipboard.writeText(code);
|
||||||
|
setCopied(true);
|
||||||
|
|
||||||
|
// Organic "rustle" animation on click
|
||||||
|
if (copyBtnRef.current) {
|
||||||
|
gsap.to(copyBtnRef.current, {
|
||||||
|
rotate: 15,
|
||||||
|
scale: 1.1,
|
||||||
|
duration: 0.1,
|
||||||
|
yoyo: true,
|
||||||
|
repeat: 1,
|
||||||
|
ease: "sine.inOut"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const ctx = gsap.context(() => {
|
||||||
|
// Growth animation using a "spring-like" organic entry
|
||||||
|
gsap.fromTo(containerRef.current,
|
||||||
|
{
|
||||||
|
opacity: 0,
|
||||||
|
y: 40,
|
||||||
|
scale: 0.95,
|
||||||
|
skewX: -1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
scale: 1,
|
||||||
|
skewX: 0,
|
||||||
|
duration: 1,
|
||||||
|
ease: "elastic.out(1, 0.75)",
|
||||||
|
scrollTrigger: {
|
||||||
|
trigger: containerRef.current,
|
||||||
|
start: "top 92%",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, containerRef);
|
||||||
|
|
||||||
|
return () => ctx.revert();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
// Using OKLCH based on the primary hue (142.77) for a perfectly matched "dark forest" look
|
||||||
|
className="my-10 rounded-[2rem] overflow-hidden border border-primary/20 shadow-2xl relative group bg-[oklch(0.18_0.02_142.77)]"
|
||||||
|
style={{
|
||||||
|
boxShadow: '0 20px 50px -12px oklch(0.15 0.05 142.77 / 0.5)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Texture Overlay from index.html */}
|
||||||
|
<div className="absolute inset-0 grain-texture opacity-10 pointer-events-none" />
|
||||||
|
|
||||||
|
{/* Nature-inspired Gradient Mask */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-accent/5 pointer-events-none" />
|
||||||
|
|
||||||
|
{/* Organic Header - "The Pebble" style */}
|
||||||
|
<div className="flex items-center justify-between px-8 py-4 bg-[oklch(0.25_0.03_142.77)]/80 backdrop-blur-md border-b border-white/5 relative z-10">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
{/* Organic leaf-shaded dots */}
|
||||||
|
<div className="w-2.5 h-2.5 rounded-full bg-primary animate-pulse" style={{ animationDelay: '0s' }}></div>
|
||||||
|
<div className="w-2.5 h-2.5 rounded-full bg-accent animate-pulse" style={{ animationDelay: '0.2s' }}></div>
|
||||||
|
<div className="w-2.5 h-2.5 rounded-full bg-secondary animate-pulse" style={{ animationDelay: '0.4s' }}></div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10">
|
||||||
|
<Sprout size={14} className="text-primary" />
|
||||||
|
<span className="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-foreground/70">
|
||||||
|
{language}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
ref={copyBtnRef}
|
||||||
|
onClick={handleCopy}
|
||||||
|
className={`
|
||||||
|
relative overflow-hidden px-4 py-2 rounded-full transition-all duration-500 flex items-center gap-2 group/btn
|
||||||
|
${copied
|
||||||
|
? 'bg-primary text-primary-foreground'
|
||||||
|
: 'bg-white/5 hover:bg-white/10 text-white/50 hover:text-white'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div className="relative z-10 flex items-center gap-2">
|
||||||
|
{copied ? (
|
||||||
|
<>
|
||||||
|
<Check size={14} className="stroke-[3px]" />
|
||||||
|
<span className="text-[10px] font-black uppercase">Copied!</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Copy size={14} className="group-hover/btn:rotate-12 transition-transform" />
|
||||||
|
<span className="text-[10px] font-black uppercase">Copy</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* Hover leaf effect */}
|
||||||
|
{!copied && (
|
||||||
|
<Wind
|
||||||
|
size={16}
|
||||||
|
className="absolute -right-4 top-1/2 -translate-y-1/2 opacity-0 group-hover/btn:right-2 group-hover/btn:opacity-20 transition-all duration-500 text-primary"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Code Area */}
|
||||||
|
<div className="p-8 overflow-x-auto relative z-10 custom-scrollbar">
|
||||||
|
<pre className="leading-relaxed font-mono selection:bg-primary/40 selection:text-white">
|
||||||
|
<code className="text-[oklch(0.95_0.02_142.77)]">
|
||||||
|
{code.split('\n').map((line, i) => {
|
||||||
|
const isComment = line.trim().startsWith('#');
|
||||||
|
const isCommand = line.trim().startsWith('git');
|
||||||
|
return (
|
||||||
|
<span key={i} className="block relative group/line">
|
||||||
|
{/* Subtle line highlight */}
|
||||||
|
<div className="absolute -left-8 -right-8 top-0 bottom-0 bg-primary/5 opacity-0 group-hover/line:opacity-100 transition-opacity pointer-events-none" />
|
||||||
|
|
||||||
|
<span className={isComment ? 'text-primary/50 italic' : isCommand ? 'text-accent font-bold' : ''}>
|
||||||
|
{line}
|
||||||
|
</span>
|
||||||
|
{'\n'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Decorative Organic Elements */}
|
||||||
|
<div className="absolute bottom-4 right-4 opacity-5 group-hover:opacity-20 transition-all duration-1000 transform group-hover:scale-110 group-hover:rotate-6 pointer-events-none">
|
||||||
|
<Leaf size={120} className="text-primary" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* "Growing" accent line at bottom */}
|
||||||
|
<div className="absolute bottom-0 left-0 h-1 bg-gradient-to-r from-transparent via-primary/30 to-transparent w-full scale-x-0 group-hover:scale-x-100 transition-transform duration-1000 origin-center" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
244
constants.tsx
Normal file
244
constants.tsx
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
import { SectionData } from './types';
|
||||||
|
|
||||||
|
export const GUIDE_CONTENT: SectionData[] = [
|
||||||
|
{
|
||||||
|
id: "intro",
|
||||||
|
title: "1. What is Git?",
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
content: "Git is a version control system. Imagine you're working on a game design document, and you want to try out a new idea for a level, but you're not sure if it'll work. With git, you can save your current version, try the new idea, and if it doesn't work out, you can go back to exactly where you were before. No \"final_FINAL_v3_actually_final.txt\" nonsense."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
content: "Git tracks changes to files over time, creating a history of your project. Every time you \"save\" (which we call a commit), git takes a snapshot of your entire project. You can view any of these snapshots at any time, compare them, or even combine changes from different versions."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "setup",
|
||||||
|
title: "2. Initial Setup",
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
content: "First, let's configure git with your information. Open your terminal and run these commands:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'code',
|
||||||
|
language: 'bash',
|
||||||
|
content: `# Set your name (this will appear in your commits)
|
||||||
|
git config --global user.name "Sylvi"
|
||||||
|
|
||||||
|
# Set your email (use the email you registered with Gitea)
|
||||||
|
git config --global user.email "your-email@example.com"
|
||||||
|
|
||||||
|
# Set your default text editor (optional, but helpful)
|
||||||
|
git config --global core.editor "nano"
|
||||||
|
|
||||||
|
# Check your configuration
|
||||||
|
git config --list`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'callout',
|
||||||
|
variant: 'tip',
|
||||||
|
content: "Every change you make in git is tagged with your name and email. This helps everyone (including future you) know who made which changes and when."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "file-types",
|
||||||
|
title: "3. Understanding Files",
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
content: "When working with git, you'll encounter a few special files and formats. Let's break them down:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'subheading',
|
||||||
|
content: ".git Directory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
content: "This hidden folder is where git stores all its magic. It contains the entire history of your project. You'll never need to edit files in here directly—git handles it all."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'subheading',
|
||||||
|
content: ".gitignore File"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
content: "This file tells git which files or folders to ignore. It's super useful for keeping temporary files, build artifacts, or sensitive data out of your repository."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'code',
|
||||||
|
language: 'plaintext',
|
||||||
|
content: `# Example .gitignore file
|
||||||
|
*.log
|
||||||
|
node_modules/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
config/secrets.json`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'collapsible',
|
||||||
|
content: {
|
||||||
|
title: "📄 Common File Formats You'll Work With",
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
content: [
|
||||||
|
".md - Markdown files (like README.md) for documentation",
|
||||||
|
".html, .css, .js - Web development files",
|
||||||
|
".json - Configuration and data files",
|
||||||
|
".yml/.yaml - Configuration files",
|
||||||
|
"Binary Files: Images, compiled code, etc."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "creating-repo",
|
||||||
|
title: "4. Creating Your First Repo",
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
content: "There are two ways to start with git: create a new repository from scratch, or clone an existing one."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'subheading',
|
||||||
|
content: "Method 1: Local Repository"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'code',
|
||||||
|
language: 'bash',
|
||||||
|
content: `# Create a new directory for your project
|
||||||
|
mkdir my-awesome-project
|
||||||
|
cd my-awesome-project
|
||||||
|
|
||||||
|
# Initialize a git repository
|
||||||
|
git init
|
||||||
|
|
||||||
|
# Check the status (you should see "No commits yet")
|
||||||
|
git status`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'subheading',
|
||||||
|
content: "Method 2: Create on Gitea First"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ordered-list',
|
||||||
|
content: [
|
||||||
|
"Log into your Gitea account",
|
||||||
|
"Click the '+' icon or 'New Repository' button",
|
||||||
|
"Give it a name and description",
|
||||||
|
"Click 'Create Repository'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'collapsible',
|
||||||
|
variant: 'success',
|
||||||
|
content: {
|
||||||
|
title: "✏️ Practice Exercise #1",
|
||||||
|
body: [
|
||||||
|
{ type: 'paragraph', content: "Create a new directory called 'git-practice' and initialize it as a git repository. Then check its status." },
|
||||||
|
{ type: 'subheading', content: "Solution:" },
|
||||||
|
{ type: 'code', language: 'bash', content: "mkdir git-practice\ncd git-practice\ngit init\ngit status" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "basic-workflow",
|
||||||
|
title: "5. The Basic Workflow",
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
content: "The core git workflow has three main stages: Working Directory (your files), Staging Area (files marked for commit), and Repository (saved history)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'diagram',
|
||||||
|
content: 'workflow'
|
||||||
|
},
|
||||||
|
{ type: 'subheading', content: "Step 1: Make Changes" },
|
||||||
|
{ type: 'code', language: 'bash', content: `echo "# My Awesome Project" > README.md\ngit status` },
|
||||||
|
|
||||||
|
{ type: 'subheading', content: "Step 2: Stage Changes" },
|
||||||
|
{ type: 'paragraph', content: "Tell git which changes you want to include in the next commit." },
|
||||||
|
{ type: 'code', language: 'bash', content: `git add README.md\n# Or stage all\ngit add .` },
|
||||||
|
|
||||||
|
{ type: 'subheading', content: "Step 3: Commit Changes" },
|
||||||
|
{ type: 'paragraph', content: "Create a snapshot of your staged changes." },
|
||||||
|
{ type: 'code', language: 'bash', content: `git commit -m "Add README file"` },
|
||||||
|
|
||||||
|
{ type: 'callout', variant: 'info', content: "Tip: Write clear commit messages! 'Add player movement controls' is better than 'stuff'." },
|
||||||
|
|
||||||
|
{ type: 'subheading', content: "Step 4: Push to Remote" },
|
||||||
|
{ type: 'code', language: 'bash', content: `git push origin main` },
|
||||||
|
{ type: 'callout', variant: 'warning', content: "First push? You might need: git push -u origin main" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "cloning",
|
||||||
|
title: "6. Cloning Repositories",
|
||||||
|
blocks: [
|
||||||
|
{ type: 'paragraph', content: "Cloning is how you download a copy of an existing repository from Gitea to your computer." },
|
||||||
|
{ type: 'code', language: 'bash', content: `git clone https://your-gitea-instance.com/username/repo-name.git` },
|
||||||
|
{ type: 'callout', variant: 'info', content: "HTTPS is simpler (username/password). SSH is more secure but requires key setup." }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "branching",
|
||||||
|
title: "7. Branching & Merging",
|
||||||
|
blocks: [
|
||||||
|
{ type: 'paragraph', content: "Branches are like parallel universes for your project. You can try things out without affecting the main code." },
|
||||||
|
{ type: 'code', language: 'bash', content: `# Create and switch to new branch
|
||||||
|
git checkout -b new-feature
|
||||||
|
|
||||||
|
# Make changes...
|
||||||
|
|
||||||
|
# Switch back to main
|
||||||
|
git checkout main
|
||||||
|
|
||||||
|
# Merge the new feature
|
||||||
|
git merge new-feature
|
||||||
|
|
||||||
|
# Delete the old branch
|
||||||
|
git branch -d new-feature` },
|
||||||
|
{ type: 'callout', variant: 'warning', content: "Merge Conflicts happen when git can't automatically combine changes. Don't panic! Open the file, look for <<<<<<< HEAD, fix it, and commit." }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "common-pitfalls",
|
||||||
|
title: "8. Common Pitfalls",
|
||||||
|
blocks: [
|
||||||
|
{ type: 'collapsible', content: { title: "Mistake #1: Committed to Wrong Branch", body: [
|
||||||
|
{ type: 'paragraph', content: "If you haven't pushed yet:" },
|
||||||
|
{ type: 'code', language: 'bash', content: `git branch correct-branch\ngit reset --hard HEAD~1\ngit checkout correct-branch` }
|
||||||
|
]} },
|
||||||
|
{ type: 'collapsible', content: { title: "Mistake #2: Undo Last Commit", body: [
|
||||||
|
{ type: 'paragraph', content: "Undo commit but keep changes:" },
|
||||||
|
{ type: 'code', language: 'bash', content: `git reset --soft HEAD~1` }
|
||||||
|
]} },
|
||||||
|
{ type: 'collapsible', content: { title: "Mistake #3: Pushed Sensitive Data", body: [
|
||||||
|
{ type: 'callout', variant: 'warning', content: "If you push passwords/keys, consider them compromised. Change the passwords immediately." }
|
||||||
|
]} }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "cheat-sheet",
|
||||||
|
title: "9. Cheat Sheet",
|
||||||
|
blocks: [
|
||||||
|
{ type: 'paragraph', content: "Quick reference for your daily workflow." },
|
||||||
|
{ type: 'code', language: 'bash', content: `git status # Check status
|
||||||
|
git add . # Stage all
|
||||||
|
git commit -m "m" # Commit
|
||||||
|
git push # Upload
|
||||||
|
git pull # Download
|
||||||
|
git log --oneline # History` }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
154
index.html
Normal file
154
index.html
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Git Guide for Sylvi 🌿</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></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&family=JetBrains+Mono:wght@400;500&family=Sora:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
darkMode: 'class',
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['var(--font-sans)', 'Sora', 'Inter', 'sans-serif'],
|
||||||
|
mono: ['var(--font-mono)', 'JetBrains Mono', 'monospace'],
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
background: 'var(--background)',
|
||||||
|
foreground: 'var(--foreground)',
|
||||||
|
card: 'var(--card)',
|
||||||
|
'card-foreground': 'var(--card-foreground)',
|
||||||
|
popover: 'var(--popover)',
|
||||||
|
'popover-foreground': 'var(--popover-foreground)',
|
||||||
|
primary: {
|
||||||
|
DEFAULT: 'var(--primary)',
|
||||||
|
foreground: 'var(--primary-foreground)',
|
||||||
|
50: '#f0fdf4',
|
||||||
|
100: '#dcfce7',
|
||||||
|
500: 'var(--primary)',
|
||||||
|
600: 'var(--primary)',
|
||||||
|
700: '#15803d',
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: 'var(--secondary)',
|
||||||
|
foreground: 'var(--secondary-foreground)',
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: 'var(--muted)',
|
||||||
|
foreground: 'var(--muted-foreground)',
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: 'var(--accent)',
|
||||||
|
foreground: 'var(--accent-foreground)',
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: 'var(--destructive)',
|
||||||
|
foreground: 'var(--destructive-foreground)',
|
||||||
|
},
|
||||||
|
border: 'var(--border)',
|
||||||
|
input: 'var(--input)',
|
||||||
|
ring: 'var(--ring)',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
/* Custom Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: oklch(0.9818 0.0054 95.0986);
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: oklch(0.8847 0.0069 97.3627);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
overflow-x: hidden;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: oklch(0.9818 0.0054 95.0986);
|
||||||
|
--foreground: oklch(0.3438 0.0269 95.7226);
|
||||||
|
--card: oklch(0.9818 0.0054 95.0986);
|
||||||
|
--card-foreground: oklch(0.2174 0.0019 106.5582);
|
||||||
|
--popover: oklch(1.0000 0 0);
|
||||||
|
--popover-foreground: oklch(0.2671 0.0196 98.9390);
|
||||||
|
--primary: oklch(0.6551 0.0198 142.7714);
|
||||||
|
--primary-foreground: oklch(0.9688 0.0066 106.5223);
|
||||||
|
--secondary: oklch(0.9245 0.0138 92.9892);
|
||||||
|
--secondary-foreground: oklch(0.3821 0.0407 166.3004);
|
||||||
|
--muted: oklch(0.9369 0.0124 91.5218);
|
||||||
|
--muted-foreground: oklch(0.4866 0.0160 145.3235);
|
||||||
|
--accent: oklch(0.8097 0.0177 145.4098);
|
||||||
|
--accent-foreground: oklch(0.2891 0 0);
|
||||||
|
--destructive: oklch(0.6507 0.1836 39.0189);
|
||||||
|
--destructive-foreground: oklch(0.8429 0.1511 88.3005);
|
||||||
|
--border: oklch(0.8847 0.0069 97.3627);
|
||||||
|
--input: oklch(0.7621 0.0156 98.3528);
|
||||||
|
--ring: oklch(0.6507 0.1836 39.0189);
|
||||||
|
--font-sans: 'Sora', 'Inter', sans-serif;
|
||||||
|
--font-mono: 'JetBrains Mono', monospace;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grain-texture {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
|
||||||
|
opacity: 0.05;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: oklch(0.2178 0 0);
|
||||||
|
--foreground: oklch(0.8074 0.0142 93.0137);
|
||||||
|
--card: oklch(0.2679 0.0036 106.6427);
|
||||||
|
--card-foreground: oklch(0.9818 0.0054 95.0986);
|
||||||
|
--popover: oklch(0.3085 0.0035 106.6039);
|
||||||
|
--popover-foreground: oklch(0.9211 0.0040 106.4781);
|
||||||
|
--primary: oklch(0.6551 0.0198 142.7714);
|
||||||
|
--primary-foreground: oklch(0.9688 0.0066 106.5223);
|
||||||
|
--secondary: oklch(0.9245 0.0138 92.9892);
|
||||||
|
--secondary-foreground: oklch(0.3821 0.0407 166.3004);
|
||||||
|
--muted: oklch(0.2561 0.0071 145.3653);
|
||||||
|
--muted-foreground: oklch(0.7713 0.0169 99.0657);
|
||||||
|
--accent: oklch(0.4890 0.0180 145.2933);
|
||||||
|
--accent-foreground: oklch(0.9663 0.0080 98.8792);
|
||||||
|
--destructive: oklch(0.6507 0.1836 39.0189);
|
||||||
|
--destructive-foreground: oklch(1.0000 0 0);
|
||||||
|
--border: oklch(0.2809 0 0);
|
||||||
|
--input: oklch(0.6551 0.0198 142.7714);
|
||||||
|
--ring: oklch(0.6507 0.1836 39.0189);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"react": "https://esm.sh/react@^19.2.3",
|
||||||
|
"react/": "https://esm.sh/react@^19.2.3/",
|
||||||
|
"lucide-react": "https://esm.sh/lucide-react@^0.562.0",
|
||||||
|
"react-dom/": "https://esm.sh/react-dom@^19.2.3/",
|
||||||
|
"gsap": "https://esm.sh/gsap@^3.14.2",
|
||||||
|
"gsap/": "https://esm.sh/gsap@^3.14.2/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="/index.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
15
index.tsx
Normal file
15
index.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
if (!rootElement) {
|
||||||
|
throw new Error("Could not find root element to mount to");
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
5
metadata.json
Normal file
5
metadata.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "Sylvi's Git Guide",
|
||||||
|
"description": "An interactive, animated guide to Git version control, tailored for Sylvi.",
|
||||||
|
"requestFramePermissions": []
|
||||||
|
}
|
||||||
26
package.json
Normal file
26
package.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "sylvi's-git-guide",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"deploy": "bun run build && wrangler pages deploy dist",
|
||||||
|
"pages:dev": "wrangler pages dev dist --port 8788"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.2.3",
|
||||||
|
"lucide-react": "^0.562.0",
|
||||||
|
"react-dom": "^19.2.3",
|
||||||
|
"gsap": "^3.14.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.14.0",
|
||||||
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
"typescript": "~5.8.2",
|
||||||
|
"vite": "^6.2.0",
|
||||||
|
"wrangler": "^4.59.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
public/_headers
Normal file
14
public/_headers
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
X-Frame-Options: DENY
|
||||||
|
X-Content-Type-Options: nosniff
|
||||||
|
Referrer-Policy: strict-origin-when-cross-origin
|
||||||
|
Permissions-Policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()
|
||||||
|
|
||||||
|
/*.js
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.css
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/index.html
|
||||||
|
Cache-Control: public, max-age=0, must-revalidate
|
||||||
29
tsconfig.json
Normal file
29
tsconfig.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": [
|
||||||
|
"ES2022",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"allowJs": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
29
types.ts
Normal file
29
types.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
export type ContentType =
|
||||||
|
| 'paragraph'
|
||||||
|
| 'code'
|
||||||
|
| 'callout'
|
||||||
|
| 'list'
|
||||||
|
| 'ordered-list'
|
||||||
|
| 'subheading'
|
||||||
|
| 'collapsible'
|
||||||
|
| 'diagram';
|
||||||
|
|
||||||
|
export interface ContentBlock {
|
||||||
|
type: ContentType;
|
||||||
|
content: string | string[] | { title: string; body: ContentBlock[] };
|
||||||
|
variant?: 'warning' | 'info' | 'success' | 'tip';
|
||||||
|
language?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SectionData {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
blocks: ContentBlock[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavItem {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
23
vite.config.ts
Normal file
23
vite.config.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, '.', '');
|
||||||
|
return {
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
},
|
||||||
|
plugins: [react()],
|
||||||
|
define: {
|
||||||
|
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
||||||
|
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, '.'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
6
wrangler.toml
Normal file
6
wrangler.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
name = "sylvi-git-guide"
|
||||||
|
compatibility_date = "2025-01-14"
|
||||||
|
main = "workers-site/index.js"
|
||||||
|
|
||||||
|
[site]
|
||||||
|
bucket = "./dist"
|
||||||
Loading…
x
Reference in New Issue
Block a user