Upload and test

This commit is contained in:
MangoPig
2025-12-09 17:25:14 +00:00
parent f9a669ae69
commit dba1acccee
53 changed files with 1756 additions and 107 deletions

View File

@@ -0,0 +1,21 @@
---
// Path: src/components/Post/Blockquotes/Ganbatte.astro
import styles from "./Ganbatte.module.scss";
interface Props {
toc?: string;
tocLevel?: string;
imageAlt?: string;
}
const { toc, tocLevel = "1", imageAlt = "MangoPig Ganbatte" } = Astro.props;
---
<blockquote class={styles.ganbatte} data-toc={toc} data-toc-level={tocLevel}>
<slot />
<picture>
<img src="https://pic.mangopig.tech/i/4c4d1b5f-b9ce-4952-a1b4-991b19c0adb5.png" alt={imageAlt} />
</picture>
</blockquote>

View File

@@ -0,0 +1,41 @@
/* Path: src/components/Post/Blockquotes/Ganbatte.module.scss */
.ganbatte {
background-color: #fff45e1a;
padding: 30px;
border-radius: 10px;
position: relative;
min-height: 100px;
picture {
position: absolute;
bottom: -10px;
right: -10px;
margin: 0;
width: 200px;
max-width: 30%;
transform: rotate(10deg);
img {
width: 100%;
height: auto;
box-shadow: none;
}
}
ul {
list-style-type: disc;
padding-left: 20px;
margin-right: 220px;
}
span {
position: absolute;
top: 50%;
left: 30px;
transform: translateY(-50%);
}
}

View File

@@ -0,0 +1,12 @@
---
// Path: src/components/Post/Blockquotes/Important.astro
import styles from "./Important.module.scss";
---
<blockquote class={styles.important}>
<slot />
<picture class={styles.sticker}>
<img src="https://pic.mangopig.tech/i/7eb5b343-5ddf-47ae-a272-4b82ca3d53d7.webp" alt="MangoPig Important" />
</picture>
</blockquote>

View File

@@ -0,0 +1,55 @@
/* Path: src/components/Post/Blockquotes/Important.module.scss */
.important {
background-color: #ff5e5e33;
padding: 30px;
border-radius: 10px;
position: relative;
min-height: 100px;
font-weight: 500;
.sticker {
position: absolute;
bottom: 0px;
right: -10px;
margin: 0;
width: 100px;
max-width: 30%;
transform: rotate(10deg);
img {
width: 100%;
height: auto;
box-shadow: none;
}
}
ul {
list-style-type: disc;
padding-left: 20px;
margin-right: 100px;
}
ol {
list-style-type: decimal;
margin-top: 20px;
padding-left: 20px;
margin-right: 100px;
}
p {
margin-right: 100px;
}
span {
// Place in middle vertically
position: absolute;
top: 50%;
left: 30px;
transform: translateY(-50%);
}
}

View File

@@ -0,0 +1,12 @@
---
// Path: src/components/Post/Blockquotes/Info.astro
import styles from "./Info.module.scss";
---
<blockquote class={styles.info}>
<slot />
<picture class={styles.sticker}>
<img src="https://pic.mangopig.tech/i/ebf2e26a-8791-4277-90cb-079ad7454aef.webp" alt="MangoPig Ganbattte" />
</picture>
</blockquote>

View File

@@ -0,0 +1,53 @@
/* Path: src/components/Post/Blockquotes/Info.module.scss */
.info {
background-color: #5efaff1a;
padding: 30px;
border-radius: 10px;
position: relative;
min-height: 100px;
.sticker {
position: absolute;
bottom: -10px;
right: -10px;
margin: 0;
width: 100px;
max-width: 30%;
transform: rotate(10deg);
img {
width: 100%;
height: auto;
box-shadow: none;
}
}
ul {
list-style-type: disc;
padding-left: 20px;
margin-right: 100px;
}
ol {
list-style-type: decimal;
margin-top: 20px;
padding-left: 20px;
margin-right: 100px;
}
p {
margin-right: 100px;
}
span {
// Place in middle vertically
position: absolute;
top: 50%;
left: 30px;
transform: translateY(-50%);
}
}

View File

@@ -0,0 +1,27 @@
---
// Path: src/components/Post/Blockquotes/QA.astro
import styles from "./QA.module.scss";
---
<div class={styles.qaContainer}>
{/* The Question Section */}
<div class={styles.questionHeader}>
<span class={styles.prefix}>Q:</span>
<span class={styles.questionText}>
<slot name="question" />
</span>
</div>
{/* The Answer Section (Default Slot) */}
<div class={styles.answerBody}>
<span class={styles.prefix}>A:</span>
<div class={styles.answerContent}>
<slot />
</div>
</div>
{/* The Sticker */}
<picture class={styles.sticker}>
<img src="https://pic.mangopig.tech/i/4c4d1b5f-b9ce-4952-a1b4-991b19c0adb5.png" alt="Thinking MangoPig" />
</picture>
</div>

View File

@@ -0,0 +1,78 @@
/* Path: src/components/Post/Blockquotes/QA.module.scss */
.qaContainer {
// Use a yellowish tint to differentiate from Info block
background-color: #fff45e26;
padding: 30px;
border-radius: 10px;
position: relative;
min-height: 120px;
margin-bottom: 20px;
// --- Sticker Logic (Same as Info block) ---
.sticker {
position: absolute;
bottom: -10px;
right: -10px;
margin: 0;
width: 100px;
max-width: 30%;
// Rotate opposite way for variety
transform: rotate(-10deg);
pointer-events: none;
img {
width: 100%;
height: auto;
box-shadow: none;
}
}
//Common prefix style (Q: and A:)
.prefix {
font-weight: 800;
color: #ffbd72; // Matches your H2 color scheme
margin-right: 12px;
display: inline-block;
min-width: 25px;
}
// --- Question Section ---
.questionHeader {
display: flex;
align-items: baseline;
margin-bottom: 20px;
font-size: 1.1em;
font-weight: 700;
// Ensure text doesn't hit the sticker
margin-right: 90px;
color: color-adjust(primary, 0, 0);
}
// --- Answer Section ---
.answerBody {
display: flex;
align-items: baseline;
// Ensure text doesn't hit the sticker
margin-right: 90px;
}
.answerContent {
flex: 1;
line-height: 1.6;
// Handle standard markdown elements inside the answer slot
p {
margin-bottom: 1em;
&:last-child {
margin-bottom: 0;
}
}
ul,
ol {
margin-bottom: 1em;
padding-left: 20px;
}
}
}

View File

@@ -0,0 +1,79 @@
---
// 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) => {
const label = el.getAttribute("data-toc") || "";
const level = el.getAttribute("data-toc-level") || "1";
// --- CHANGED SECTION START: ID Generation (Slugify) ---
if (!el.id) {
// Convert "Setting Up Developer Environment" -> "setting-up-developer-environment"
const slug = label
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, "") // Remove non-word chars
.replace(/[\s_-]+/g, "-") // Replace spaces with dashes
.replace(/^-+|-+$/g, ""); // Trim dashes from start/end
// Safety check: If ID exists or slug is empty, fallback to index
if (!slug || document.getElementById(slug)) {
el.id = `section-${index}`;
} else {
el.id = slug;
}
}
// --- CHANGED SECTION END ---
const li = document.createElement("li");
const a = document.createElement("a");
li.setAttribute("data-level", level);
a.href = `#${el.id}`;
a.innerHTML = `<span class="toc-text">${label}</span>`;
a.addEventListener("click", (e) => {
e.preventDefault();
el.scrollIntoView({ behavior: "smooth", block: "center" });
// Optional: Update URL hash without jumping
history.pushState(null, "", `#${el.id}`);
});
li.appendChild(a);
list.appendChild(li);
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
document.querySelectorAll("#toc-list a").forEach((link) => link.classList.remove("active"));
a.classList.add("active");
}
});
},
{ rootMargin: "-50% 0px -50% 0px", threshold: 0 }
);
observer.observe(el);
});
}
generateTOC();
document.addEventListener("astro:after-swap", generateTOC);
</script>

View 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;
}
}
}

View File

@@ -0,0 +1,28 @@
/* 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);
button {
background: none;
border: none;
color: color-adjust(primary, 0, 0);
cursor: pointer;
font-weight: bold;
padding: 0;
text-decoration: underline;
&:hover {
opacity: 0.8;
}
}
}
.spoilerContent {
margin-top: 1rem;
position: relative;
width: 100%;
min-width: 0;
}

View 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;