Yes
This commit is contained in:
parent
3f9fb28c6b
commit
f9a669ae69
24
00-Lesson-Site/.gitignore
vendored
Normal file
24
00-Lesson-Site/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# build output
|
||||||
|
dist/
|
||||||
|
# generated types
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# macOS-specific files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# jetbrains setting folder
|
||||||
|
.idea/
|
||||||
43
00-Lesson-Site/README.md
Normal file
43
00-Lesson-Site/README.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Astro Starter Kit: Minimal
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm create astro@latest -- --template minimal
|
||||||
|
```
|
||||||
|
|
||||||
|
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||||
|
|
||||||
|
## 🚀 Project Structure
|
||||||
|
|
||||||
|
Inside of your Astro project, you'll see the following folders and files:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/
|
||||||
|
├── public/
|
||||||
|
├── src/
|
||||||
|
│ └── pages/
|
||||||
|
│ └── index.astro
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||||
|
|
||||||
|
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||||
|
|
||||||
|
Any static assets, like images, can be placed in the `public/` directory.
|
||||||
|
|
||||||
|
## 🧞 Commands
|
||||||
|
|
||||||
|
All commands are run from the root of the project, from a terminal:
|
||||||
|
|
||||||
|
| Command | Action |
|
||||||
|
| :------------------------ | :----------------------------------------------- |
|
||||||
|
| `npm install` | Installs dependencies |
|
||||||
|
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||||
|
| `npm run build` | Build your production site to `./dist/` |
|
||||||
|
| `npm run preview` | Preview your build locally, before deploying |
|
||||||
|
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||||
|
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||||
|
|
||||||
|
## 👀 Want to learn more?
|
||||||
|
|
||||||
|
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||||
26
00-Lesson-Site/astro.config.mjs
Normal file
26
00-Lesson-Site/astro.config.mjs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// @ts-check
|
||||||
|
import mdx from "@astrojs/mdx";
|
||||||
|
import solidJs from "@astrojs/solid-js";
|
||||||
|
import { defineConfig } from "astro/config";
|
||||||
|
|
||||||
|
import expressiveCode from "astro-expressive-code";
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [
|
||||||
|
solidJs(),
|
||||||
|
expressiveCode({
|
||||||
|
themes: ["vitesse-dark"],
|
||||||
|
}),
|
||||||
|
mdx(),
|
||||||
|
],
|
||||||
|
vite: {
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
additionalData: `@use "/src/styles/global_vars" as *; \n @use "/src/styles/reset" as *; \n @use "/src/styles/global_fonts" as *; \n`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
6472
00-Lesson-Site/package-lock.json
generated
Normal file
6472
00-Lesson-Site/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
00-Lesson-Site/package.json
Normal file
21
00-Lesson-Site/package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "web-dev",
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/mdx": "^4.3.12",
|
||||||
|
"@astrojs/solid-js": "^5.1.3",
|
||||||
|
"astro": "^5.16.4",
|
||||||
|
"astro-expressive-code": "^0.41.3",
|
||||||
|
"solid-js": "^1.9.10"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"sass": "^1.94.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
4577
00-Lesson-Site/pnpm-lock.yaml
generated
Normal file
4577
00-Lesson-Site/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
00-Lesson-Site/public/fonts/Geist.woff2
Normal file
BIN
00-Lesson-Site/public/fonts/Geist.woff2
Normal file
Binary file not shown.
BIN
00-Lesson-Site/public/fonts/GeistMono.woff2
Normal file
BIN
00-Lesson-Site/public/fonts/GeistMono.woff2
Normal file
Binary file not shown.
49
00-Lesson-Site/src/components/Navbar/DarkModeToggle.astro
Normal file
49
00-Lesson-Site/src/components/Navbar/DarkModeToggle.astro
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
// Path: src/components/Navbar/DarkModeToggle.astro
|
||||||
|
import Sun from "../SVGs/Sun.astro";
|
||||||
|
import Moon from "../SVGs/Moon.astro";
|
||||||
|
import styles from "./DarkModeToggle.module.scss";
|
||||||
|
---
|
||||||
|
|
||||||
|
<button id="theme-toggle" aria-label="Toggle Dark Mode" class={styles["toggle-btn"]}>
|
||||||
|
<div class={styles["icon-container"]}>
|
||||||
|
<span class={`${styles["color-icon"]} ${styles["moon-wrapper"]}`}>
|
||||||
|
<Moon />
|
||||||
|
</span>
|
||||||
|
<span class={`${styles["color-icon"]} ${styles["sun-wrapper"]}`}>
|
||||||
|
<Sun />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 1. Define the logic OUTSIDE the event listener.
|
||||||
|
// This creates a stable reference that doesn't change on navigation.
|
||||||
|
const handleToggleClick = () => {
|
||||||
|
const root = document.documentElement;
|
||||||
|
const isDark = root.classList.contains("dark");
|
||||||
|
const targetTheme = isDark ? "light" : "dark";
|
||||||
|
|
||||||
|
if (targetTheme === "dark") {
|
||||||
|
root.classList.add("dark");
|
||||||
|
root.setAttribute("data-theme", "dark");
|
||||||
|
} else {
|
||||||
|
root.classList.remove("dark");
|
||||||
|
root.setAttribute("data-theme", "light");
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("theme", targetTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("astro:page-load", () => {
|
||||||
|
const toggleBtn = document.getElementById("theme-toggle");
|
||||||
|
|
||||||
|
if (toggleBtn) {
|
||||||
|
// 2. Now this actually works!
|
||||||
|
// Since 'handleToggleClick' is the exact same function from step 1,
|
||||||
|
// the browser successfully removes the old one before adding the new one.
|
||||||
|
toggleBtn.removeEventListener("click", handleToggleClick);
|
||||||
|
toggleBtn.addEventListener("click", handleToggleClick);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
/* Path: src/components/Navbar/DarkModeToggle.module.scss */
|
||||||
|
|
||||||
|
.toggle-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 3.5rem;
|
||||||
|
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
transition: background-color 500ms;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(128, 128, 128, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
position: relative;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-icon {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition:
|
||||||
|
transform 1000ms ease,
|
||||||
|
opacity 500ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moon-wrapper {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
:global([data-theme="dark"]) & {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sun-wrapper {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
:global([data-theme="dark"]) & {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
00-Lesson-Site/src/components/Navbar/Navbar.astro
Normal file
44
00-Lesson-Site/src/components/Navbar/Navbar.astro
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
// Path: src/components/Navbar/Navbar.astro
|
||||||
|
|
||||||
|
import "../../styles/main.scss";
|
||||||
|
import styles from "./Navbar.module.scss";
|
||||||
|
|
||||||
|
import DarkModeToggle from "./DarkModeToggle.astro";
|
||||||
|
import UserIcon from "./UserIcon.astro";
|
||||||
|
|
||||||
|
const pathname = new URL(Astro.request.url).pathname;
|
||||||
|
const isActive = pathname === "/" || pathname === "";
|
||||||
|
---
|
||||||
|
|
||||||
|
<nav class={styles.navbar}>
|
||||||
|
<a href="/" class:list={[styles["nav-logo"], { [styles["active"]]: isActive }]}> LeafPig </a>
|
||||||
|
|
||||||
|
<ul class={styles["nav-links"]}>
|
||||||
|
<li>
|
||||||
|
<a href="/lessons" class:list={[{ [styles["active"]]: pathname.startsWith("/lessons") }]}>
|
||||||
|
Lessons
|
||||||
|
{pathname.startsWith("/lessons") && <span class={styles["magic-line"]} transition:name="nav-underline" />}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="/changelog" class:list={[{ [styles["active"]]: pathname.startsWith("/changelog") }]}>
|
||||||
|
Changelog
|
||||||
|
{pathname.startsWith("/changelog") && <span class={styles["magic-line"]} transition:name="nav-underline" />}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="/resources" class:list={[{ [styles["active"]]: pathname.startsWith("/resources") }]}>
|
||||||
|
Resources
|
||||||
|
{pathname.startsWith("/resources") && <span class={styles["magic-line"]} transition:name="nav-underline" />}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class={styles["nav-right"]} transition:persist="nav-tools">
|
||||||
|
<DarkModeToggle />
|
||||||
|
<UserIcon />
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
97
00-Lesson-Site/src/components/Navbar/Navbar.module.scss
Normal file
97
00-Lesson-Site/src/components/Navbar/Navbar.module.scss
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/* Path: src/components/Navbar/Navbar.module.scss */
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
width: 100%;
|
||||||
|
height: 6rem;
|
||||||
|
padding: 0.75rem 2rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-logo {
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
transition:
|
||||||
|
transform 1000ms ease,
|
||||||
|
opacity 500ms;
|
||||||
|
|
||||||
|
color: color-adjust(text, 0, 0);
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
height: 2px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: color-adjust(text, 0, 0);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: -4px;
|
||||||
|
transform-origin: left;
|
||||||
|
|
||||||
|
transform: scaleX(0);
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover::after,
|
||||||
|
&:focus::after,
|
||||||
|
&.active::after {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: auto;
|
||||||
|
margin-right: 2rem;
|
||||||
|
justify-items: end;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
margin-left: 2rem;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: color-adjust(text, 0, 0);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
transition: color 300ms ease-in-out;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: color-adjust(secondary, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.magic-line {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: -4px;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background-color: color-adjust(secondary, 0, 0);
|
||||||
|
z-index: 10;
|
||||||
|
contain: layout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
16
00-Lesson-Site/src/components/Navbar/UserIcon.astro
Normal file
16
00-Lesson-Site/src/components/Navbar/UserIcon.astro
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
// Path: src/components/Navbar/UserIcon.astro
|
||||||
|
|
||||||
|
import ProfileSpinner from "../SVGs/ProfileSpinner.astro";
|
||||||
|
import UserSVG from "../SVGs/UserSVG.astro";
|
||||||
|
import styles from "./UserIcon.module.scss";
|
||||||
|
---
|
||||||
|
|
||||||
|
<a class={styles["user-icon"]}>
|
||||||
|
<div class={styles["spin-container"]}>
|
||||||
|
<div class={styles["spin-animation"]}>
|
||||||
|
<ProfileSpinner />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UserSVG />
|
||||||
|
</a>
|
||||||
65
00-Lesson-Site/src/components/Navbar/UserIcon.module.scss
Normal file
65
00-Lesson-Site/src/components/Navbar/UserIcon.module.scss
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/* Path: src/components/Navbar/UserIcon.module.scss */
|
||||||
|
|
||||||
|
.user-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
// 1. Fix alignment (Flexbox uses justify-content)
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
// 2. Create a positioning context for the absolute child
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
margin-left: 1rem;
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 3.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
// Optional: Reset link styles
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Trigger the animation when the USER hovers the main button
|
||||||
|
&:hover .spin-container {
|
||||||
|
animation-play-state: running;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spin-container {
|
||||||
|
position: absolute;
|
||||||
|
// 4. Force the container to fill the parent exactly
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
// Animation 1: Rotate CCW fast
|
||||||
|
animation: spin 1.5s ease-in-out infinite reverse;
|
||||||
|
animation-play-state: paused;
|
||||||
|
|
||||||
|
// Allow clicks to pass through to the link/button underneath
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spin-animation {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
animation: spin 15s linear infinite normal;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
65
00-Lesson-Site/src/components/Post/FloatingTOC.astro
Normal file
65
00-Lesson-Site/src/components/Post/FloatingTOC.astro
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
// Path: src/components/Post/FloatingTOC.astro
|
||||||
|
import styles from "./FloatingTOC.module.scss";
|
||||||
|
---
|
||||||
|
|
||||||
|
<nav class={styles.toc} aria-label="Table of Contents">
|
||||||
|
<ul id="toc-list"></ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function generateTOC() {
|
||||||
|
const content = document.getElementById("lesson-container");
|
||||||
|
const list = document.getElementById("toc-list");
|
||||||
|
|
||||||
|
if (list) list.innerHTML = "";
|
||||||
|
if (!content || !list) return;
|
||||||
|
|
||||||
|
const targets = content.querySelectorAll("[data-toc]");
|
||||||
|
|
||||||
|
targets.forEach((el, index) => {
|
||||||
|
if (!el.id) el.id = `toc-item-${index}`;
|
||||||
|
|
||||||
|
const label = el.getAttribute("data-toc");
|
||||||
|
const level = el.getAttribute("data-toc-level") || "1";
|
||||||
|
|
||||||
|
const li = document.createElement("li");
|
||||||
|
const a = document.createElement("a");
|
||||||
|
|
||||||
|
// Add data attribute for CSS to target specific lengths
|
||||||
|
li.setAttribute("data-level", level);
|
||||||
|
|
||||||
|
a.href = `#${el.id}`;
|
||||||
|
// No Icon span, just the text which we will hide via CSS
|
||||||
|
a.innerHTML = `<span class="toc-text">${label}</span>`;
|
||||||
|
|
||||||
|
a.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
});
|
||||||
|
|
||||||
|
li.appendChild(a);
|
||||||
|
list.appendChild(li);
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
// Clear all active classes
|
||||||
|
document.querySelectorAll("#toc-list a").forEach((link) => link.classList.remove("active"));
|
||||||
|
// Set current active
|
||||||
|
a.classList.add("active");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// Tweak: Use a 1px line exactly in the vertical center of the screen
|
||||||
|
{ rootMargin: "-50% 0px -50% 0px", threshold: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
observer.observe(el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
generateTOC();
|
||||||
|
document.addEventListener("astro:after-swap", generateTOC);
|
||||||
|
</script>
|
||||||
142
00-Lesson-Site/src/components/Post/FloatingTOC.module.scss
Normal file
142
00-Lesson-Site/src/components/Post/FloatingTOC.module.scss
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
/* Path: src/components/Post/FloatingTOC.module.scss */
|
||||||
|
|
||||||
|
.toc {
|
||||||
|
// 1. Container Layout
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
right: 20px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
// 2. THE FIX: Make container wide enough to hold the text
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
// 3. THE FIX: Allow clicks to pass through the empty area
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
// Keep scrolling functionality
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nesting protections
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end; // Keep lines on the right
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%; // Ensure list item spans width
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 5px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
// 4. THE FIX: Re-enable clicks on the actual links
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
// --- THE LINE ---
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
height: 3px;
|
||||||
|
background-color: #ffb8b8;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- THE TEXT ---
|
||||||
|
:global(.toc-text) {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
// 5. THE FIX: Position relative to the line inside the new wider box
|
||||||
|
// Since the box is 300px wide, we don't need 'right: 150%'.
|
||||||
|
// We just place it to the left of the line.
|
||||||
|
right: 60px;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: color-adjust(text, 0, 0);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(10px);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
background: color-adjust(bg, 0, 0.8);
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- HOVER STATE ---
|
||||||
|
&:hover {
|
||||||
|
:global(.toc-text) {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
background-color: color-adjust(text, 0, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ACTIVE STATE ---
|
||||||
|
&:global(.active) {
|
||||||
|
&::after {
|
||||||
|
background-color: color-adjust(secondary, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- STAGGERED LENGTHS ---
|
||||||
|
li[data-level="1"] {
|
||||||
|
a::after {
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
a:global(.active)::after {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li[data-level="2"] {
|
||||||
|
a::after {
|
||||||
|
width: 25px;
|
||||||
|
}
|
||||||
|
a:global(.active)::after {
|
||||||
|
width: 35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li[data-level="3"] {
|
||||||
|
a::after {
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
|
a:global(.active)::after {
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
00-Lesson-Site/src/components/Post/Spoiler.module.scss
Normal file
14
00-Lesson-Site/src/components/Post/Spoiler.module.scss
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* Path: src/components/Post/Spoiler.module.scss */
|
||||||
|
|
||||||
|
.spoiler {
|
||||||
|
border-left: 4px solid color-adjust(secondary, 0, 0);
|
||||||
|
padding: 1rem;
|
||||||
|
background: rgba(128, 128, 128, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spoilerContent {
|
||||||
|
margin-top: 1rem;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
28
00-Lesson-Site/src/components/Post/Spoiler.tsx
Normal file
28
00-Lesson-Site/src/components/Post/Spoiler.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Path: src/components/Post/Spoiler.tsx
|
||||||
|
import type { Component, JSX } from "solid-js";
|
||||||
|
import { createSignal } from "solid-js";
|
||||||
|
import styles from "./Spoiler.module.scss";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title?: string;
|
||||||
|
buttonText?: string;
|
||||||
|
children: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Spoiler: Component<Props> = (props) => {
|
||||||
|
const [visible, setVisible] = createSignal(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<blockquote class={styles.spoiler}>
|
||||||
|
{props.title ? <strong class={styles.spoilerTitle}>{props.title}</strong> : <></>}
|
||||||
|
<button onClick={() => setVisible(!visible())}>
|
||||||
|
{visible() ? "Hide" : "Show"}
|
||||||
|
{props.buttonText ? `${props.buttonText}` : " Answer"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{visible() && <div class={styles.spoilerContent}>{props.children}</div>}
|
||||||
|
</blockquote>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Spoiler;
|
||||||
19
00-Lesson-Site/src/components/SVGs/Moon.astro
Normal file
19
00-Lesson-Site/src/components/SVGs/Moon.astro
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
// Path: src/components/SVGs/Moon.astro
|
||||||
|
---
|
||||||
|
|
||||||
|
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" class="svg-icon" stroke-width={1}>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M14.228 7.9439C10.5176 8.82869 7.75757 12.1054 7.75757 15.9987C7.75757 20.5716 11.5618 24.2919 16.2367 24.2919C19.2323 24.2919 21.9337 22.7699 23.4514 20.3585C23.2779 20.3676 23.1033 20.3722 22.9287 20.3722C17.7826 20.3722 13.5951 16.2772 13.5951 11.2435C13.5951 10.1032 13.8108 8.98914 14.228 7.9439M16.2367 26.4993C10.3171 26.4993 5.50037 21.7899 5.50037 15.9987C5.50037 10.2109 10.3171 5.49927 16.2367 5.49927C16.6598 5.49927 17.0501 5.72963 17.2435 6.09753C17.438 6.46428 17.4087 6.90668 17.1638 7.24363C16.3059 8.42297 15.8535 9.80631 15.8535 11.2435C15.8535 15.06 19.0272 18.1637 22.9287 18.1637C23.6483 18.1637 24.3573 18.0582 25.0359 17.8531C25.4378 17.7293 25.8785 17.8359 26.1738 18.1304C26.4715 18.425 26.5758 18.8559 26.4446 19.2467C25.0019 23.5847 20.9 26.4993 16.2367 26.4993"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.svg-icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
9
00-Lesson-Site/src/components/SVGs/ProfileSpinner.astro
Normal file
9
00-Lesson-Site/src/components/SVGs/ProfileSpinner.astro
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
// Path: src/components/SVGs/ProfileSpinner.astro
|
||||||
|
---
|
||||||
|
|
||||||
|
<svg height={56} width={56} viewBox="0 0 56 56">
|
||||||
|
<path d="M29.465,0.038373A28,28,0,0,1,52.948,40.712L51.166,39.804A26,26,0,0,0,29.361,2.0356Z" style="color: oklch(62.3% 0.214 259.815);" fill="currentColor"></path>
|
||||||
|
<path d="M51.483,43.250A28,28,0,0,1,4.5172,43.250L6.1946,42.161A26,26,0,0,0,49.805,42.161Z" style="color: oklch(79.5% 0.184 86.047);" fill="currentColor"></path>
|
||||||
|
<path d="M3.0518,40.712A28,28,0,0,1,26.535,0.038373L26.639,2.0356A26,26,0,0,0,4.8338,39.804Z" style="color: #D14242;" fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
58
00-Lesson-Site/src/components/SVGs/Sun.astro
Normal file
58
00-Lesson-Site/src/components/SVGs/Sun.astro
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
// Path: src/components/SVGs/Sun.astro
|
||||||
|
---
|
||||||
|
|
||||||
|
<svg class="w-full" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" class="svg-icon" stroke-width={1}>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M16.0003 21.4194C13.0123 21.4194 10.5813 18.9874 10.5813 15.9994C10.5813 13.0114 13.0123 10.5804 16.0003 10.5804C18.9883 10.5804 21.4193 13.0114 21.4193 15.9994C21.4193 18.9874 18.9883 21.4194 16.0003 21.4194M16.0003 8.64136C11.9423 8.64136 8.64233 11.9414 8.64233 15.9994C8.64233 20.0574 11.9423 23.3574 16.0003 23.3574C20.0573 23.3574 23.3583 20.0574 23.3583 15.9994C23.3583 11.9414 20.0573 8.64136 16.0003 8.64136"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M6.11559 15.0298H3.34559C2.81059 15.0298 2.37659 15.4648 2.37659 15.9998C2.37659 16.5348 2.81059 16.9688 3.34559 16.9688H6.11559C6.65159 16.9688 7.08459 16.5348 7.08459 15.9998C7.08459 15.4648 6.65159 15.0298 6.11559 15.0298"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M16.0004 7.08447C16.5364 7.08447 16.9704 6.64946 16.9704 6.11446V3.34546C16.9704 2.81046 16.5364 2.37646 16.0004 2.37646C15.4644 2.37646 15.0304 2.81046 15.0304 3.34546V6.11446C15.0304 6.64946 15.4644 7.08447 16.0004 7.08447"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M16.0004 24.9146C15.4644 24.9146 15.0304 25.3496 15.0304 25.8846V28.6536C15.0304 29.1886 15.4644 29.6236 16.0004 29.6236C16.5364 29.6236 16.9704 29.1886 16.9704 28.6536V25.8846C16.9704 25.3496 16.5364 24.9146 16.0004 24.9146"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M28.6542 15.0298H25.8842C25.3492 15.0298 24.9152 15.4648 24.9152 15.9998C24.9152 16.5348 25.3492 16.9688 25.8842 16.9688H28.6542C29.1902 16.9688 29.6242 16.5348 29.6242 15.9998C29.6242 15.4648 29.1902 15.0298 28.6542 15.0298"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M22.9896 9.97995C23.2376 9.97995 23.4856 9.88495 23.6756 9.69595L24.7036 8.66795C25.0816 8.28995 25.0816 7.67495 24.7036 7.29595C24.3246 6.91795 23.7106 6.91795 23.3316 7.29595L22.3036 8.32495C21.9256 8.70295 21.9256 9.31695 22.3036 9.69595C22.4926 9.88495 22.7416 9.97995 22.9896 9.97995"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M8.32507 9.69593C8.51407 9.88493 8.76207 9.97993 9.01107 9.97993C9.25907 9.97993 9.50707 9.88493 9.69607 9.69593C10.0751 9.31693 10.0751 8.70293 9.69607 8.32493L8.66807 7.29693C8.28907 6.91893 7.67507 6.91893 7.29707 7.29693C6.91807 7.67493 6.91807 8.28993 7.29707 8.66793L8.32507 9.69593Z"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M8.32507 22.3043L7.29707 23.3313C6.91807 23.7093 6.91807 24.3243 7.29707 24.7023C7.48607 24.8923 7.73407 24.9873 7.98207 24.9873C8.23007 24.9873 8.47807 24.8923 8.66807 24.7023L9.69607 23.6753C10.0751 23.2973 10.0751 22.6833 9.69607 22.3043C9.31807 21.9253 8.70307 21.9253 8.32507 22.3043"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M23.6752 22.3043C23.2962 21.9253 22.6822 21.9253 22.3032 22.3043C21.9252 22.6833 21.9252 23.2973 22.3042 23.6753L23.3322 24.7023C23.5212 24.8923 23.7692 24.9873 24.0182 24.9873C24.2662 24.9873 24.5142 24.8923 24.7032 24.7023C25.0822 24.3243 25.0822 23.7093 24.7032 23.3313L23.6752 22.3043Z"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.svg-icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
00-Lesson-Site/src/components/SVGs/UserSVG.astro
Normal file
7
00-Lesson-Site/src/components/SVGs/UserSVG.astro
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
// Path: src/components/SVGs/UserSVG.astro
|
||||||
|
---
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="width: 2rem; height: 2rem" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width={1.5}>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
||||||
|
</svg>
|
||||||
61
00-Lesson-Site/src/content/lessons/01-intro.mdx
Normal file
61
00-Lesson-Site/src/content/lessons/01-intro.mdx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
# Path: src/content/lessons/01-intro.mdx
|
||||||
|
|
||||||
|
title: "Introduction to Web Dev"
|
||||||
|
description: "Setting up the environment"
|
||||||
|
style: "type-1"
|
||||||
|
---
|
||||||
|
|
||||||
|
import Spoiler from "../../components/Post/Spoiler.tsx";
|
||||||
|
|
||||||
|
# Hosting a Large Language Model (LLM) Locally
|
||||||
|
|
||||||
|
<picture>
|
||||||
|
<img src="https://pic.mangopig.tech/i/879aaccd-6822-423f-883a-74cf5ba598e7.jpg" alt="Web Development Illustration" />
|
||||||
|
</picture>
|
||||||
|
|
||||||
|
<blockquote class="lesson-meta">
|
||||||
|
<span>Lesson 01</span>
|
||||||
|
<span>Created at: **December 2025**</span>
|
||||||
|
<span>Last Updated: **December 2025**</span>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<blockquote class="objectives" data-toc="Lesson Objectives">
|
||||||
|
|
||||||
|
## Lesson Objectives
|
||||||
|
|
||||||
|
- Setting up your Developer Environment
|
||||||
|
- Setting up a isolated Docker environment for hosting LLMs
|
||||||
|
- Introduction to basic Python environment setup
|
||||||
|
- Hosting a basic LLM model with Llama.cpp locally
|
||||||
|
|
||||||
|
<picture>
|
||||||
|
<img src="https://pic.mangopig.tech/i/4c4d1b5f-b9ce-4952-a1b4-991b19c0adb5.png" alt="MangoPig Ganbattte" />
|
||||||
|
</picture>
|
||||||
|
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<section data-toc="Setting Up Developer Environment">
|
||||||
|
<h2 data-toc="WSL" data-toc-level="2">Setting Up WSL (Windows Subsystem for Linux)</h2>
|
||||||
|
To set up WSL on your Windows machine, follow these steps:
|
||||||
|
1. Open PowerShell as Administrator.
|
||||||
|
2. Run the following command to enable WSL and install a Linux distribution (Ubuntu is recommended):
|
||||||
|
|
||||||
|
```zsh frame="none"
|
||||||
|
wsl --install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Restart your computer when prompted.
|
||||||
|
4. After restarting, open the Ubuntu application from the Start menu and complete the initial setup by creating a user account.
|
||||||
|
5. Update your package lists and upgrade installed packages by running:
|
||||||
|
|
||||||
|
```zsh frame="none"
|
||||||
|
sudo apt update && sudo apt upgrade -y
|
||||||
|
```
|
||||||
|
|
||||||
|
<h2 data-toc="Getting Your Environment Ready" data-toc-level="2">Getting Your Environment Ready</h2>
|
||||||
|
|
||||||
|
```wsl frame="none"
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
15
00-Lesson-Site/src/content/lessons/config.ts
Normal file
15
00-Lesson-Site/src/content/lessons/config.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// Path: 00-Lesson-Site/src/content/lessons/config.ts
|
||||||
|
|
||||||
|
import { defineCollection, z } from "astro:content";
|
||||||
|
|
||||||
|
const lessonsCollection = defineCollection({
|
||||||
|
type: "content",
|
||||||
|
schema: z.object({
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const collections = {
|
||||||
|
lessons: lessonsCollection,
|
||||||
|
};
|
||||||
42
00-Lesson-Site/src/helpers/colorMode.astro
Normal file
42
00-Lesson-Site/src/helpers/colorMode.astro
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
// Path: src/helpers/colorMode.astro
|
||||||
|
---
|
||||||
|
|
||||||
|
<script is:inline>
|
||||||
|
// 1. Basic function to set theme on load
|
||||||
|
function getTheme() {
|
||||||
|
const stored = localStorage.getItem("theme");
|
||||||
|
if (stored) return stored;
|
||||||
|
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTheme(theme) {
|
||||||
|
if (theme === "dark") {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
document.documentElement.setAttribute("data-theme", "dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
document.documentElement.setAttribute("data-theme", "light");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run on initial load
|
||||||
|
setTheme(getTheme());
|
||||||
|
|
||||||
|
// 2. THE FIX: Intercept the new page BEFORE the swap
|
||||||
|
document.addEventListener("astro:before-swap", (event) => {
|
||||||
|
// Read the current class from the OLD document (the one you are looking at)
|
||||||
|
const isDark = document.documentElement.classList.contains("dark");
|
||||||
|
|
||||||
|
// Apply it to the NEW document (the one about to slide in)
|
||||||
|
// This ensures the "snapshot" of the new page is already dark.
|
||||||
|
if (isDark) {
|
||||||
|
event.newDocument.documentElement.classList.add("dark");
|
||||||
|
event.newDocument.documentElement.setAttribute("data-theme", "dark");
|
||||||
|
} else {
|
||||||
|
// Explicitly remove it for light mode to be safe
|
||||||
|
event.newDocument.documentElement.classList.remove("dark");
|
||||||
|
event.newDocument.documentElement.setAttribute("data-theme", "light");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
36
00-Lesson-Site/src/layouts/LandingLayout.astro
Normal file
36
00-Lesson-Site/src/layouts/LandingLayout.astro
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
// Path: src/layouts/LandingLayout.astro
|
||||||
|
|
||||||
|
import { ClientRouter } from "astro:transitions";
|
||||||
|
|
||||||
|
import Navbar from "../components/Navbar/Navbar.astro";
|
||||||
|
import ThemeScript from "../helpers/colorMode.astro";
|
||||||
|
import "../styles/main.scss";
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
<ClientRouter />
|
||||||
|
|
||||||
|
<!-- Color Mode Helper -->
|
||||||
|
<ThemeScript />
|
||||||
|
|
||||||
|
<!-- Favicon Icons -->
|
||||||
|
<link rel="icon" href="favicon/favicon.ico" />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon/favicon-32x32.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="favicon/favicon-16x16.png" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png" />
|
||||||
|
<link rel="manifest" href="favicon/site.webmanifest" />
|
||||||
|
|
||||||
|
<title>Web Dev Lesson Notes</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Navbar />
|
||||||
|
<main>
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
46
00-Lesson-Site/src/layouts/LessonLayout.astro
Normal file
46
00-Lesson-Site/src/layouts/LessonLayout.astro
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
// Path: src/layouts/LessonLayout.astro
|
||||||
|
|
||||||
|
import { ClientRouter } from "astro:transitions";
|
||||||
|
|
||||||
|
import Navbar from "../components/Navbar/Navbar.astro";
|
||||||
|
import FloatingTOC from "../components/Post/FloatingTOC.astro";
|
||||||
|
import ThemeScript from "../helpers/colorMode.astro";
|
||||||
|
import "../styles/main.scss";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
pageTitle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pageTitle = "Web Dev Lessons" } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
<ClientRouter />
|
||||||
|
|
||||||
|
<!-- Color Mode Helper -->
|
||||||
|
<ThemeScript />
|
||||||
|
|
||||||
|
<!-- Favicon Icons -->
|
||||||
|
<link rel="icon" href="favicon/favicon.ico" />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon/favicon-32x32.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="favicon/favicon-16x16.png" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png" />
|
||||||
|
<link rel="manifest" href="favicon/site.webmanifest" />
|
||||||
|
|
||||||
|
<title>{pageTitle}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<FloatingTOC />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
00-Lesson-Site/src/pages/changelog.astro
Normal file
12
00-Lesson-Site/src/pages/changelog.astro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
// Path: src/pages/changelog.astro
|
||||||
|
|
||||||
|
import Layout from "../layouts/LessonLayout.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<h1>Welcome to the Lesson Site</h1>
|
||||||
|
<p>This is the homepage of the lesson site built with Astro.</p>
|
||||||
|
|
||||||
|
<a href="/lessons/01-intro">Lesson 1</a>
|
||||||
|
</Layout>
|
||||||
12
00-Lesson-Site/src/pages/index.astro
Normal file
12
00-Lesson-Site/src/pages/index.astro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
// Path: 00-Lesson-Site/src/pages/index.astro
|
||||||
|
|
||||||
|
import Layout from "../layouts/LessonLayout.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<h1>Welcome to the Lesson Site</h1>
|
||||||
|
<p>This is the homepage of the lesson site built with Astro.</p>
|
||||||
|
|
||||||
|
<a href="/lessons/01-intro">Lesson 1</a>
|
||||||
|
</Layout>
|
||||||
35
00-Lesson-Site/src/pages/lessons/[slug].astro
Normal file
35
00-Lesson-Site/src/pages/lessons/[slug].astro
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
// Path: src/pages/lessons/[slug].astro
|
||||||
|
|
||||||
|
import { getCollection, type CollectionEntry } from "astro:content";
|
||||||
|
import LessonLayout from "../../layouts/LessonLayout.astro";
|
||||||
|
|
||||||
|
import styles from "./lessonPage.module.scss";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
entry: CollectionEntry<"lessons">;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const lessonEntries = await getCollection("lessons");
|
||||||
|
|
||||||
|
return lessonEntries.map((entry: CollectionEntry<"lessons">) => ({
|
||||||
|
params: { slug: entry.slug },
|
||||||
|
props: { entry },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { entry } = Astro.props;
|
||||||
|
const { Content } = await entry.render();
|
||||||
|
|
||||||
|
// Dynamically Import Lesson Style from the entry's frontmatter
|
||||||
|
if (entry.data.style) {
|
||||||
|
await import(`../../styles/lessons/${entry.data.style}.scss`);
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<LessonLayout pageTitle={entry.data.title}>
|
||||||
|
<div class:list={[styles.content]} id="lesson-container">
|
||||||
|
<Content />
|
||||||
|
</div>
|
||||||
|
</LessonLayout>
|
||||||
12
00-Lesson-Site/src/pages/lessons/index.astro
Normal file
12
00-Lesson-Site/src/pages/lessons/index.astro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
// Path: src/pages/lessons/index.astro
|
||||||
|
|
||||||
|
import Layout from "../../layouts/LessonLayout.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<h1>Welcome to the Lesson Site</h1>
|
||||||
|
<p>This is the homepage of the lesson site built with Astro.</p>
|
||||||
|
|
||||||
|
<a href="/lessons/01-intro">Lesson 1</a>
|
||||||
|
</Layout>
|
||||||
11
00-Lesson-Site/src/pages/lessons/lessonPage.module.scss
Normal file
11
00-Lesson-Site/src/pages/lessons/lessonPage.module.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/* Path: src/pages/lessons/lessonPage.module.scss */
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
12
00-Lesson-Site/src/pages/resources.astro
Normal file
12
00-Lesson-Site/src/pages/resources.astro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
// Path: src/pages/resources.astro
|
||||||
|
|
||||||
|
import Layout from "../layouts/LessonLayout.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<h1>Welcome to the Lesson Site</h1>
|
||||||
|
<p>This is the homepage of the lesson site built with Astro.</p>
|
||||||
|
|
||||||
|
<a href="/lessons/01-intro">Lesson 1</a>
|
||||||
|
</Layout>
|
||||||
13
00-Lesson-Site/src/styles/_global_fonts.scss
Normal file
13
00-Lesson-Site/src/styles/_global_fonts.scss
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/* Path: src/styles/_fonts.scss */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Geist";
|
||||||
|
src: url("/fonts/Geist.woff2") format("woff2");
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "GeistMono";
|
||||||
|
src: url("/fonts/GeistMono.woff2") format("woff2");
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
11
00-Lesson-Site/src/styles/_global_vars.scss
Normal file
11
00-Lesson-Site/src/styles/_global_vars.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/* Path: src/styles/_global_vars.scss */
|
||||||
|
|
||||||
|
@use "sass:meta";
|
||||||
|
|
||||||
|
@function color-adjust($color, $contrast-boost: 0, $c-offset: 0) {
|
||||||
|
$L: calc(var(--#{meta.inspect($color)}-base-l) - (#{$contrast-boost} * var(--theme-polarity)));
|
||||||
|
$C: calc(var(--#{meta.inspect($color)}-base-c) + #{$c-offset});
|
||||||
|
$H: var(--#{meta.inspect($color)}-base-h);
|
||||||
|
|
||||||
|
@return oklch(#{$L} #{$C} #{$H});
|
||||||
|
}
|
||||||
52
00-Lesson-Site/src/styles/_reset.scss
Normal file
52
00-Lesson-Site/src/styles/_reset.scss
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/* Path: src/styles/_reset.scss */
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
line-height: 1.5;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
picture,
|
||||||
|
video,
|
||||||
|
canvas,
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root,
|
||||||
|
#__next {
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
98
00-Lesson-Site/src/styles/lessons/type-1.scss
Normal file
98
00-Lesson-Site/src/styles/lessons/type-1.scss
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/* Path: src/styles/lessons/type-1.scss */
|
||||||
|
|
||||||
|
.lesson-meta {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #888;
|
||||||
|
background-color: color-adjust(background, 0.01, 0.01);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5em;
|
||||||
|
color: #ff5e5e;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2em;
|
||||||
|
color: #ffbd72;
|
||||||
|
}
|
||||||
|
|
||||||
|
picture {
|
||||||
|
display: block;
|
||||||
|
margin: 20px auto;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 8px rgba(252, 241, 145, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
li,
|
||||||
|
blockquote {
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffb8b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expressive-code {
|
||||||
|
margin: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.objectives {
|
||||||
|
background-color: #fff45e1a;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
min-height: 100px;
|
||||||
|
|
||||||
|
picture {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -10px;
|
||||||
|
right: -10px;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
width: 140px;
|
||||||
|
max-width: 30%;
|
||||||
|
|
||||||
|
transform: rotate(10deg);
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
78
00-Lesson-Site/src/styles/main.scss
Normal file
78
00-Lesson-Site/src/styles/main.scss
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/* Path: src/styles/main.scss */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--theme-polarity: 1;
|
||||||
|
|
||||||
|
--background-base-l: 0.99;
|
||||||
|
--background-base-c: 0.02;
|
||||||
|
--background-base-h: 100;
|
||||||
|
|
||||||
|
--text-base-l: 0.4;
|
||||||
|
--text-base-c: 0.015;
|
||||||
|
--text-base-h: 84;
|
||||||
|
|
||||||
|
--primary-base-l: 0.75;
|
||||||
|
--primary-base-c: 0.145;
|
||||||
|
--primary-base-h: 142;
|
||||||
|
|
||||||
|
--secondary-base-l: 0.75;
|
||||||
|
--secondary-base-c: 0.06;
|
||||||
|
--secondary-base-h: 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin dark-values {
|
||||||
|
--theme-polarity: -1;
|
||||||
|
|
||||||
|
--background-base-l: 0.1;
|
||||||
|
--background-base-c: 0.015;
|
||||||
|
--background-base-h: 84;
|
||||||
|
|
||||||
|
--text-base-l: 0.95;
|
||||||
|
--text-base-c: 0.015;
|
||||||
|
--text-base-h: 84;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
@include dark-values;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root:not([data-theme="light"]) {
|
||||||
|
@include dark-values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: color-adjust(background, 0, 0);
|
||||||
|
color: color-adjust(text, 0, 0);
|
||||||
|
|
||||||
|
transition:
|
||||||
|
background-color 0.4s ease,
|
||||||
|
color 0.4s ease;
|
||||||
|
|
||||||
|
font-family: "Geist", sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav,
|
||||||
|
button,
|
||||||
|
hr,
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
.icon,
|
||||||
|
svg {
|
||||||
|
transition:
|
||||||
|
background-color 0.4s ease,
|
||||||
|
color 0.4s ease,
|
||||||
|
border-color 0.4s ease,
|
||||||
|
fill 0.4s ease,
|
||||||
|
stroke 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: flow-root;
|
||||||
|
}
|
||||||
14
00-Lesson-Site/tsconfig.json
Normal file
14
00-Lesson-Site/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"include": [
|
||||||
|
".astro/types.d.ts",
|
||||||
|
"**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "solid-js"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user