feat: Establish CI pipeline, pre-commit hooks, and code quality linting for improved developer experience.

This commit is contained in:
2026-03-11 20:51:35 +03:00
parent c0e261c2ed
commit 187c8eb078
49 changed files with 208356 additions and 1216 deletions
+15 -2
View File
@@ -1,5 +1,5 @@
---
applyTo: "**/*"
applyTo: '**/*'
---
# Copilot Instructions for pwnforfunandprofit.xyz
@@ -13,6 +13,7 @@ Produce production-grade changes that are correct, verifiable, maintainable, and
## 1. Project Context
### Workspace Structure
- **Framework**: Zola (Rust-based static site generator)
- **Templates**: Tera templates (`templates/`)
- **Styling**: SCSS (`sass/`)
@@ -22,6 +23,7 @@ Produce production-grade changes that are correct, verifiable, maintainable, and
- **Skill Instructions**: High-level AI design rules (`llms/`)
### Key Dependencies & Tools
- **Zola**: Static Site Generator (v0.22.1)
- **Template Engine**: Tera (Django/Jinja2-like)
- **Styling**: Vanilla SCSS (No Tailwind or external CSS themes allowed)
@@ -32,6 +34,7 @@ Produce production-grade changes that are correct, verifiable, maintainable, and
- **Comments**: Utterances (GitHub issues based)
### Runtime & Commands
- **Serve Locally**: `zola serve`
- **Build**: `zola build`
- **Check/Lint**: `zola check`
@@ -63,6 +66,7 @@ When instructions conflict, follow this order:
## 4. Definition of Done (Mandatory)
A task is complete only when all applicable items pass:
1. ✓ Requested behavior implemented.
2. ✓ `zola check` detects no orphaned links or missing taxonomies.
3. ✓ `zola build` outputs seamlessly in less than a second.
@@ -74,10 +78,12 @@ A task is complete only when all applicable items pass:
## 5. Mandatory Execution Workflow
### Initialization
1. Check existing implementations in `templates/` and `sass/`.
2. Use absolute URLs (`get_url()`) inside templates instead of relative paths.
### Implementation
3. Implement in small, verifiable increments.
4. If modifying SCSS variables or structural Tera blocks, verify via:
```bash
@@ -85,6 +91,7 @@ A task is complete only when all applicable items pass:
```
### Verification
5. Spin up the server to verify visual cadence:
```bash
zola serve
@@ -99,19 +106,21 @@ A task is complete only when all applicable items pass:
- **Forbidden**: commented-out code, generic placeholder names (e.g. "John Doe", "Acme"), emojis in code or text (unless explicitly requested).
- **Allowed**: required comments explaining non-obvious logic.
- Keep SCSS deterministic: Rely on global variables rather than arbitrary hardcoded hexs.
- Never use emojis in code, documentation, commit messages, or alt texts.
- Never use emojis in code, documentation, commit messages, or alt texts.
---
## 7. Frontend Rules (Zola + SCSS)
### 7.1 Architecture
- Use `{% extends "base.html" %}` to establish layout inheritance.
- Leverage `{% block %}` elements for page-specific overrides.
- Use `config.extra.*` for user-defined configuration toggles.
- Never write JS SPAs inside this structure: interactivity resides in targeted Vanilla JS files in `static/js/`.
### 7.2 Styling Constraints
- Rely exclusively on internal SCSS architecture. Do not suggest or install TailwindCSS.
- Follow `llms/taste-skill.md` requirements:
- Dark Mode overrides inside `html.dark`.
@@ -121,6 +130,7 @@ A task is complete only when all applicable items pass:
- Ensure focus rings and accessible outlines exist.
### 7.3 Content Constraints
- All Markdown files must be prefixed with valid TOML frontmatter (`+++`).
- Frontmatter must include: `title`, `date`.
- Do not utilize unsupported Markdown extensions (Zola relies on CommonMark + specific extensions configurable in `zola.toml`).
@@ -139,12 +149,15 @@ python3 scripts/migrate.py # Re-run Substack migration script if testing feeds
## 9. Quick Help
### "My template is failing to build with a parse error"
→ Zola's Tera engine uses `{{ variable }}` for printing and `{% expression %}` for logic. Check your variable structures.
### "Styles updates aren't applying"
→ Ensure `compile_sass` is enabled in `zola.toml` and you are modifying valid `.scss` assets inside `sass/`.
### "My taxonomy doesn't work or throws an error"
→ If you see `Tried to render taxonomy_list.html`, ensure you created the taxonomy view templates and registered the taxonomy array in `zola.toml`.
---
+45
View File
@@ -0,0 +1,45 @@
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Zola
uses: snok/install-zola@v2
- name: Run Zola Check
run: zola check
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Format Check
run: pnpm run format:check
- name: Markdown Lint
run: pnpm run lint
- name: Spell Check
run: npx cspell "content/**/*.md"
+1
View File
@@ -0,0 +1 @@
node scripts/validate-post.mjs
+9
View File
@@ -0,0 +1,9 @@
{
"default": true,
"MD013": false,
"MD033": false,
"MD041": false,
"MD024": {
"siblings_only": true
}
}
+11
View File
@@ -0,0 +1,11 @@
# Ignore Zola compiled output and dependencies
public/
node_modules/
# Ignore Zola HTML templates (Tera syntax breaks standard HTML parsing)
templates/**/*.html
# Ignore minified static files
static/**/*.min.js
static/**/*.min.css
static/js/cookieconsent.umd.js
+9
View File
@@ -0,0 +1,9 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "all",
"printWidth": 100,
"plugins": ["prettier-plugin-toml"]
}
+4 -4
View File
@@ -1,5 +1,5 @@
{
"chat.tools.terminal.autoApprove": {
"zola": true
}
}
"chat.tools.terminal.autoApprove": {
"zola": true
}
}
+5 -5
View File
@@ -56,11 +56,11 @@ Feature backlog for the blog. Check off items as they're implemented.
## Developer Experience
- [ ] **Prettier / markdownlint** — add linting for content files to enforce consistent front-matter and formatting
- [ ] **`zola check` in CI** — GitHub Actions job that runs `zola check` on every PR to catch broken internal links
- [ ] **Automated post slug validation** — pre-commit hook to enforce kebab-case filenames and required front-matter fields
- [x] **Prettier / markdownlint** — add linting for content files to enforce consistent front-matter and formatting
- [x] **`zola check` in CI** — GitHub Actions job that runs `zola check` on every PR to catch broken internal links
- [x] **Automated post slug validation** — pre-commit hook to enforce kebab-case filenames and required front-matter fields
- [ ] **Cloudflare Pages preview deployments** — auto-deploy PR branches to a `*.pages.dev` preview URL for review
- [ ] **Content spell-check in CI** — run `cspell` or `vale` on markdown files in the GitHub Actions pipeline
- [x] **Content spell-check in CI** — run `cspell` or `vale` on markdown files in the GitHub Actions pipeline
---
@@ -74,4 +74,4 @@ Feature backlog for the blog. Check off items as they're implemented.
---
*Last updated: 2026-03-11*
_Last updated: 2026-03-11_
+1 -1
View File
@@ -7,6 +7,6 @@ I believe this is the start of something great.
Welcome to **pwnforfunandprofit**, your home for all things binary exploitation, reverse engineering, and offensive security. If you've ever felt the thrill of solving a complex puzzle, the satisfaction of understanding how something truly works, or the urge to peek behind the digital curtain, then you're in the right place.
In a world increasingly built on code, understanding its vulnerabilities is not just a niche skill; it's a superpower. Here, we're not just reading about exploits; we're *crafting* them. We're not just looking at binaries; we're *disassembling* them. And we're not just talking about offensive security; we're *doing* it.
In a world increasingly built on code, understanding its vulnerabilities is not just a niche skill; it's a superpower. Here, we're not just reading about exploits; we're _crafting_ them. We're not just looking at binaries; we're _disassembling_ them. And we're not just talking about offensive security; we're _doing_ it.
My mission is simple yet ambitious: to transform the daunting landscapes of low-level security into accessible, engaging, and ultimately, empowering knowledge. Whether you're aiming to land that dream cybersecurity role, dominate CTF leaderboards, uncover critical bugs, or satisfy a deep intellectual curiosity, I'm here to guide you.
+2 -2
View File
@@ -10,6 +10,6 @@ I believe this is the start of something great.
Welcome to **pwnforfunandprofit**, your new home for all things binary exploitation, reverse engineering, and offensive security. If you've ever felt the thrill of solving a complex puzzle, the satisfaction of understanding how something truly works, or the urge to peek behind the digital curtain, then you're in the right place.
In a world increasingly built on code, understanding its vulnerabilities is not just a niche skill; it's a superpower. Here, we're not just reading about exploits; we're *crafting* them. We're not just looking at binaries; we're *disassembling* them. And we're not just talking about offensive security; we're *doing* it.
In a world increasingly built on code, understanding its vulnerabilities is not just a niche skill; it's a superpower. Here, we're not just reading about exploits; we're _crafting_ them. We're not just looking at binaries; we're _disassembling_ them. And we're not just talking about offensive security; we're _doing_ it.
My mission is simple yet ambitious: to transform the daunting landscapes of low-level security into accessible, engaging, and ultimately, empowering knowledge. Whether you're aiming to land that dream cybersecurity role, dominate CTF leaderboards, uncover critical bugs, or satisfy a deep intellectual curiosity, we're here to guide you.
My mission is simple yet ambitious: to transform the daunting landscapes of low-level security into accessible, engaging, and ultimately, empowering knowledge. Whether you're aiming to land that dream cybersecurity role, dominate CTF leaderboards, uncover critical bugs, or satisfy a deep intellectual curiosity, we're here to guide you.
@@ -22,7 +22,7 @@ Big-Endian (BE): The Most Significant Byte (MSB) is stored first, at the lowest
If you’re attacking an embedded device, router, or IoT target, it might be Big-Endian. Always confirm the target architecture and ABI. Common mistakes when cross-compiling or reusing gadgets:
Using x86/x86\_64 gadgets and packers on a MIPS/PowerPC target (different endianness and instruction set) will not work.
Using x86/x86_64 gadgets and packers on a MIPS/PowerPC target (different endianness and instruction set) will not work.
pwntools and other frameworks allow you to set `context.arch` and `context.endian` So packers and disassemblers behave correctly.
@@ -32,7 +32,7 @@ Run the small C program above on the target if you have code execution. Use `rea
In GDB, examine the memory representation of a known constant or pointer. Put a known value in a register or memory, and inspect the byte order with `x/4xb address`.
```
```gdb
(gdb) p/x 0x41424344
$1 = 0x41424344
(gdb) set {int}0x601000 = 0x41424344
@@ -40,7 +40,7 @@ $1 = 0x41424344
0x601000: 0x44 0x43 0x42 0x41 # little-endian: ‘DCBA’
```
# Debugging tips when an exploit “looks right” but crashes
## Debugging tips when an exploit “looks right” but crashes
Print the raw payload bytes you are sending, compare with what the target receives (packet capture or logging), and what the target’s memory shows in a debugger.
@@ -54,25 +54,25 @@ Use tool packing (`struct.pack`, `pwntools`) instead of hand-reversing where pos
If attacking over the network, confirm whether fields are consumed in network byte order.
# Short checklist for exploit authors
## Short checklist for exploit authors
* Confirm target architecture (x86, x86\_64, MIPS, ARM, PowerPC).
* Confirm endianness (LE or BE).
* Use correct pointer width (`p32` vs `p64`) and pack accordingly.
* Use library packing (`struct`, `pwntools`) to avoid manual reversal mistakes.
* For network services, encode multi-byte protocol fields in network (big-endian) byte order unless the protocol documentation says otherwise.
* Check struct padding and alignment on the target.
* Inspect the target’s memory with a debugger to verify the actual byte layout.
- Confirm target architecture (x86, x86_64, MIPS, ARM, PowerPC).
- Confirm endianness (LE or BE).
- Use correct pointer width (`p32` vs `p64`) and pack accordingly.
- Use library packing (`struct`, `pwntools`) to avoid manual reversal mistakes.
- For network services, encode multi-byte protocol fields in network (big-endian) byte order unless the protocol documentation says otherwise.
- Check struct padding and alignment on the target.
- Inspect the target’s memory with a debugger to verify the actual byte layout.
# Final thoughts
## Final thoughts
Endianness is simple in concept but vital in practice. For most modern exploit work, you’ll be on x86\_64 (Little-Endian), so make it a habit to pack addresses in reverse byte order relative to how you read them in a disassembler. Use `pwntools`, `struct`, or equivalent to do the packing for you, but keep in mind *why* bytes are reversed; that understanding is what will let you debug the next crash quickly and confidently.
Endianness is simple in concept but vital in practice. For most modern exploit work, you’ll be on x86*64 (Little-Endian), so make it a habit to pack addresses in reverse byte order relative to how you read them in a disassembler. Use `pwntools`, `struct`, or equivalent to do the packing for you, but keep in mind \_why* bytes are reversed; that understanding is what will let you debug the next crash quickly and confidently.
Recommended reading:
* <https://betterexplained.com/articles/understanding-big-and-little-endian-byte-order/>
* <https://medium.com/coderscorner/byte-ordering-e4737416d906>
* <https://devopedia.org/byte-ordering>
* <https://www.geeksforgeeks.org/dsa/little-and-big-endian-mystery/>
- <https://betterexplained.com/articles/understanding-big-and-little-endian-byte-order/>
- <https://medium.com/coderscorner/byte-ordering-e4737416d906>
- <https://devopedia.org/byte-ordering>
- <https://www.geeksforgeeks.org/dsa/little-and-big-endian-mystery/>
Disclaimer: This article was written with AI assistance, for a bit of brainstorming and proofreading.
Disclaimer: This article was written with AI assistance, for a bit of brainstorming and proofreading.
+61 -61
View File
@@ -12,138 +12,138 @@ In most challenges, you’ll be given a Linux binary to exploit, and occasionall
Over the course of this series, we will look at the basics of a binary from simple to advanced concepts. This will be a multipart post covering the concepts over a couple of months. I’ll also use it as a reference to track my own progress.
> This assumes that the reader has some significant experience with programming, i will not be explaining how to write software. The tutotials will be in C, simple enough while being low level for some of the things well do. I recommend reading :
> This assumes that the reader has some significant experience with programming, i will not be explaining how to write software. The tutorials will be in C, simple enough while being low level for some of the things well do. I recommend reading :
>
> *[The C Programming Language](https://www.amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/0131103628)*[, 2nd Edition, by Brian W. Kernighan and Dennis M. Ritchie (Prentice Hall, 1988).](https://www.amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/0131103628)
> _[The C Programming Language](https://www.amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/0131103628)_[, 2nd Edition, by Brian W. Kernighan and Dennis M. Ritchie (Prentice Hall, 1988).](https://www.amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/0131103628)
---
# Concepts to Cover
## Concepts to Cover
This roadmap will take you from *“I don’t know how the stack works”* to *“I can write exploits for CTFs and understand real-world CVEs.”*
This roadmap will take you from _“I don’t know how the stack works”_ to _“I can write exploits for CTFs and understand real-world CVEs.”_
### **Episode 1: Getting Started**
* Why binary exploitation matters (CTFs, real-world, research).
* Computer architecture basics (x86/x64 registers, stack vs heap, memory layout).
* Compiling and running simple C programs.
* Intro to ELF binaries.
- Why binary exploitation matters (CTFs, real-world, research).
- Computer architecture basics (x86/x64 registers, stack vs heap, memory layout).
- Compiling and running simple C programs.
- Intro to ELF binaries.
---
### **Episode 2: Assembly & Tools Crash Course**
* Function calls in assembly (prologue/epilogue, stack frames).
* Common instructions (`mov`, `call`, `jmp`, `ret`).
* Debugging with `gdb`, `gef`/`pwndbg`.
* Disassembly with `objdump`, `radare2`, and `Ghidra`.
- Function calls in assembly (prologue/epilogue, stack frames).
- Common instructions (`mov`, `call`, `jmp`, `ret`).
- Debugging with `gdb`, `gef`/`pwndbg`.
- Disassembly with `objdump`, `radare2`, and `Ghidra`.
---
### **Episode 3: Memory Corruption 101**
* What is a segmentation fault?
* Stack buffer overflows (first crashes).
* Off-by-one errors.
* Writing your first shellcode and executing it locally.
- What is a segmentation fault?
- Stack buffer overflows (first crashes).
- Off-by-one errors.
- Writing your first shellcode and executing it locally.
---
### **Episode 4: Exploiting Control Flow**
* Overwriting return addresses.
* NOP sleds and shellcode execution.
* Return-to-libc attacks (ret2libc).
* Environment variables and argument tricks.
- Overwriting return addresses.
- NOP sleds and shellcode execution.
- Return-to-libc attacks (ret2libc).
- Environment variables and argument tricks.
---
### **Episode 5: Format String Vulnerabilities**
* How `printf` becomes an exploit primitive.
* Memory leaks via format strings.
* Overwriting memory using `%n`.
- How `printf` becomes an exploit primitive.
- Memory leaks via format strings.
- Overwriting memory using `%n`.
---
### **Episode 6: Modern Protections & Bypasses (Part 1)**
* NX (non-executable stack) and its bypass with ROP.
* Intro to ROP gadgets and building small chains.
* Automating with Pwntools and ROPgadget.
- NX (non-executable stack) and its bypass with ROP.
- Intro to ROP gadgets and building small chains.
- Automating with Pwntools and ROPgadget.
---
### **Episode 7: Modern Protections & Bypasses (Part 2)**
* ASLR: how randomization works.
* Leaking addresses to defeat ASLR.
* Stack canaries and partial overwrites.
- ASLR: how randomization works.
- Leaking addresses to defeat ASLR.
- Stack canaries and partial overwrites.
---
### **Episode 8: Heap Exploitation (Basics)**
* Understanding malloc/free and heap metadata.
* Classic heap overflows.
* Use-after-free and double free.
* Simple heap exploitation demos.
- Understanding malloc/free and heap metadata.
- Classic heap overflows.
- Use-after-free and double free.
- Simple heap exploitation demos.
---
### **Episode 9: Heap Exploitation (Advanced)**
* Unlink attacks and unsafe unlink.
* Fastbin dup tricks.
* The “House of” techniques:
- Unlink attacks and unsafe unlink.
- Fastbin dup tricks.
- The “House of” techniques:
- House of Force
- House of Spirit
- House of Lore
- House of Einherjar
+ House of Force
+ House of Spirit
+ House of Lore
+ House of Einherjar
* Tcache poisoning in modern glibc.
- Tcache poisoning in modern glibc.
---
### **Episode 10: Advanced Exploitation Techniques**
* GOT/PLT overwrites.
* Ret2dlresolve.
* Sigreturn-oriented programming (SROP).
* JOP (Jump-Oriented Programming).
- GOT/PLT overwrites.
- Ret2dlresolve.
- Sigreturn-oriented programming (SROP).
- JOP (Jump-Oriented Programming).
---
### **Episode 11: Kernel & Windows Exploitation (Intro)**
* Userland vs kernel space.
* Exploiting syscalls for privilege escalation.
* Windows basics: SEH overwrites, DEP/ASLR bypass.
* Kernel driver exploitation.
- Userland vs kernel space.
- Exploiting syscalls for privilege escalation.
- Windows basics: SEH overwrites, DEP/ASLR bypass.
- Kernel driver exploitation.
---
### **Episode 12: Modern Defenses & Mitigations**
* Control-Flow Integrity (CFI).
* Shadow stacks & CET.
* Hardened malloc and allocator security.
* Sandboxing & seccomp-bpf.
- Control-Flow Integrity (CFI).
- Shadow stacks & CET.
- Hardened malloc and allocator security.
- Sandboxing & seccomp-bpf.
---
### **Episode 13: Exploit Workflow in Practice**
* Solving a CTF challenge step-by-step.
* Remote exploitation (exploiting over sockets).
* Exploit reliability (stability, bruteforcing).
* Post-exploitation steps (getting a shell, escalating privileges).
- Solving a CTF challenge step-by-step.
- Remote exploitation (exploiting over sockets).
- Exploit reliability (stability, bruteforcing).
- Post-exploitation steps (getting a shell, escalating privileges).
---
### **Episode 14: Beyond the Basics**
* Automating exploit discovery with fuzzing (AFL, honggfuzz).
* Symbolic execution with angr.
* Real-world case studies (Heartbleed, Dirty COW, Shellshock).
* Writing weaponized exploits responsibly.
- Automating exploit discovery with fuzzing (AFL, honggfuzz).
- Symbolic execution with angr.
- Real-world case studies (Heartbleed, Dirty COW, Shellshock).
- Writing weaponized exploits responsibly.
+68
View File
@@ -0,0 +1,68 @@
{
"version": "0.2",
"language": "en",
"words": [
"DCBA",
"Einherjar",
"Fastbin",
"Ghidra",
"Heartbleed",
"Kernighan",
"Pgadget",
"Pwntools",
"Ritchie",
"SIGSEGV",
"SROP",
"Sandboxing",
"Shellshock",
"Sigreturn",
"Tcache",
"Userland",
"andwati",
"angr",
"bruteforcing",
"buttondown",
"catppuccin",
"cookieconsent",
"disassemblers",
"dlresolve",
"elasticlunr",
"endianness",
"fgets",
"frontmatter",
"honggfuzz",
"katex",
"kebab",
"libc",
"lightbox",
"listmonk",
"malloc",
"markdownlint",
"mockingspectre",
"objdump",
"precommit",
"pwn",
"pwndbg",
"pwnforfunandprofit",
"pwntools",
"radare",
"readelf",
"scanf",
"seccomp",
"shellcode",
"shortcodes",
"subdirectories",
"syscalls",
"utterances",
"webmention",
"zola"
],
"ignorePaths": [
"node_modules",
"package.json",
"pnpm-lock.yaml",
"public",
"static/js/**",
"static/css/**"
]
}
+2 -1
View File
@@ -43,7 +43,8 @@ On "continue", pick up exactly where you stopped. No recap, no repetition.
## Quick Check
Before finalizing any response, verify:
- No banned patterns from the list above appear anywhere in the output
- Every item the user requested is present and finished
- Code blocks contain actual runnable code, not descriptions of what code would do
- Nothing was shortened to save space
- Nothing was shortened to save space
+5 -1
View File
@@ -134,23 +134,27 @@ Check for these problems and fix them:
When upgrading a project, pull from these high-impact techniques to replace generic patterns:
### Typography Upgrades
- **Variable font animation.** Interpolate weight or width on scroll or hover for text that feels alive.
- **Outlined-to-fill transitions.** Text starts as a stroke outline and fills with color on scroll entry or interaction.
- **Text mask reveals.** Large typography acting as a window to video or animated imagery behind it.
### Layout Upgrades
- **Broken grid / asymmetry.** Elements that deliberately ignore column structure — overlapping, bleeding off-screen, or offset with calculated randomness.
- **Whitespace maximization.** Aggressive use of negative space to force focus on a single element.
- **Parallax card stacks.** Sections that stick and physically stack over each other during scroll.
- **Split-screen scroll.** Two halves of the screen sliding in opposite directions.
### Motion Upgrades
- **Smooth scroll with inertia.** Decouple scrolling from browser defaults for a heavier, cinematic feel.
- **Staggered entry.** Elements cascade in with slight delays, combining Y-axis translation with opacity fade. Never mount everything at once.
- **Spring physics.** Replace linear easing with spring-based motion for a natural, weighty feel on all interactive elements.
- **Scroll-driven reveals.** Content entering through expanding masks, wipes, or draw-on SVG paths tied to scroll progress.
### Surface Upgrades
- **True glassmorphism.** Go beyond `backdrop-filter: blur`. Add a 1px inner border and a subtle inner shadow to simulate edge refraction.
- **Spotlight borders.** Card borders that illuminate dynamically under the cursor.
- **Grain and noise overlays.** A fixed, pointer-events-none overlay with subtle noise to break digital flatness.
@@ -175,4 +179,4 @@ Apply changes in this order for maximum visual impact with minimum risk:
- Before importing any new library, check the project's dependency file first.
- If the project uses Tailwind, check the version (v3 vs v4) before modifying config.
- If the project has no framework, use vanilla CSS.
- Keep changes reviewable and focused. Small, targeted improvements over big rewrites.
- Keep changes reviewable and focused. Small, targeted improvements over big rewrites.
+164 -127
View File
@@ -6,209 +6,244 @@ description: Senior UI/UX Engineer. Architect digital interfaces overriding defa
# High-Agency Frontend Skill
## 1. ACTIVE BASELINE CONFIGURATION
* DESIGN_VARIANCE: 8 (1=Perfect Symmetry, 10=Artsy Chaos)
* MOTION_INTENSITY: 6 (1=Static/No movement, 10=Cinematic/Magic Physics)
* VISUAL_DENSITY: 4 (1=Art Gallery/Airy, 10=Pilot Cockpit/Packed Data)
- DESIGN_VARIANCE: 8 (1=Perfect Symmetry, 10=Artsy Chaos)
- MOTION_INTENSITY: 6 (1=Static/No movement, 10=Cinematic/Magic Physics)
- VISUAL_DENSITY: 4 (1=Art Gallery/Airy, 10=Pilot Cockpit/Packed Data)
**AI Instruction:** The standard baseline for all generations is strictly set to these values (8, 6, 4). Do not ask the user to edit this file. Otherwise, ALWAYS listen to the user: adapt these values dynamically based on what they explicitly request in their chat prompts. Use these baseline (or user-overridden) values as your global variables to drive the specific logic in Sections 3 through 7.
## 2. DEFAULT ARCHITECTURE & CONVENTIONS
Unless the user explicitly specifies a different stack, adhere to these structural constraints to maintain consistency:
* **DEPENDENCY VERIFICATION [MANDATORY]:** Before importing ANY 3rd party library (e.g. `framer-motion`, `lucide-react`, `zustand`), you MUST check `package.json`. If the package is missing, you MUST output the installation command (e.g. `npm install package-name`) before providing the code. **Never** assume a library exists.
* **Framework & Interactivity:** React or Next.js. Default to Server Components (`RSC`).
* **RSC SAFETY:** Global state works ONLY in Client Components. In Next.js, wrap providers in a `"use client"` component.
* **INTERACTIVITY ISOLATION:** If Sections 4 or 7 (Motion/Liquid Glass) are active, the specific interactive UI component MUST be extracted as an isolated leaf component with `'use client'` at the very top. Server Components must exclusively render static layouts.
* **State Management:** Use local `useState`/`useReducer` for isolated UI. Use global state strictly for deep prop-drilling avoidance.
* **Styling Policy:** Use Tailwind CSS (v3/v4) for 90% of styling.
* **TAILWIND VERSION LOCK:** Check `package.json` first. Do not use v4 syntax in v3 projects.
* **T4 CONFIG GUARD:** For v4, do NOT use `tailwindcss` plugin in `postcss.config.js`. Use `@tailwindcss/postcss` or the Vite plugin.
* **ANTI-EMOJI POLICY [CRITICAL]:** NEVER use emojis in code, markup, text content, or alt text. Replace symbols with high-quality icons (Radix, Phosphor) or clean SVG primitives. Emojis are BANNED.
* **Responsiveness & Spacing:**
* Standardize breakpoints (`sm`, `md`, `lg`, `xl`).
* Contain page layouts using `max-w-[1400px] mx-auto` or `max-w-7xl`.
* **Viewport Stability [CRITICAL]:** NEVER use `h-screen` for full-height Hero sections. ALWAYS use `min-h-[100dvh]` to prevent catastrophic layout jumping on mobile browsers (iOS Safari).
* **Grid over Flex-Math:** NEVER use complex flexbox percentage math (`w-[calc(33%-1rem)]`). ALWAYS use CSS Grid (`grid grid-cols-1 md:grid-cols-3 gap-6`) for reliable structures.
* **Icons:** You MUST use exactly `@phosphor-icons/react` or `@radix-ui/react-icons` as the import paths (check installed version). Standardize `strokeWidth` globally (e.g., exclusively use `1.5` or `2.0`).
- **DEPENDENCY VERIFICATION [MANDATORY]:** Before importing ANY 3rd party library (e.g. `framer-motion`, `lucide-react`, `zustand`), you MUST check `package.json`. If the package is missing, you MUST output the installation command (e.g. `npm install package-name`) before providing the code. **Never** assume a library exists.
- **Framework & Interactivity:** React or Next.js. Default to Server Components (`RSC`).
- **RSC SAFETY:** Global state works ONLY in Client Components. In Next.js, wrap providers in a `"use client"` component.
- **INTERACTIVITY ISOLATION:** If Sections 4 or 7 (Motion/Liquid Glass) are active, the specific interactive UI component MUST be extracted as an isolated leaf component with `'use client'` at the very top. Server Components must exclusively render static layouts.
- **State Management:** Use local `useState`/`useReducer` for isolated UI. Use global state strictly for deep prop-drilling avoidance.
- **Styling Policy:** Use Tailwind CSS (v3/v4) for 90% of styling.
- **TAILWIND VERSION LOCK:** Check `package.json` first. Do not use v4 syntax in v3 projects.
- **T4 CONFIG GUARD:** For v4, do NOT use `tailwindcss` plugin in `postcss.config.js`. Use `@tailwindcss/postcss` or the Vite plugin.
- **ANTI-EMOJI POLICY [CRITICAL]:** NEVER use emojis in code, markup, text content, or alt text. Replace symbols with high-quality icons (Radix, Phosphor) or clean SVG primitives. Emojis are BANNED.
- **Responsiveness & Spacing:**
- Standardize breakpoints (`sm`, `md`, `lg`, `xl`).
- Contain page layouts using `max-w-[1400px] mx-auto` or `max-w-7xl`.
- **Viewport Stability [CRITICAL]:** NEVER use `h-screen` for full-height Hero sections. ALWAYS use `min-h-[100dvh]` to prevent catastrophic layout jumping on mobile browsers (iOS Safari).
- **Grid over Flex-Math:** NEVER use complex flexbox percentage math (`w-[calc(33%-1rem)]`). ALWAYS use CSS Grid (`grid grid-cols-1 md:grid-cols-3 gap-6`) for reliable structures.
- **Icons:** You MUST use exactly `@phosphor-icons/react` or `@radix-ui/react-icons` as the import paths (check installed version). Standardize `strokeWidth` globally (e.g., exclusively use `1.5` or `2.0`).
## 3. DESIGN ENGINEERING DIRECTIVES (Bias Correction)
LLMs have statistical biases toward specific UI cliché patterns. Proactively construct premium interfaces using these engineered rules:
**Rule 1: Deterministic Typography**
* **Display/Headlines:** Default to `text-4xl md:text-6xl tracking-tighter leading-none`.
* **ANTI-SLOP:** Discourage `Inter` for "Premium" or "Creative" vibes. Force unique character using `Geist`, `Outfit`, `Cabinet Grotesk`, or `Satoshi`.
* **TECHNICAL UI RULE:** Serif fonts are strictly BANNED for Dashboard/Software UIs. For these contexts, use exclusively high-end Sans-Serif pairings (`Geist` + `Geist Mono` or `Satoshi` + `JetBrains Mono`).
* **Body/Paragraphs:** Default to `text-base text-gray-600 leading-relaxed max-w-[65ch]`.
- **Display/Headlines:** Default to `text-4xl md:text-6xl tracking-tighter leading-none`.
- **ANTI-SLOP:** Discourage `Inter` for "Premium" or "Creative" vibes. Force unique character using `Geist`, `Outfit`, `Cabinet Grotesk`, or `Satoshi`.
- **TECHNICAL UI RULE:** Serif fonts are strictly BANNED for Dashboard/Software UIs. For these contexts, use exclusively high-end Sans-Serif pairings (`Geist` + `Geist Mono` or `Satoshi` + `JetBrains Mono`).
- **Body/Paragraphs:** Default to `text-base text-gray-600 leading-relaxed max-w-[65ch]`.
**Rule 2: Color Calibration**
* **Constraint:** Max 1 Accent Color. Saturation < 80%.
* **THE LILA BAN:** The "AI Purple/Blue" aesthetic is strictly BANNED. No purple button glows, no neon gradients. Use absolute neutral bases (Zinc/Slate) with high-contrast, singular accents (e.g. Emerald, Electric Blue, or Deep Rose).
* **COLOR CONSISTENCY:** Stick to one palette for the entire output. Do not fluctuate between warm and cool grays within the same project.
- **Constraint:** Max 1 Accent Color. Saturation < 80%.
- **THE LILA BAN:** The "AI Purple/Blue" aesthetic is strictly BANNED. No purple button glows, no neon gradients. Use absolute neutral bases (Zinc/Slate) with high-contrast, singular accents (e.g. Emerald, Electric Blue, or Deep Rose).
- **COLOR CONSISTENCY:** Stick to one palette for the entire output. Do not fluctuate between warm and cool grays within the same project.
**Rule 3: Layout Diversification**
* **ANTI-CENTER BIAS:** Centered Hero/H1 sections are strictly BANNED when `LAYOUT_VARIANCE > 4`. Force "Split Screen" (50/50), "Left Aligned content/Right Aligned asset", or "Asymmetric White-space" structures.
- **ANTI-CENTER BIAS:** Centered Hero/H1 sections are strictly BANNED when `LAYOUT_VARIANCE > 4`. Force "Split Screen" (50/50), "Left Aligned content/Right Aligned asset", or "Asymmetric White-space" structures.
**Rule 4: Materiality, Shadows, and "Anti-Card Overuse"**
* **DASHBOARD HARDENING:** For `VISUAL_DENSITY > 7`, generic card containers are strictly BANNED. Use logic-grouping via `border-t`, `divide-y`, or purely negative space. Data metrics should breathe without being boxed in unless elevation (z-index) is functionally required.
* **Execution:** Use cards ONLY when elevation communicates hierarchy. When a shadow is used, tint it to the background hue.
- **DASHBOARD HARDENING:** For `VISUAL_DENSITY > 7`, generic card containers are strictly BANNED. Use logic-grouping via `border-t`, `divide-y`, or purely negative space. Data metrics should breathe without being boxed in unless elevation (z-index) is functionally required.
- **Execution:** Use cards ONLY when elevation communicates hierarchy. When a shadow is used, tint it to the background hue.
**Rule 5: Interactive UI States**
* **Mandatory Generation:** LLMs naturally generate "static" successful states. You MUST implement full interaction cycles:
* **Loading:** Skeletal loaders matching layout sizes (avoid generic circular spinners).
* **Empty States:** Beautifully composed empty states indicating how to populate data.
* **Error States:** Clear, inline error reporting (e.g., forms).
* **Tactile Feedback:** On `:active`, use `-translate-y-[1px]` or `scale-[0.98]` to simulate a physical push indicating success/action.
- **Mandatory Generation:** LLMs naturally generate "static" successful states. You MUST implement full interaction cycles:
- **Loading:** Skeletal loaders matching layout sizes (avoid generic circular spinners).
- **Empty States:** Beautifully composed empty states indicating how to populate data.
- **Error States:** Clear, inline error reporting (e.g., forms).
- **Tactile Feedback:** On `:active`, use `-translate-y-[1px]` or `scale-[0.98]` to simulate a physical push indicating success/action.
**Rule 6: Data & Form Patterns**
* **Forms:** Label MUST sit above input. Helper text is optional but should exist in markup. Error text below input. Use a standard `gap-2` for input blocks.
- **Forms:** Label MUST sit above input. Helper text is optional but should exist in markup. Error text below input. Use a standard `gap-2` for input blocks.
## 4. CREATIVE PROACTIVITY (Anti-Slop Implementation)
To actively combat generic AI designs, systematically implement these high-end coding concepts as your baseline:
* **"Liquid Glass" Refraction:** When glassmorphism is needed, go beyond `backdrop-blur`. Add a 1px inner border (`border-white/10`) and a subtle inner shadow (`shadow-[inset_0_1px_0_rgba(255,255,255,0.1)]`) to simulate physical edge refraction.
* **Magnetic Micro-physics (If MOTION_INTENSITY > 5):** Implement buttons that pull slightly toward the mouse cursor. **CRITICAL:** NEVER use React `useState` for magnetic hover or continuous animations. Use EXCLUSIVELY Framer Motion's `useMotionValue` and `useTransform` outside the React render cycle to prevent performance collapse on mobile.
* **Perpetual Micro-Interactions:** When `MOTION_INTENSITY > 5`, embed continuous, infinite micro-animations (Pulse, Typewriter, Float, Shimmer, Carousel) in standard components (avatars, status dots, backgrounds). Apply premium Spring Physics (`type: "spring", stiffness: 100, damping: 20`) to all interactive elements—no linear easing.
* **Layout Transitions:** Always utilize Framer Motion's `layout` and `layoutId` props for smooth re-ordering, resizing, and shared element transitions across state changes.
* **Staggered Orchestration:** Do not mount lists or grids instantly. Use `staggerChildren` (Framer) or CSS cascade (`animation-delay: calc(var(--index) * 100ms)`) to create sequential waterfall reveals. **CRITICAL:** For `staggerChildren`, the Parent (`variants`) and Children MUST reside in the identical Client Component tree. If data is fetched asynchronously, pass the data as props into a centralized Parent Motion wrapper.
- **"Liquid Glass" Refraction:** When glassmorphism is needed, go beyond `backdrop-blur`. Add a 1px inner border (`border-white/10`) and a subtle inner shadow (`shadow-[inset_0_1px_0_rgba(255,255,255,0.1)]`) to simulate physical edge refraction.
- **Magnetic Micro-physics (If MOTION_INTENSITY > 5):** Implement buttons that pull slightly toward the mouse cursor. **CRITICAL:** NEVER use React `useState` for magnetic hover or continuous animations. Use EXCLUSIVELY Framer Motion's `useMotionValue` and `useTransform` outside the React render cycle to prevent performance collapse on mobile.
- **Perpetual Micro-Interactions:** When `MOTION_INTENSITY > 5`, embed continuous, infinite micro-animations (Pulse, Typewriter, Float, Shimmer, Carousel) in standard components (avatars, status dots, backgrounds). Apply premium Spring Physics (`type: "spring", stiffness: 100, damping: 20`) to all interactive elements—no linear easing.
- **Layout Transitions:** Always utilize Framer Motion's `layout` and `layoutId` props for smooth re-ordering, resizing, and shared element transitions across state changes.
- **Staggered Orchestration:** Do not mount lists or grids instantly. Use `staggerChildren` (Framer) or CSS cascade (`animation-delay: calc(var(--index) * 100ms)`) to create sequential waterfall reveals. **CRITICAL:** For `staggerChildren`, the Parent (`variants`) and Children MUST reside in the identical Client Component tree. If data is fetched asynchronously, pass the data as props into a centralized Parent Motion wrapper.
## 5. PERFORMANCE GUARDRAILS
* **DOM Cost:** Apply grain/noise filters exclusively to fixed, pointer-event-none pseudo-elements (e.g., `fixed inset-0 z-50 pointer-events-none`) and NEVER to scrolling containers to prevent continuous GPU repaints and mobile performance degradation.
* **Hardware Acceleration:** Never animate `top`, `left`, `width`, or `height`. Animate exclusively via `transform` and `opacity`.
* **Z-Index Restraint:** NEVER spam arbitrary `z-50` or `z-10` unprompted. Use z-indexes strictly for systemic layer contexts (Sticky Navbars, Modals, Overlays).
- **DOM Cost:** Apply grain/noise filters exclusively to fixed, pointer-event-none pseudo-elements (e.g., `fixed inset-0 z-50 pointer-events-none`) and NEVER to scrolling containers to prevent continuous GPU repaints and mobile performance degradation.
- **Hardware Acceleration:** Never animate `top`, `left`, `width`, or `height`. Animate exclusively via `transform` and `opacity`.
- **Z-Index Restraint:** NEVER spam arbitrary `z-50` or `z-10` unprompted. Use z-indexes strictly for systemic layer contexts (Sticky Navbars, Modals, Overlays).
## 6. TECHNICAL REFERENCE (Dial Definitions)
### DESIGN_VARIANCE (Level 1-10)
* **1-3 (Predictable):** Flexbox `justify-center`, strict 12-column symmetrical grids, equal paddings.
* **4-7 (Offset):** Use `margin-top: -2rem` overlapping, varied image aspect ratios (e.g., 4:3 next to 16:9), left-aligned headers over center-aligned data.
* **8-10 (Asymmetric):** Masonry layouts, CSS Grid with fractional units (e.g., `grid-template-columns: 2fr 1fr 1fr`), massive empty zones (`padding-left: 20vw`).
* **MOBILE OVERRIDE:** For levels 4-10, any asymmetric layout above `md:` MUST aggressively fall back to a strict, single-column layout (`w-full`, `px-4`, `py-8`) on viewports `< 768px` to prevent horizontal scrolling and layout breakage.
- **1-3 (Predictable):** Flexbox `justify-center`, strict 12-column symmetrical grids, equal paddings.
- **4-7 (Offset):** Use `margin-top: -2rem` overlapping, varied image aspect ratios (e.g., 4:3 next to 16:9), left-aligned headers over center-aligned data.
- **8-10 (Asymmetric):** Masonry layouts, CSS Grid with fractional units (e.g., `grid-template-columns: 2fr 1fr 1fr`), massive empty zones (`padding-left: 20vw`).
- **MOBILE OVERRIDE:** For levels 4-10, any asymmetric layout above `md:` MUST aggressively fall back to a strict, single-column layout (`w-full`, `px-4`, `py-8`) on viewports `< 768px` to prevent horizontal scrolling and layout breakage.
### MOTION_INTENSITY (Level 1-10)
* **1-3 (Static):** No automatic animations. CSS `:hover` and `:active` states only.
* **4-7 (Fluid CSS):** Use `transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1)`. Use `animation-delay` cascades for load-ins. Focus strictly on `transform` and `opacity`. Use `will-change: transform` sparingly.
* **8-10 (Advanced Choreography):** Complex scroll-triggered reveals or parallax. Use Framer Motion hooks. NEVER use `window.addEventListener('scroll')`.
- **1-3 (Static):** No automatic animations. CSS `:hover` and `:active` states only.
- **4-7 (Fluid CSS):** Use `transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1)`. Use `animation-delay` cascades for load-ins. Focus strictly on `transform` and `opacity`. Use `will-change: transform` sparingly.
- **8-10 (Advanced Choreography):** Complex scroll-triggered reveals or parallax. Use Framer Motion hooks. NEVER use `window.addEventListener('scroll')`.
### VISUAL_DENSITY (Level 1-10)
* **1-3 (Art Gallery Mode):** Lots of white space. Huge section gaps. Everything feels very expensive and clean.
* **4-7 (Daily App Mode):** Normal spacing for standard web apps.
* **8-10 (Cockpit Mode):** Tiny paddings. No card boxes; just 1px lines to separate data. Everything is packed. **Mandatory:** Use Monospace (`font-mono`) for all numbers.
- **1-3 (Art Gallery Mode):** Lots of white space. Huge section gaps. Everything feels very expensive and clean.
- **4-7 (Daily App Mode):** Normal spacing for standard web apps.
- **8-10 (Cockpit Mode):** Tiny paddings. No card boxes; just 1px lines to separate data. Everything is packed. **Mandatory:** Use Monospace (`font-mono`) for all numbers.
## 7. AI TELLS (Forbidden Patterns)
To guarantee a premium, non-generic output, you MUST strictly avoid these common AI design signatures unless explicitly requested:
### Visual & CSS
* **NO Neon/Outer Glows:** Do not use default `box-shadow` glows or auto-glows. Use inner borders or subtle tinted shadows.
* **NO Pure Black:** Never use `#000000`. Use Off-Black, Zinc-950, or Charcoal.
* **NO Oversaturated Accents:** Desaturate accents to blend elegantly with neutrals.
* **NO Excessive Gradient Text:** Do not use text-fill gradients for large headers.
* **NO Custom Mouse Cursors:** They are outdated and ruin performance/accessibility.
- **NO Neon/Outer Glows:** Do not use default `box-shadow` glows or auto-glows. Use inner borders or subtle tinted shadows.
- **NO Pure Black:** Never use `#000000`. Use Off-Black, Zinc-950, or Charcoal.
- **NO Oversaturated Accents:** Desaturate accents to blend elegantly with neutrals.
- **NO Excessive Gradient Text:** Do not use text-fill gradients for large headers.
- **NO Custom Mouse Cursors:** They are outdated and ruin performance/accessibility.
### Typography
* **NO Inter Font:** Banned. Use `Geist`, `Outfit`, `Cabinet Grotesk`, or `Satoshi`.
* **NO Oversized H1s:** The first heading should not scream. Control hierarchy with weight and color, not just massive scale.
* **Serif Constraints:** Use Serif fonts ONLY for creative/editorial designs. **NEVER** use Serif on clean Dashboards.
- **NO Inter Font:** Banned. Use `Geist`, `Outfit`, `Cabinet Grotesk`, or `Satoshi`.
- **NO Oversized H1s:** The first heading should not scream. Control hierarchy with weight and color, not just massive scale.
- **Serif Constraints:** Use Serif fonts ONLY for creative/editorial designs. **NEVER** use Serif on clean Dashboards.
### Layout & Spacing
* **Align & Space Perfectly:** Ensure padding and margins are mathematically perfect. Avoid floating elements with awkward gaps.
* **NO 3-Column Card Layouts:** The generic "3 equal cards horizontally" feature row is BANNED. Use a 2-column Zig-Zag, asymmetric grid, or horizontal scrolling approach instead.
- **Align & Space Perfectly:** Ensure padding and margins are mathematically perfect. Avoid floating elements with awkward gaps.
- **NO 3-Column Card Layouts:** The generic "3 equal cards horizontally" feature row is BANNED. Use a 2-column Zig-Zag, asymmetric grid, or horizontal scrolling approach instead.
### Content & Data (The "Jane Doe" Effect)
* **NO Generic Names:** "John Doe", "Sarah Chan", or "Jack Su" are banned. Use highly creative, realistic-sounding names.
* **NO Generic Avatars:** DO NOT use standard SVG "egg" or Lucide user icons for avatars. Use creative, believable photo placeholders or specific styling.
* **NO Fake Numbers:** Avoid predictable outputs like `99.99%`, `50%`, or basic phone numbers (`1234567`). Use organic, messy data (`47.2%`, `+1 (312) 847-1928`).
* **NO Startup Slop Names:** "Acme", "Nexus", "SmartFlow". Invent premium, contextual brand names.
* **NO Filler Words:** Avoid AI copywriting clichés like "Elevate", "Seamless", "Unleash", or "Next-Gen". Use concrete verbs.
- **NO Generic Names:** "John Doe", "Sarah Chan", or "Jack Su" are banned. Use highly creative, realistic-sounding names.
- **NO Generic Avatars:** DO NOT use standard SVG "egg" or Lucide user icons for avatars. Use creative, believable photo placeholders or specific styling.
- **NO Fake Numbers:** Avoid predictable outputs like `99.99%`, `50%`, or basic phone numbers (`1234567`). Use organic, messy data (`47.2%`, `+1 (312) 847-1928`).
- **NO Startup Slop Names:** "Acme", "Nexus", "SmartFlow". Invent premium, contextual brand names.
- **NO Filler Words:** Avoid AI copywriting clichés like "Elevate", "Seamless", "Unleash", or "Next-Gen". Use concrete verbs.
### External Resources & Components
* **NO Broken Unsplash Links:** Do not use Unsplash. Use absolute, reliable placeholders like `https://picsum.photos/seed/{random_string}/800/600` or SVG UI Avatars.
* **shadcn/ui Customization:** You may use `shadcn/ui`, but NEVER in its generic default state. You MUST customize the radii, colors, and shadows to match the high-end project aesthetic.
* **Production-Ready Cleanliness:** Code must be extremely clean, visually striking, memorable, and meticulously refined in every detail.
- **NO Broken Unsplash Links:** Do not use Unsplash. Use absolute, reliable placeholders like `https://picsum.photos/seed/{random_string}/800/600` or SVG UI Avatars.
- **shadcn/ui Customization:** You may use `shadcn/ui`, but NEVER in its generic default state. You MUST customize the radii, colors, and shadows to match the high-end project aesthetic.
- **Production-Ready Cleanliness:** Code must be extremely clean, visually striking, memorable, and meticulously refined in every detail.
## 8. THE CREATIVE ARSENAL (High-End Inspiration)
Do not default to generic UI. Pull from this library of advanced concepts to ensure the output is visually striking and memorable. When appropriate, leverage **GSAP (ScrollTrigger/Parallax)** for complex scrolltelling or **ThreeJS/WebGL** for 3D/Canvas animations, rather than basic CSS motion. **CRITICAL:** Never mix GSAP/ThreeJS with Framer Motion in the same component tree. Default to Framer Motion for UI/Bento interactions. Use GSAP/ThreeJS EXCLUSIVELY for isolated full-page scrolltelling or canvas backgrounds, wrapped in strict useEffect cleanup blocks.
### The Standard Hero Paradigm
* Stop doing centered text over a dark image. Try asymmetric Hero sections: Text cleanly aligned to the left or right. The background should feature a high-quality, relevant image with a subtle stylistic fade (darkening or lightening gracefully into the background color depending on if it is Light or Dark mode).
- Stop doing centered text over a dark image. Try asymmetric Hero sections: Text cleanly aligned to the left or right. The background should feature a high-quality, relevant image with a subtle stylistic fade (darkening or lightening gracefully into the background color depending on if it is Light or Dark mode).
### Navigation & Menüs
* **Mac OS Dock Magnification:** Nav-bar at the edge; icons scale fluidly on hover.
* **Magnetic Button:** Buttons that physically pull toward the cursor.
* **Gooey Menu:** Sub-items detach from the main button like a viscous liquid.
* **Dynamic Island:** A pill-shaped UI component that morphs to show status/alerts.
* **Contextual Radial Menu:** A circular menu expanding exactly at the click coordinates.
* **Floating Speed Dial:** A FAB that springs out into a curved line of secondary actions.
* **Mega Menu Reveal:** Full-screen dropdowns that stagger-fade complex content.
- **Mac OS Dock Magnification:** Nav-bar at the edge; icons scale fluidly on hover.
- **Magnetic Button:** Buttons that physically pull toward the cursor.
- **Gooey Menu:** Sub-items detach from the main button like a viscous liquid.
- **Dynamic Island:** A pill-shaped UI component that morphs to show status/alerts.
- **Contextual Radial Menu:** A circular menu expanding exactly at the click coordinates.
- **Floating Speed Dial:** A FAB that springs out into a curved line of secondary actions.
- **Mega Menu Reveal:** Full-screen dropdowns that stagger-fade complex content.
### Layout & Grids
* **Bento Grid:** Asymmetric, tile-based grouping (e.g., Apple Control Center).
* **Masonry Layout:** Staggered grid without fixed row heights (e.g., Pinterest).
* **Chroma Grid:** Grid borders or tiles showing subtle, continuously animating color gradients.
* **Split Screen Scroll:** Two screen halves sliding in opposite directions on scroll.
* **Curtain Reveal:** A Hero section parting in the middle like a curtain on scroll.
- **Bento Grid:** Asymmetric, tile-based grouping (e.g., Apple Control Center).
- **Masonry Layout:** Staggered grid without fixed row heights (e.g., Pinterest).
- **Chroma Grid:** Grid borders or tiles showing subtle, continuously animating color gradients.
- **Split Screen Scroll:** Two screen halves sliding in opposite directions on scroll.
- **Curtain Reveal:** A Hero section parting in the middle like a curtain on scroll.
### Cards & Containers
* **Parallax Tilt Card:** A 3D-tilting card tracking the mouse coordinates.
* **Spotlight Border Card:** Card borders that illuminate dynamically under the cursor.
* **Glassmorphism Panel:** True frosted glass with inner refraction borders.
* **Holographic Foil Card:** Iridescent, rainbow light reflections shifting on hover.
* **Tinder Swipe Stack:** A physical stack of cards the user can swipe away.
* **Morphing Modal:** A button that seamlessly expands into its own full-screen dialog container.
- **Parallax Tilt Card:** A 3D-tilting card tracking the mouse coordinates.
- **Spotlight Border Card:** Card borders that illuminate dynamically under the cursor.
- **Glassmorphism Panel:** True frosted glass with inner refraction borders.
- **Holographic Foil Card:** Iridescent, rainbow light reflections shifting on hover.
- **Tinder Swipe Stack:** A physical stack of cards the user can swipe away.
- **Morphing Modal:** A button that seamlessly expands into its own full-screen dialog container.
### Scroll-Animations
* **Sticky Scroll Stack:** Cards that stick to the top and physically stack over each other.
* **Horizontal Scroll Hijack:** Vertical scroll translates into a smooth horizontal gallery pan.
* **Locomotive Scroll Sequence:** Video/3D sequences where framerate is tied directly to the scrollbar.
* **Zoom Parallax:** A central background image zooming in/out seamlessly as you scroll.
* **Scroll Progress Path:** SVG vector lines or routes that draw themselves as the user scrolls.
* **Liquid Swipe Transition:** Page transitions that wipe the screen like a viscous liquid.
- **Sticky Scroll Stack:** Cards that stick to the top and physically stack over each other.
- **Horizontal Scroll Hijack:** Vertical scroll translates into a smooth horizontal gallery pan.
- **Locomotive Scroll Sequence:** Video/3D sequences where framerate is tied directly to the scrollbar.
- **Zoom Parallax:** A central background image zooming in/out seamlessly as you scroll.
- **Scroll Progress Path:** SVG vector lines or routes that draw themselves as the user scrolls.
- **Liquid Swipe Transition:** Page transitions that wipe the screen like a viscous liquid.
### Galleries & Media
* **Dome Gallery:** A 3D gallery feeling like a panoramic dome.
* **Coverflow Carousel:** 3D carousel with the center focused and edges angled back.
* **Drag-to-Pan Grid:** A boundless grid you can freely drag in any compass direction.
* **Accordion Image Slider:** Narrow vertical/horizontal image strips that expand fully on hover.
* **Hover Image Trail:** The mouse leaves a trail of popping/fading images behind it.
* **Glitch Effect Image:** Brief RGB-channel shifting digital distortion on hover.
- **Dome Gallery:** A 3D gallery feeling like a panoramic dome.
- **Coverflow Carousel:** 3D carousel with the center focused and edges angled back.
- **Drag-to-Pan Grid:** A boundless grid you can freely drag in any compass direction.
- **Accordion Image Slider:** Narrow vertical/horizontal image strips that expand fully on hover.
- **Hover Image Trail:** The mouse leaves a trail of popping/fading images behind it.
- **Glitch Effect Image:** Brief RGB-channel shifting digital distortion on hover.
### Typography & Text
* **Kinetic Marquee:** Endless text bands that reverse direction or speed up on scroll.
* **Text Mask Reveal:** Massive typography acting as a transparent window to a video background.
* **Text Scramble Effect:** Matrix-style character decoding on load or hover.
* **Circular Text Path:** Text curved along a spinning circular path.
* **Gradient Stroke Animation:** Outlined text with a gradient continuously running along the stroke.
* **Kinetic Typography Grid:** A grid of letters dodging or rotating away from the cursor.
- **Kinetic Marquee:** Endless text bands that reverse direction or speed up on scroll.
- **Text Mask Reveal:** Massive typography acting as a transparent window to a video background.
- **Text Scramble Effect:** Matrix-style character decoding on load or hover.
- **Circular Text Path:** Text curved along a spinning circular path.
- **Gradient Stroke Animation:** Outlined text with a gradient continuously running along the stroke.
- **Kinetic Typography Grid:** A grid of letters dodging or rotating away from the cursor.
### Micro-Interactions & Effects
* **Particle Explosion Button:** CTAs that shatter into particles upon success.
* **Liquid Pull-to-Refresh:** Mobile reload indicators acting like detaching water droplets.
* **Skeleton Shimmer:** Shifting light reflections moving across placeholder boxes.
* **Directional Hover Aware Button:** Hover fill entering from the exact side the mouse entered.
* **Ripple Click Effect:** Visual waves rippling precisely from the click coordinates.
* **Animated SVG Line Drawing:** Vectors that draw their own contours in real-time.
* **Mesh Gradient Background:** Organic, lava-lamp-like animated color blobs.
* **Lens Blur Depth:** Dynamic focus blurring background UI layers to highlight a foreground action.
- **Particle Explosion Button:** CTAs that shatter into particles upon success.
- **Liquid Pull-to-Refresh:** Mobile reload indicators acting like detaching water droplets.
- **Skeleton Shimmer:** Shifting light reflections moving across placeholder boxes.
- **Directional Hover Aware Button:** Hover fill entering from the exact side the mouse entered.
- **Ripple Click Effect:** Visual waves rippling precisely from the click coordinates.
- **Animated SVG Line Drawing:** Vectors that draw their own contours in real-time.
- **Mesh Gradient Background:** Organic, lava-lamp-like animated color blobs.
- **Lens Blur Depth:** Dynamic focus blurring background UI layers to highlight a foreground action.
## 9. THE "MOTION-ENGINE" BENTO PARADIGM
When generating modern SaaS dashboards or feature sections, you MUST utilize the following "Bento 2.0" architecture and motion philosophy. This goes beyond static cards and enforces a "Vercel-core meets Dribbble-clean" aesthetic heavily reliant on perpetual physics.
### A. Core Design Philosophy
* **Aesthetic:** High-end, minimal, and functional.
* **Palette:** Background in `#f9fafb`. Cards are pure white (`#ffffff`) with a 1px border of `border-slate-200/50`.
* **Surfaces:** Use `rounded-[2.5rem]` for all major containers. Apply a "diffusion shadow" (a very light, wide-spreading shadow, e.g., `shadow-[0_20px_40px_-15px_rgba(0,0,0,0.05)]`) to create depth without clutter.
* **Typography:** Strict `Geist`, `Satoshi`, or `Cabinet Grotesk` font stack. Use subtle tracking (`tracking-tight`) for headers.
* **Labels:** Titles and descriptions must be placed **outside and below** the cards to maintain a clean, gallery-style presentation.
* **Pixel-Perfection:** Use generous `p-8` or `p-10` padding inside cards.
- **Aesthetic:** High-end, minimal, and functional.
- **Palette:** Background in `#f9fafb`. Cards are pure white (`#ffffff`) with a 1px border of `border-slate-200/50`.
- **Surfaces:** Use `rounded-[2.5rem]` for all major containers. Apply a "diffusion shadow" (a very light, wide-spreading shadow, e.g., `shadow-[0_20px_40px_-15px_rgba(0,0,0,0.05)]`) to create depth without clutter.
- **Typography:** Strict `Geist`, `Satoshi`, or `Cabinet Grotesk` font stack. Use subtle tracking (`tracking-tight`) for headers.
- **Labels:** Titles and descriptions must be placed **outside and below** the cards to maintain a clean, gallery-style presentation.
- **Pixel-Perfection:** Use generous `p-8` or `p-10` padding inside cards.
### B. The Animation Engine Specs (Perpetual Motion)
All cards must contain **"Perpetual Micro-Interactions."** Use the following Framer Motion principles:
* **Spring Physics:** No linear easing. Use `type: "spring", stiffness: 100, damping: 20` for a premium, weighty feel.
* **Layout Transitions:** Heavily utilize the `layout` and `layoutId` props to ensure smooth re-ordering, resizing, and shared element state transitions.
* **Infinite Loops:** Every card must have an "Active State" that loops infinitely (Pulse, Typewriter, Float, or Carousel) to ensure the dashboard feels "alive".
* **Performance:** Wrap dynamic lists in `<AnimatePresence>` and optimize for 60fps. **PERFORMANCE CRITICAL:** Any perpetual motion or infinite loop MUST be memoized (React.memo) and completely isolated in its own microscopic Client Component. Never trigger re-renders in the parent layout.
- **Spring Physics:** No linear easing. Use `type: "spring", stiffness: 100, damping: 20` for a premium, weighty feel.
- **Layout Transitions:** Heavily utilize the `layout` and `layoutId` props to ensure smooth re-ordering, resizing, and shared element state transitions.
- **Infinite Loops:** Every card must have an "Active State" that loops infinitely (Pulse, Typewriter, Float, or Carousel) to ensure the dashboard feels "alive".
- **Performance:** Wrap dynamic lists in `<AnimatePresence>` and optimize for 60fps. **PERFORMANCE CRITICAL:** Any perpetual motion or infinite loop MUST be memoized (React.memo) and completely isolated in its own microscopic Client Component. Never trigger re-renders in the parent layout.
### C. The 5-Card Archetypes (Micro-Animation Specs)
Implement these specific micro-animations when constructing Bento grids (e.g., Row 1: 3 cols | Row 2: 2 cols split 70/30):
1. **The Intelligent List:** A vertical stack of items with an infinite auto-sorting loop. Items swap positions using `layoutId`, simulating an AI prioritizing tasks in real-time.
2. **The Command Input:** A search/AI bar with a multi-step Typewriter Effect. It cycles through complex prompts, including a blinking cursor and a "processing" state with a shimmering loading gradient.
3. **The Live Status:** A scheduling interface with "breathing" status indicators. Include a pop-up notification badge that emerges with an "Overshoot" spring effect, stays for 3 seconds, and vanishes.
@@ -216,11 +251,13 @@ Implement these specific micro-animations when constructing Bento grids (e.g., R
5. **The Contextual UI (Focus Mode):** A document view that animates a staggered highlight of a text block, followed by a "Float-in" of a floating action toolbar with micro-icons.
## 10. FINAL PRE-FLIGHT CHECK
Evaluate your code against this matrix before outputting. This is the **last** filter you apply to your logic.
- [ ] Is global state used appropriately to avoid deep prop-drilling rather than arbitrarily?
- [ ] Is mobile layout collapse (`w-full`, `px-4`, `max-w-7xl mx-auto`) guaranteed for high-variance designs?
- [ ] Do full-height sections safely use `min-h-[100dvh]` instead of the bugged `h-screen`?
- [ ] Do `useEffect` animations contain strict cleanup functions?
- [ ] Are empty, loading, and error states provided?
- [ ] Are cards omitted in favor of spacing where possible?
- [ ] Did you strictly isolate CPU-heavy perpetual animations in their own Client Components?
- [ ] Did you strictly isolate CPU-heavy perpetual animations in their own Client Components?
+9 -1
View File
@@ -4,13 +4,21 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"format": "prettier --write \"**/*.{html,css,scss,js,json,toml,ya?ml,md}\"",
"format:check": "prettier --check \"**/*.{html,css,scss,js,json,toml,ya?ml,md}\"",
"lint": "markdownlint \"content/**/*.md\"",
"test": "echo \"Error: no test specified\" && exit 1",
"prepare": "husky"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "[email protected]+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268",
"devDependencies": {
"husky": "^9.1.7",
"markdownlint-cli": "^0.48.0",
"prettier": "^3.8.1",
"prettier-plugin-toml": "^2.0.6",
"wrangler": "^4.71.0"
}
}
+658
View File
@@ -8,6 +8,18 @@ importers:
.:
devDependencies:
husky:
specifier: ^9.1.7
version: 9.1.7
markdownlint-cli:
specifier: ^0.48.0
version: 0.48.0
prettier:
specifier: ^3.8.1
version: 3.8.1
prettier-plugin-toml:
specifier: ^2.0.6
version: 2.0.6([email protected])
wrangler:
specifier: ^4.71.0
version: 4.71.0
@@ -399,17 +411,94 @@ packages:
'@speed-highlight/[email protected]':
resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==}
'@taplo/[email protected]':
resolution: {integrity: sha512-r8bl54Zj1In3QLkiW/ex694bVzpPJ9EhwqT9xkcUVODnVUGirdB1JTsmiIv0o1uwqZiwhi8xNnTOQBRQCpizrQ==}
'@taplo/[email protected]':
resolution: {integrity: sha512-+xIqpQXJco3T+VGaTTwmhxLa51qpkQxCjRwezjFZgr+l21ExlywJFcDfTrNmL6lG6tqb0h8GyJKO3UPGPtSCWg==}
'@types/[email protected]':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
'@types/[email protected]':
resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==}
'@types/[email protected]':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
'@types/[email protected]':
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
[email protected]:
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
engines: {node: '>=12'}
[email protected]:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
[email protected]:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22}
[email protected]:
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
[email protected]:
resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==}
engines: {node: 18 || 20 || >=22}
[email protected]:
resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
[email protected]:
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
[email protected]:
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
[email protected]:
resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
engines: {node: '>=20'}
[email protected]:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
[email protected]:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
[email protected]:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
[email protected]:
resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
[email protected]:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
[email protected]:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
[email protected]:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
[email protected]:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
[email protected]:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
[email protected]:
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
@@ -418,26 +507,209 @@ packages:
engines: {node: '>=18'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
[email protected]:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
[email protected]:
resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
engines: {node: '>=18'}
[email protected]:
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
engines: {node: '>=18'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'}
[email protected]:
resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
[email protected]:
resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
[email protected]:
resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
[email protected]:
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
[email protected]:
resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
[email protected]:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
[email protected]:
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
[email protected]:
resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
engines: {node: '>=0.10.0'}
[email protected]:
resolution: {integrity: sha512-cjHooZUmIAUmDsHBN+1n8LaZdpmbj03LtYeYPyuYB7OuloiaeaV6N4LcfjcnHVzGWjVQmKrxxTrpDcmSzEZQwQ==}
hasBin: true
[email protected]:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
[email protected]:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
[email protected]:
resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
hasBin: true
[email protected]:
resolution: {integrity: sha512-NkZQNu2E0Q5qLEEHwWj674eYISTLD4jMHkBzDobujXd1kv+yCxi8jOaD/rZoQNW1FBBMMGQpuW5So8B51N/e0A==}
engines: {node: '>=20'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-UKybllYNheWac61Ia7T6fzuQNDZimFIpCg2w6hHjgV1Qu0w1TV0LlSgryUGzM0bkKQCBhy2FDhEELB73Kb0kAg==}
engines: {node: '>=20'}
[email protected]:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
[email protected]:
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
[email protected]:
resolution: {integrity: sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==}
[email protected]:
resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==}
[email protected]:
resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==}
[email protected]:
resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==}
[email protected]:
resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==}
[email protected]:
resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
[email protected]:
resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==}
[email protected]:
resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==}
[email protected]:
resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==}
[email protected]:
resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==}
[email protected]:
resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
[email protected]:
resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==}
[email protected]:
resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==}
[email protected]:
resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==}
[email protected]:
resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==}
[email protected]:
resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
[email protected]:
resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==}
[email protected]:
resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==}
[email protected]:
resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==}
[email protected]:
resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
[email protected]:
resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==}
[email protected]:
resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
[email protected]:
resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}
[email protected]:
resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==}
[email protected]:
resolution: {integrity: sha512-fqkHx0QMKswRH9uqQQQOU/RoaS3Wjckxy3CUX3YGJr0ZIMu7ObvI+NovdYi6RIsSPthNtq+3TPmRNxjeRiasog==}
engines: {node: '>=18.0.0'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
engines: {node: 18 || 20 || >=22}
[email protected]:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
[email protected]:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
[email protected]:
resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
[email protected]:
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
[email protected]:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
[email protected]:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
[email protected]:
resolution: {integrity: sha512-12N/wBuHa9jd/KVy9pRP20NMKxQfQLMseQCt66lIbLaPLItvGUcSIryE1eZZMJ7loSws6Ig3M2Elc2EreNh76w==}
engines: {node: '>=16.0.0'}
peerDependencies:
prettier: ^3.0.3
[email protected]:
resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==}
engines: {node: '>=14'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
[email protected]:
resolution: {integrity: sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==}
hasBin: true
[email protected]:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
engines: {node: '>=10'}
@@ -447,13 +719,36 @@ packages:
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
[email protected]:
resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==}
engines: {node: '>= 18'}
[email protected]:
resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==}
engines: {node: '>=20'}
[email protected]:
resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
engines: {node: '>=12'}
[email protected]:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
[email protected]:
resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
engines: {node: '>=18'}
[email protected]:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
[email protected]:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
[email protected]:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
[email protected]:
resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==}
engines: {node: '>=20.18.1'}
@@ -727,12 +1022,66 @@ snapshots:
'@speed-highlight/[email protected]': {}
'@taplo/[email protected]': {}
'@taplo/[email protected]':
dependencies:
'@taplo/core': 0.2.0
'@types/[email protected]':
dependencies:
'@types/ms': 2.1.0
'@types/[email protected]': {}
'@types/[email protected]': {}
'@types/[email protected]': {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
balanced-match: 4.0.4
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
ms: 2.1.3
[email protected]:
dependencies:
character-entities: 2.0.2
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
dequal: 2.0.3
[email protected]: {}
[email protected]: {}
[email protected]:
@@ -764,11 +1113,264 @@ snapshots:
'@esbuild/win32-ia32': 0.27.3
'@esbuild/win32-x64': 0.27.3
[email protected]([email protected]):
optionalDependencies:
picomatch: 4.0.3
[email protected]:
optional: true
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
is-alphabetical: 2.0.1
is-decimal: 2.0.1
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
argparse: 2.0.1
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
commander: 8.3.0
[email protected]: {}
[email protected]:
dependencies:
uc.micro: 2.1.0
[email protected]:
dependencies:
argparse: 2.0.1
entities: 4.5.0
linkify-it: 5.0.0
mdurl: 2.0.0
punycode.js: 2.3.1
uc.micro: 2.1.0
[email protected]:
dependencies:
commander: 14.0.3
deep-extend: 0.6.0
ignore: 7.0.5
js-yaml: 4.1.1
jsonc-parser: 3.3.1
jsonpointer: 5.0.1
markdown-it: 14.1.1
markdownlint: 0.40.0
minimatch: 10.2.4
run-con: 1.3.2
smol-toml: 1.6.0
tinyglobby: 0.2.15
transitivePeerDependencies:
- supports-color
[email protected]:
dependencies:
micromark: 4.0.2
micromark-core-commonmark: 2.0.3
micromark-extension-directive: 4.0.0
micromark-extension-gfm-autolink-literal: 2.1.0
micromark-extension-gfm-footnote: 2.1.0
micromark-extension-gfm-table: 2.1.1
micromark-extension-math: 3.1.0
micromark-util-types: 2.0.2
string-width: 8.1.0
transitivePeerDependencies:
- supports-color
[email protected]: {}
[email protected]:
dependencies:
decode-named-character-reference: 1.3.0
devlop: 1.1.0
micromark-factory-destination: 2.0.1
micromark-factory-label: 2.0.1
micromark-factory-space: 2.0.1
micromark-factory-title: 2.0.1
micromark-factory-whitespace: 2.0.1
micromark-util-character: 2.1.1
micromark-util-chunked: 2.0.1
micromark-util-classify-character: 2.0.1
micromark-util-html-tag-name: 2.0.1
micromark-util-normalize-identifier: 2.0.1
micromark-util-resolve-all: 2.0.1
micromark-util-subtokenize: 2.1.0
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
devlop: 1.1.0
micromark-factory-space: 2.0.1
micromark-factory-whitespace: 2.0.1
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
parse-entities: 4.0.2
[email protected]:
dependencies:
micromark-util-character: 2.1.1
micromark-util-sanitize-uri: 2.0.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
devlop: 1.1.0
micromark-core-commonmark: 2.0.3
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-normalize-identifier: 2.0.1
micromark-util-sanitize-uri: 2.0.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
devlop: 1.1.0
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
'@types/katex': 0.16.8
devlop: 1.1.0
katex: 0.16.38
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
devlop: 1.1.0
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
micromark-util-character: 2.1.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
micromark-util-symbol: 2.0.1
[email protected]:
dependencies:
micromark-util-character: 2.1.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
micromark-util-chunked: 2.0.1
micromark-util-types: 2.0.2
[email protected]:
dependencies:
micromark-util-symbol: 2.0.1
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
micromark-util-symbol: 2.0.1
[email protected]:
dependencies:
micromark-util-types: 2.0.2
[email protected]:
dependencies:
micromark-util-character: 2.1.1
micromark-util-encode: 2.0.1
micromark-util-symbol: 2.0.1
[email protected]:
dependencies:
devlop: 1.1.0
micromark-util-chunked: 2.0.1
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
'@types/debug': 4.1.12
debug: 4.4.3
decode-named-character-reference: 1.3.0
devlop: 1.1.0
micromark-core-commonmark: 2.0.3
micromark-factory-space: 2.0.1
micromark-util-character: 2.1.1
micromark-util-chunked: 2.0.1
micromark-util-combine-extensions: 2.0.1
micromark-util-decode-numeric-character-reference: 2.0.2
micromark-util-encode: 2.0.1
micromark-util-normalize-identifier: 2.0.1
micromark-util-resolve-all: 2.0.1
micromark-util-sanitize-uri: 2.0.1
micromark-util-subtokenize: 2.1.0
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
transitivePeerDependencies:
- supports-color
[email protected]:
dependencies:
'@cspotcode/source-map-support': 0.8.1
@@ -781,10 +1383,46 @@ snapshots:
- bufferutil
- utf-8-validate
[email protected]:
dependencies:
brace-expansion: 5.0.4
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
'@types/unist': 2.0.11
character-entities-legacy: 3.0.0
character-reference-invalid: 2.0.1
decode-named-character-reference: 1.3.0
is-alphanumerical: 2.0.1
is-decimal: 2.0.1
is-hexadecimal: 2.0.1
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]([email protected]):
dependencies:
'@taplo/lib': 0.5.0
prettier: 3.8.1
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
deep-extend: 0.6.0
ini: 4.1.3
minimist: 1.2.8
strip-json-comments: 3.1.1
[email protected]: {}
[email protected]:
@@ -818,11 +1456,31 @@ snapshots:
'@img/sharp-win32-ia32': 0.34.5
'@img/sharp-win32-x64': 0.34.5
[email protected]: {}
[email protected]:
dependencies:
get-east-asian-width: 1.5.0
strip-ansi: 7.2.0
[email protected]:
dependencies:
ansi-regex: 6.2.2
[email protected]: {}
[email protected]: {}
[email protected]:
dependencies:
fdir: 6.5.0([email protected])
picomatch: 4.0.3
[email protected]:
optional: true
[email protected]: {}
[email protected]: {}
[email protected]:
+30 -15
View File
@@ -55,7 +55,7 @@
padding: 1.5rem;
border-radius: 12px;
margin-bottom: 3rem;
summary {
font-weight: 600;
cursor: pointer;
@@ -63,9 +63,9 @@
display: flex;
align-items: center;
gap: 0.5rem;
&::before {
content: "↓";
content: '↓';
color: var(--accent-color);
transition: transform 0.2s ease;
}
@@ -81,15 +81,17 @@
list-style-type: none;
padding-left: 1rem;
}
li {
margin-bottom: 0.5rem;
}
a {
color: var(--text-muted);
font-size: 0.95em;
&:hover { color: var(--accent-color); }
&:hover {
color: var(--accent-color);
}
}
}
@@ -98,7 +100,7 @@
flex-direction: column;
gap: 2rem;
margin-top: 4rem;
@media (min-width: 640px) {
flex-direction: row;
justify-content: space-between;
@@ -110,7 +112,7 @@
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
.tag {
background: var(--surface-color);
border: 1px solid var(--border-color);
@@ -150,13 +152,26 @@
color: var(--bg-color);
transform: translateY(-2px);
border-color: transparent;
&.x { background: #000000; color: #ffffff; }
&.bluesky { background: #0285FF; }
&.linkedin { background: #0077b5; }
&.reddit { background: #ff4500; }
&.hackernews { background: #ff6600; }
&.email { background: #6b7280; }
&.x {
background: #000000;
color: #ffffff;
}
&.bluesky {
background: #0285ff;
}
&.linkedin {
background: #0077b5;
}
&.reddit {
background: #ff4500;
}
&.hackernews {
background: #ff6600;
}
&.email {
background: #6b7280;
}
}
}
}
+38
View File
@@ -12,6 +12,44 @@
margin-top: 2rem;
}
// Homepage "View all posts" CTA
.view-all-posts {
display: flex;
justify-content: center;
margin: 3rem auto 0;
max-width: var(--content-width);
}
.btn-view-all {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.6rem 1.5rem;
border: 1px solid var(--accent-color);
border-radius: 6px;
font-family: var(--font-mono);
font-size: 0.875rem;
color: var(--accent-color);
background: transparent;
text-decoration: none;
transition: var(--transition-smooth);
letter-spacing: 0.04em;
&::after {
content: "→";
transition: transform 0.2s ease;
}
&:hover {
background: var(--accent-color);
color: var(--bg-color);
&::after {
transform: translateX(4px);
}
}
}
.feed-item {
display: block;
}
+20 -10
View File
@@ -41,7 +41,9 @@
justify-content: center;
border-radius: 50%;
transition: var(--transition-smooth);
&:hover { color: var(--accent-color); }
&:hover {
color: var(--accent-color);
}
}
// Hamburger button — hidden on desktop, shown on mobile
@@ -54,12 +56,18 @@
padding: 0.25rem;
align-items: center;
justify-content: center;
.close-icon { display: none; }
&[aria-expanded="true"] {
.hamburger-icon { display: none; }
.close-icon { display: block; }
.close-icon {
display: none;
}
&[aria-expanded='true'] {
.hamburger-icon {
display: none;
}
.close-icon {
display: block;
}
}
}
@@ -69,12 +77,12 @@
}
// Hide the site name when the menu is open
.nav-container:has(#nav-toggle[aria-expanded="true"]) .brand-link {
.nav-container:has(#nav-toggle[aria-expanded='true']) .brand-link {
display: none;
}
// Make nav-right fill the full width so menu items span the container
.nav-container:has(#nav-toggle[aria-expanded="true"]) .nav-right {
.nav-container:has(#nav-toggle[aria-expanded='true']) .nav-right {
width: 100%;
}
@@ -103,7 +111,9 @@
padding: 0.75rem 0;
font-size: 1rem;
border-bottom: 1px solid var(--border-color);
&::after { display: none; }
&::after {
display: none;
}
}
}
}
+8 -2
View File
@@ -11,7 +11,11 @@
}
// Constrain all media & tables to their container
img, video, iframe, table, svg {
img,
video,
iframe,
table,
svg {
max-width: 100%;
}
@@ -20,7 +24,9 @@ table {
overflow-x: auto;
@media (max-width: 640px) {
scrollbar-width: none;
&::-webkit-scrollbar { display: none; }
&::-webkit-scrollbar {
display: none;
}
}
}
+3 -1
View File
@@ -13,7 +13,9 @@ html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background-color 0.3s ease, color 0.3s ease;
transition:
background-color 0.3s ease,
color 0.3s ease;
}
body {
+88 -88
View File
@@ -1,126 +1,126 @@
// Search overlay
.search-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(4px);
z-index: 9999;
display: none;
align-items: flex-start;
justify-content: center;
padding: 2rem;
overflow-y: auto;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(4px);
z-index: 9999;
display: none;
align-items: flex-start;
justify-content: center;
padding: 2rem;
overflow-y: auto;
&.is-active {
display: flex;
}
&.is-active {
display: flex;
}
}
.search-modal {
background-color: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
max-width: 42rem;
width: 100%;
margin-top: 10vh;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
overflow: hidden;
background-color: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
max-width: 42rem;
width: 100%;
margin-top: 10vh;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
overflow: hidden;
}
.search-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
gap: 0.5rem;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
}
.search-input {
flex: 1;
font-size: 1.125rem;
padding: 0.5rem;
background: transparent;
border: none;
color: var(--text-color);
outline: none;
flex: 1;
font-size: 1.125rem;
padding: 0.5rem;
background: transparent;
border: none;
color: var(--text-color);
outline: none;
&::placeholder {
color: var(--text-muted);
}
&::placeholder {
color: var(--text-muted);
}
}
.search-close {
background: transparent;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 0.25rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.25rem;
transition: background-color 0.2s ease;
background: transparent;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 0.25rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.25rem;
transition: background-color 0.2s ease;
&:hover {
background-color: var(--border-color);
color: var(--text-color);
}
&:hover {
background-color: var(--border-color);
color: var(--text-color);
}
}
.search-results {
max-height: 60vh;
overflow-y: auto;
padding: 0.5rem;
max-height: 60vh;
overflow-y: auto;
padding: 0.5rem;
}
.search-result-item {
display: block;
padding: 1rem;
border-radius: 0.375rem;
text-decoration: none;
color: var(--text-color);
transition: background-color 0.2s ease;
display: block;
padding: 1rem;
border-radius: 0.375rem;
text-decoration: none;
color: var(--text-color);
transition: background-color 0.2s ease;
&:hover {
background-color: var(--border-color);
}
&:hover {
background-color: var(--border-color);
}
}
.search-result-title {
font-size: 1rem;
font-weight: 600;
margin: 0 0 0.5rem 0;
color: var(--accent-color);
font-size: 1rem;
font-weight: 600;
margin: 0 0 0.5rem 0;
color: var(--accent-color);
}
.search-result-snippet {
font-size: 0.875rem;
color: var(--text-muted);
margin: 0;
line-height: 1.5;
font-size: 0.875rem;
color: var(--text-muted);
margin: 0;
line-height: 1.5;
}
.search-no-results {
padding: 2rem;
text-align: center;
color: var(--text-muted);
padding: 2rem;
text-align: center;
color: var(--text-muted);
}
.search-footer {
padding: 0.75rem 1rem;
border-top: 1px solid var(--border-color);
font-size: 0.75rem;
color: var(--text-muted);
text-align: center;
padding: 0.75rem 1rem;
border-top: 1px solid var(--border-color);
font-size: 0.75rem;
color: var(--text-muted);
text-align: center;
kbd {
padding: 0.125rem 0.375rem;
background-color: var(--border-color);
border-radius: 0.25rem;
font-family: var(--font-mono);
font-size: 0.75rem;
}
kbd {
padding: 0.125rem 0.375rem;
background-color: var(--border-color);
border-radius: 0.25rem;
font-family: var(--font-mono);
font-size: 0.75rem;
}
}
+12 -4
View File
@@ -17,7 +17,15 @@
}
}
html.dark .sun-icon { display: block; }
html.dark .moon-icon { display: none; }
html:not(.dark) .sun-icon { display: none; }
html:not(.dark) .moon-icon { display: block; }
html.dark .sun-icon {
display: block;
}
html.dark .moon-icon {
display: none;
}
html:not(.dark) .sun-icon {
display: none;
}
html:not(.dark) .moon-icon {
display: block;
}
+29 -8
View File
@@ -1,5 +1,10 @@
// Typography Scale (Fluid)
h1, h2, h3, h4, h5, h6 {
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.02em;
@@ -8,11 +13,22 @@ h1, h2, h3, h4, h5, h6 {
word-break: break-word;
}
h1 { font-size: clamp(1.75rem, 5vw, 4.5rem); }
h2 { font-size: clamp(1.35rem, 3vw, 2.5rem); }
h3 { font-size: clamp(1.2rem, 2vw, 1.8rem); margin-top: 1.5em; }
h1 {
font-size: clamp(1.75rem, 5vw, 4.5rem);
}
h2 {
font-size: clamp(1.35rem, 3vw, 2.5rem);
}
h3 {
font-size: clamp(1.2rem, 2vw, 1.8rem);
margin-top: 1.5em;
}
p, ul, ol, figure, blockquote {
p,
ul,
ol,
figure,
blockquote {
margin-bottom: 1.5rem;
}
@@ -20,7 +36,8 @@ p {
max-width: 65ch; // Optimal reading width
}
code, pre {
code,
pre {
font-family: var(--font-mono);
font-size: 0.9em;
}
@@ -36,7 +53,9 @@ pre {
// Scrollbars visible on desktop, hidden on mobile
@media (max-width: 640px) {
scrollbar-width: none;
&::-webkit-scrollbar { display: none; }
&::-webkit-scrollbar {
display: none;
}
font-size: 0.82em;
}
}
@@ -56,7 +75,9 @@ pre {
border-radius: 4px;
cursor: pointer;
opacity: 1;
transition: background 0.2s ease, color 0.2s ease;
transition:
background 0.2s ease,
color 0.2s ease;
svg {
width: 16px;
+1 -1
View File
@@ -1,7 +1,7 @@
// Utilities / Interactions
.hover-scale {
transition: var(--transition-spring);
&:active {
transform: scale(0.98);
}
+2 -2
View File
@@ -7,10 +7,10 @@
--surface-color: #ffffff;
--border-color: rgba(0, 0, 0, 0.05);
--shadow-color: rgba(0, 0, 0, 0.05);
--font-sans: 'Outfit', sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--transition-spring: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
--transition-smooth: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
+68
View File
@@ -0,0 +1,68 @@
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'node:child_process';
// Get staged files from git
const stagedFiles = execSync('git diff --cached --name-only --diff-filter=ACMR')
.toString()
.split('\n')
.filter(Boolean);
// Filter to only new or modified posts
const postFiles = stagedFiles.filter(
(file) => file.startsWith('content/posts/') && file.endsWith('.md') && !file.endsWith('_index.md'),
);
if (postFiles.length === 0) {
process.exit(0);
}
let hasError = false;
for (const file of postFiles) {
const basename = path.basename(file, '.md');
// 1. Slug validation (kebab-case: lowercase letters, numbers, and hyphens)
if (!/^[a-z0-9\-]+$/.test(basename)) {
console.error(`[ERROR] in ${file}:`);
console.error(` Filename must be kebab-case (e.g. 'my-cool-post.md').`);
hasError = true;
}
// 2. Front-matter validation
const content = fs.readFileSync(file, 'utf8');
// Zola uses +++ for TOML front-matter
const frontMatterMatch = content.match(/^\+\+\+([\s\S]*?)\+\+\+/);
if (!frontMatterMatch) {
console.error(`[ERROR] in ${file}:`);
console.error(` Missing TOML front-matter (must be enclosed in +++).`);
hasError = true;
continue;
}
const frontMatter = frontMatterMatch[1];
const hasTitle = /^title\s*=\s*".+"/m.test(frontMatter);
const hasDate = /^date\s*=\s*\d{4}-\d{2}-\d{2}/m.test(frontMatter);
if (!hasTitle) {
console.error(`[ERROR] in ${file}:`);
console.error(` Missing 'title' field in front-matter (e.g. title = "My Post").`);
hasError = true;
}
if (!hasDate) {
console.error(`[ERROR] in ${file}:`);
console.error(` Missing 'date' field in front-matter (e.g. date = 2024-01-01).`);
hasError = true;
}
}
if (hasError) {
console.error('\n[FATAL] Commit rejected due to validation errors. Please fix the issues above and try again.\n');
process.exit(1);
} else {
console.log('[SUCCESS] Post validation passed!');
}
File diff suppressed because one or more lines are too long
+1128 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+41 -39
View File
@@ -1,50 +1,52 @@
document.addEventListener('DOMContentLoaded', () => {
// Select all <pre> elements that contain code blocks
const preBlocks = document.querySelectorAll('pre');
// Select all <pre> elements that contain code blocks
const preBlocks = document.querySelectorAll('pre');
const copyIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>';
const checkIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>';
const copyIcon =
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>';
const checkIcon =
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>';
preBlocks.forEach((pre) => {
// Create the copy button
const button = document.createElement('button');
button.innerHTML = copyIcon;
button.className = 'copy-code-btn';
button.setAttribute('aria-label', 'Copy code');
button.setAttribute('title', 'Copy code');
preBlocks.forEach((pre) => {
// Create the copy button
const button = document.createElement('button');
button.innerHTML = copyIcon;
button.className = 'copy-code-btn';
button.setAttribute('aria-label', 'Copy code');
button.setAttribute('title', 'Copy code');
// Add the button to the <pre> block
// Assuming pre is position relative in CSS
pre.appendChild(button);
// Add the button to the <pre> block
// Assuming pre is position relative in CSS
pre.appendChild(button);
button.addEventListener('click', async () => {
// Find the <code> element within the <pre>
const code = pre.querySelector('code');
if (!code) return;
button.addEventListener('click', async () => {
// Find the <code> element within the <pre>
const code = pre.querySelector('code');
if (!code) return;
// Get the text content, removing any leading/trailing whitespace
const text = code.innerText;
// Get the text content, removing any leading/trailing whitespace
const text = code.innerText;
try {
// Wait for the clipboard API
await navigator.clipboard.writeText(text);
try {
// Wait for the clipboard API
await navigator.clipboard.writeText(text);
// Add visual feedback
button.innerHTML = checkIcon;
button.classList.add('copied');
button.setAttribute('aria-label', 'Copied');
button.setAttribute('title', 'Copied!');
// Add visual feedback
button.innerHTML = checkIcon;
button.classList.add('copied');
button.setAttribute('aria-label', 'Copied');
button.setAttribute('title', 'Copied!');
// Revert back after 2 seconds
setTimeout(() => {
button.innerHTML = copyIcon;
button.classList.remove('copied');
button.setAttribute('aria-label', 'Copy code');
button.setAttribute('title', 'Copy code');
}, 2000);
} catch (err) {
console.error('Failed to copy code: ', err);
}
});
// Revert back after 2 seconds
setTimeout(() => {
button.innerHTML = copyIcon;
button.classList.remove('copied');
button.setAttribute('aria-label', 'Copy code');
button.setAttribute('title', 'Copy code');
}, 2000);
} catch (err) {
console.error('Failed to copy code: ', err);
}
});
});
});
+170 -1
View File
@@ -1 +1,170 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,(function(e){return function(){"use strict";var t={771:function(t){t.exports=e}},r={};function n(e){var i=r[e];if(void 0!==i)return i.exports;var a=r[e]={exports:{}};return t[e](a,a.exports,n),a.exports}n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var i={};return function(){n.d(i,{default:function(){return s}});var e=n(771),t=n.n(e),r=function(e,t,r){for(var n=r,i=0,a=e.length;n<t.length;){var o=t[n];if(i<=0&&t.slice(n,n+a)===e)return n;"\\"===o?n++:"{"===o?i++:"}"===o&&i--,n++}return-1},a=/^\\begin{/,o=function(e,t){for(var n,i=[],o=new RegExp("("+t.map((function(e){return e.left.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")})).join("|")+")");-1!==(n=e.search(o));){n>0&&(i.push({type:"text",data:e.slice(0,n)}),e=e.slice(n));var l=t.findIndex((function(t){return e.startsWith(t.left)}));if(-1===(n=r(t[l].right,e,t[l].left.length)))break;var d=e.slice(0,n+t[l].right.length),s=a.test(d)?d:e.slice(t[l].left.length,n);i.push({type:"math",data:s,rawData:d,display:t[l].display}),e=e.slice(n+t[l].right.length)}return""!==e&&i.push({type:"text",data:e}),i},l=function(e,r){var n=o(e,r.delimiters);if(1===n.length&&"text"===n[0].type)return null;for(var i=document.createDocumentFragment(),a=0;a<n.length;a++)if("text"===n[a].type)i.appendChild(document.createTextNode(n[a].data));else{var l=document.createElement("span"),d=n[a].data;r.displayMode=n[a].display;try{r.preProcess&&(d=r.preProcess(d)),t().render(d,l,r)}catch(e){if(!(e instanceof t().ParseError))throw e;r.errorCallback("KaTeX auto-render: Failed to parse `"+n[a].data+"` with ",e),i.appendChild(document.createTextNode(n[a].rawData));continue}i.appendChild(l)}return i},d=function e(t,r){for(var n=0;n<t.childNodes.length;n++){var i=t.childNodes[n];if(3===i.nodeType){for(var a=i.textContent,o=i.nextSibling,d=0;o&&o.nodeType===Node.TEXT_NODE;)a+=o.textContent,o=o.nextSibling,d++;var s=l(a,r);if(s){for(var f=0;f<d;f++)i.nextSibling.remove();n+=s.childNodes.length-1,t.replaceChild(s,i)}else n+=d}else 1===i.nodeType&&function(){var t=" "+i.className+" ";-1===r.ignoredTags.indexOf(i.nodeName.toLowerCase())&&r.ignoredClasses.every((function(e){return-1===t.indexOf(" "+e+" ")}))&&e(i,r)}()}},s=function(e,t){if(!e)throw new Error("No element provided to render");var r={};for(var n in t)t.hasOwnProperty(n)&&(r[n]=t[n]);r.delimiters=r.delimiters||[{left:"$$",right:"$$",display:!0},{left:"\\(",right:"\\)",display:!1},{left:"\\begin{equation}",right:"\\end{equation}",display:!0},{left:"\\begin{align}",right:"\\end{align}",display:!0},{left:"\\begin{alignat}",right:"\\end{alignat}",display:!0},{left:"\\begin{gather}",right:"\\end{gather}",display:!0},{left:"\\begin{CD}",right:"\\end{CD}",display:!0},{left:"\\[",right:"\\]",display:!0}],r.ignoredTags=r.ignoredTags||["script","noscript","style","textarea","pre","code","option"],r.ignoredClasses=r.ignoredClasses||[],r.errorCallback=r.errorCallback||console.error,r.macros=r.macros||{},d(e,r)}}(),i=i.default}()}));
!(function (e, t) {
'object' == typeof exports && 'object' == typeof module
? (module.exports = t(require('katex')))
: 'function' == typeof define && define.amd
? define(['katex'], t)
: 'object' == typeof exports
? (exports.renderMathInElement = t(require('katex')))
: (e.renderMathInElement = t(e.katex));
})('undefined' != typeof self ? self : this, function (e) {
return (function () {
'use strict';
var t = {
771: function (t) {
t.exports = e;
},
},
r = {};
function n(e) {
var i = r[e];
if (void 0 !== i) return i.exports;
var a = (r[e] = { exports: {} });
return (t[e](a, a.exports, n), a.exports);
}
((n.n = function (e) {
var t =
e && e.__esModule
? function () {
return e.default;
}
: function () {
return e;
};
return (n.d(t, { a: t }), t);
}),
(n.d = function (e, t) {
for (var r in t)
n.o(t, r) && !n.o(e, r) && Object.defineProperty(e, r, { enumerable: !0, get: t[r] });
}),
(n.o = function (e, t) {
return Object.prototype.hasOwnProperty.call(e, t);
}));
var i = {};
return (
(function () {
n.d(i, {
default: function () {
return s;
},
});
var e = n(771),
t = n.n(e),
r = function (e, t, r) {
for (var n = r, i = 0, a = e.length; n < t.length; ) {
var o = t[n];
if (i <= 0 && t.slice(n, n + a) === e) return n;
('\\' === o ? n++ : '{' === o ? i++ : '}' === o && i--, n++);
}
return -1;
},
a = /^\\begin{/,
o = function (e, t) {
for (
var n,
i = [],
o = new RegExp(
'(' +
t
.map(function (e) {
return e.left.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
})
.join('|') +
')',
);
-1 !== (n = e.search(o));
) {
n > 0 && (i.push({ type: 'text', data: e.slice(0, n) }), (e = e.slice(n)));
var l = t.findIndex(function (t) {
return e.startsWith(t.left);
});
if (-1 === (n = r(t[l].right, e, t[l].left.length))) break;
var d = e.slice(0, n + t[l].right.length),
s = a.test(d) ? d : e.slice(t[l].left.length, n);
(i.push({ type: 'math', data: s, rawData: d, display: t[l].display }),
(e = e.slice(n + t[l].right.length)));
}
return ('' !== e && i.push({ type: 'text', data: e }), i);
},
l = function (e, r) {
var n = o(e, r.delimiters);
if (1 === n.length && 'text' === n[0].type) return null;
for (var i = document.createDocumentFragment(), a = 0; a < n.length; a++)
if ('text' === n[a].type) i.appendChild(document.createTextNode(n[a].data));
else {
var l = document.createElement('span'),
d = n[a].data;
r.displayMode = n[a].display;
try {
(r.preProcess && (d = r.preProcess(d)), t().render(d, l, r));
} catch (e) {
if (!(e instanceof t().ParseError)) throw e;
(r.errorCallback(
'KaTeX auto-render: Failed to parse `' + n[a].data + '` with ',
e,
),
i.appendChild(document.createTextNode(n[a].rawData)));
continue;
}
i.appendChild(l);
}
return i;
},
d = function e(t, r) {
for (var n = 0; n < t.childNodes.length; n++) {
var i = t.childNodes[n];
if (3 === i.nodeType) {
for (
var a = i.textContent, o = i.nextSibling, d = 0;
o && o.nodeType === Node.TEXT_NODE;
)
((a += o.textContent), (o = o.nextSibling), d++);
var s = l(a, r);
if (s) {
for (var f = 0; f < d; f++) i.nextSibling.remove();
((n += s.childNodes.length - 1), t.replaceChild(s, i));
} else n += d;
} else
1 === i.nodeType &&
(function () {
var t = ' ' + i.className + ' ';
-1 === r.ignoredTags.indexOf(i.nodeName.toLowerCase()) &&
r.ignoredClasses.every(function (e) {
return -1 === t.indexOf(' ' + e + ' ');
}) &&
e(i, r);
})();
}
},
s = function (e, t) {
if (!e) throw new Error('No element provided to render');
var r = {};
for (var n in t) t.hasOwnProperty(n) && (r[n] = t[n]);
((r.delimiters = r.delimiters || [
{ left: '$$', right: '$$', display: !0 },
{ left: '\\(', right: '\\)', display: !1 },
{ left: '\\begin{equation}', right: '\\end{equation}', display: !0 },
{ left: '\\begin{align}', right: '\\end{align}', display: !0 },
{ left: '\\begin{alignat}', right: '\\end{alignat}', display: !0 },
{ left: '\\begin{gather}', right: '\\end{gather}', display: !0 },
{ left: '\\begin{CD}', right: '\\end{CD}', display: !0 },
{ left: '\\[', right: '\\]', display: !0 },
]),
(r.ignoredTags = r.ignoredTags || [
'script',
'noscript',
'style',
'textarea',
'pre',
'code',
'option',
]),
(r.ignoredClasses = r.ignoredClasses || []),
(r.errorCallback = r.errorCallback || console.error),
(r.macros = r.macros || {}),
d(e, r));
};
})(),
(i = i.default)
);
})();
});
+11562 -1
View File
File diff suppressed because one or more lines are too long
+190510 -407
View File
File diff suppressed because one or more lines are too long
+53 -51
View File
@@ -1,61 +1,63 @@
document.addEventListener('DOMContentLoaded', () => {
const themeToggleBtn = document.getElementById('theme-toggle');
const htmlElement = document.documentElement;
const themeToggleBtn = document.getElementById('theme-toggle');
const htmlElement = document.documentElement;
const applyTheme = (theme) => {
if (theme === 'dark') {
htmlElement.classList.add('dark');
} else {
htmlElement.classList.remove('dark');
}
localStorage.setItem('theme', theme);
};
// Check local storage for theme preference
const currentTheme = localStorage.getItem('theme');
if (currentTheme === 'dark' || currentTheme === 'light') {
applyTheme(currentTheme);
const applyTheme = (theme) => {
if (theme === 'dark') {
htmlElement.classList.add('dark');
} else {
// Fallback to system preference
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
applyTheme(prefersDarkScheme.matches ? 'dark' : 'light');
htmlElement.classList.remove('dark');
}
localStorage.setItem('theme', theme);
};
// Toggle theme on click
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', () => {
const nextTheme = htmlElement.classList.contains('dark') ? 'light' : 'dark';
applyTheme(nextTheme);
});
}
// Check local storage for theme preference
const currentTheme = localStorage.getItem('theme');
if (currentTheme === 'dark' || currentTheme === 'light') {
applyTheme(currentTheme);
} else {
// Fallback to system preference
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
applyTheme(prefersDarkScheme.matches ? 'dark' : 'light');
}
// Mobile nav toggle
const navToggle = document.getElementById('nav-toggle');
const navLinks = document.getElementById('nav-links');
// Toggle theme on click
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', () => {
const nextTheme = htmlElement.classList.contains('dark') ? 'light' : 'dark';
applyTheme(nextTheme);
});
}
if (navToggle && navLinks) {
navToggle.addEventListener('click', () => {
const isOpen = navLinks.classList.toggle('is-open');
navToggle.setAttribute('aria-expanded', isOpen);
});
// Mobile nav toggle
const navToggle = document.getElementById('nav-toggle');
const navLinks = document.getElementById('nav-links');
// Close nav when a link is clicked
navLinks.querySelectorAll('.nav-item').forEach(link => {
link.addEventListener('click', () => {
navLinks.classList.remove('is-open');
navToggle.setAttribute('aria-expanded', 'false');
});
});
}
if (navToggle && navLinks) {
navToggle.addEventListener('click', () => {
const isOpen = navLinks.classList.toggle('is-open');
navToggle.setAttribute('aria-expanded', isOpen);
});
// Sync cookieconsent dark mode reactively with the site theme
const syncCCTheme = () => {
const ccMain = document.getElementById('cc-main');
if (!ccMain) return;
ccMain.classList.toggle('cc--darkmode', htmlElement.classList.contains('dark'));
};
new MutationObserver(syncCCTheme)
.observe(htmlElement, { attributes: true, attributeFilter: ['class'] });
// Initial sync in case banner is already mounted
setTimeout(syncCCTheme, 100);
// Close nav when a link is clicked
navLinks.querySelectorAll('.nav-item').forEach((link) => {
link.addEventListener('click', () => {
navLinks.classList.remove('is-open');
navToggle.setAttribute('aria-expanded', 'false');
});
});
}
// Sync cookieconsent dark mode reactively with the site theme
const syncCCTheme = () => {
const ccMain = document.getElementById('cc-main');
if (!ccMain) return;
ccMain.classList.toggle('cc--darkmode', htmlElement.classList.contains('dark'));
};
new MutationObserver(syncCCTheme).observe(htmlElement, {
attributes: true,
attributeFilter: ['class'],
});
// Initial sync in case banner is already mounted
setTimeout(syncCCTheme, 100);
});
+45 -23
View File
@@ -1,29 +1,51 @@
{% extends "base.html" %}
{% block title %}{{ section.title }} | {{ config.title }}{% endblock title %}
{% block content %}
{% extends "base.html" %} {% block title %}{{ section.title }} | {{ config.title }}{% endblock title
%} {% block content %}
<div class="post-detail">
<header class="post-header" style="margin-bottom: 3rem;">
<h1 class="post-title" style="font-size: clamp(2.5rem, 5vw, 4.5rem);">Archive</h1>
</header>
<header class="post-header" style="margin-bottom: 3rem">
<h1 class="post-title" style="font-size: clamp(2.5rem, 5vw, 4.5rem)">Archive</h1>
</header>
<div class="archive-list" style="max-width: 65ch; margin: 0 auto;">
{% set section = get_section(path="posts/_index.md") %}
{% for year, posts in section.pages | sort(attribute="date") | reverse | group_by(attribute="year") %}
<div class="archive" style="margin-bottom: 2rem;">
<h2 style="font-size: 1.5rem; color: var(--text-color); margin-bottom: 1rem; margin-top: 2rem;">{{ year }}</h2>
<ul style="list-style-type: none; padding-left: 1rem; margin: 0; display: flex; flex-direction: column; gap: 0.75rem;">
{% for post in posts %}
<li class="hover-scale" style="display: flex; gap: 1rem; align-items: baseline;">
<time datetime="{{ post.date }}" style="color: var(--text-muted); font-size: 0.95rem; white-space: nowrap; font-family: var(--font-mono);">{{ post.date | date(format="%d-%b") }}</time>
<a href="{{ post.permalink }}" style="color: var(--accent-color); font-weight: 500; font-size: 1.05rem;">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
</div>
<div class="archive-list" style="max-width: 65ch; margin: 0 auto">
{% set section = get_section(path="posts/_index.md") %} {% for year, posts in section.pages |
sort(attribute="date") | reverse | group_by(attribute="year") %}
<div class="archive" style="margin-bottom: 2rem">
<h2
style="font-size: 1.5rem; color: var(--text-color); margin-bottom: 1rem; margin-top: 2rem"
>
{{ year }}
</h2>
<ul
style="
list-style-type: none;
padding-left: 1rem;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.75rem;
"
>
{% for post in posts %}
<li class="hover-scale" style="display: flex; gap: 1rem; align-items: baseline">
<time
datetime="{{ post.date }}"
style="
color: var(--text-muted);
font-size: 0.95rem;
white-space: nowrap;
font-family: var(--font-mono);
"
>{{ post.date | date(format="%d-%b") }}</time
>
<a
href="{{ post.permalink }}"
style="color: var(--accent-color); font-weight: 500; font-size: 1.05rem"
>{{ post.title }}</a
>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
</div>
{% endblock content %}
+37 -40
View File
@@ -1,47 +1,44 @@
{% extends "base.html" %}
{% block canonical %}{{ config.base_url }}/{% endblock canonical %}
{% block og_url %}{{ config.base_url }}/{% endblock og_url %}
{% block head %}
{% extends "base.html" %} {% block canonical %}{{ config.base_url }}/{% endblock canonical %} {%
block og_url %}{{ config.base_url }}/{% endblock og_url %} {% block head %}
<!-- SEO: Blog (Homepage) JSON-LD -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Blog",
"@id": "{{ config.base_url }}/#blog",
"name": "{{ config.title }}",
"description": "{{ config.description }}",
"url": "{{ config.base_url }}/",
"inLanguage": "en-US",
"author": {
"@type": "Person",
"@id": "{{ config.base_url }}/#author"
},
"blogPost": [
{% set section = get_section(path="posts/_index.md") %}
{% for page in section.pages %}
{
"@type": "BlogPosting",
"@id": "{{ page.permalink }}#article",
"headline": {{ page.title | json_encode }},
"url": "{{ page.permalink }}"
{% if page.date %},"datePublished": "{{ page.date | date(format="%+") }}"{% endif %}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
{
"@context": "https://schema.org",
"@type": "Blog",
"@id": "{{ config.base_url }}/#blog",
"name": "{{ config.title }}",
"description": "{{ config.description }}",
"url": "{{ config.base_url }}/",
"inLanguage": "en-US",
"author": {
"@type": "Person",
"@id": "{{ config.base_url }}/#author"
},
"blogPost": [
{% set section = get_section(path="posts/_index.md") %}
{% for page in section.pages %}
{
"@type": "BlogPosting",
"@id": "{{ page.permalink }}#article",
"headline": {{ page.title | json_encode }},
"url": "{{ page.permalink }}"
{% if page.date %},"datePublished": "{{ page.date | date(format="%+") }}"{% endif %}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
</script>
{% endblock head %}
{% endblock head %} {% block content %} {% include "partials/profile.html" %}
{% block content %}
{% include "partials/profile.html" %}
<div
class="post-feed-list"
style="max-width: 65ch; margin: 0 auto; display: flex; flex-direction: column; gap: 2.5rem"
>
{% set section = get_section(path="posts/_index.md") %} {% for page in section.pages | slice(end=3) %} {% include
"partials/post-card.html" %} {% endfor %}
</div>
<div class="post-feed-list" style="max-width: 65ch; margin: 0 auto; display: flex; flex-direction: column; gap: 2.5rem;">
{% set section = get_section(path="posts/_index.md") %}
{% for page in section.pages %}
{% include "partials/post-card.html" %}
{% endfor %}
<div class="view-all-posts">
<a href="/posts" class="btn-view-all">View all posts</a>
</div>
{% endblock content %}
+7 -5
View File
@@ -1,8 +1,10 @@
<footer class="site-footer">
<div class="footer-content">
<p>&copy; {{ now() | date(format="%Y") }} {{ config.extra.author | default(value="Author") }}</p>
<div class="social-links">
<!-- Social Links -->
</div>
<div class="footer-content">
<p>
&copy; {{ now() | date(format="%Y") }} {{ config.extra.author | default(value="Author") }}
</p>
<div class="social-links">
<!-- Social Links -->
</div>
</div>
</footer>
+63 -30
View File
@@ -1,32 +1,65 @@
<article class="feed-item" style="border-bottom: 1px dashed var(--border-color); padding-bottom: 2rem;">
<h2 class="item-title" style="font-size: 1.25rem; margin-bottom: 0.5rem; font-weight: 600;">
<a href="{{ page.permalink }}">{{ page.title }}</a>
</h2>
<div class="item-meta" style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1rem; display: flex; align-items: center; gap: 0.25rem;">
<span>{{ page.date | date(format="%Y-%m-%d") }}</span>
</div>
<div class="item-snippet" style="font-size: 0.95rem; color: var(--text-color); margin-bottom: 1rem; opacity: 0.9;">
{% if page.description %}
<p style="margin: 0;">{{ page.description }}</p>
{% else %}
<p style="margin: 0;">{{ page.content | striptags | truncate(length=120) | safe }}</p>
{% endif %}
</div>
<div class="item-footer" style="display: flex; justify-content: space-between; align-items: center; font-size: 0.85rem;">
<a href="{{ page.permalink }}" style="color: var(--accent-color);">Read more...</a>
{% if page.taxonomies.tags %}
<div class="item-tags" style="display: flex; align-items: center; gap: 0.5rem; color: var(--text-muted);">
<svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="2" fill="none"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>
<span>
{% for tag in page.taxonomies.tags %}
{{ tag }}{% if not loop.last %}, {% endif %}
{% endfor %}
</span>
</div>
{% endif %}
<article
class="feed-item"
style="border-bottom: 1px dashed var(--border-color); padding-bottom: 2rem"
>
<h2 class="item-title" style="font-size: 1.25rem; margin-bottom: 0.5rem; font-weight: 600">
<a href="{{ page.permalink }}">{{ page.title }}</a>
</h2>
<div
class="item-meta"
style="
font-size: 0.85rem;
color: var(--text-muted);
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.25rem;
"
>
<span>{{ page.date | date(format="%Y-%m-%d") }}</span>
</div>
<div
class="item-snippet"
style="font-size: 0.95rem; color: var(--text-color); margin-bottom: 1rem; opacity: 0.9"
>
{% if page.description %}
<p style="margin: 0">{{ page.description }}</p>
{% else %}
<p style="margin: 0">{{ page.content | striptags | truncate(length=120) | safe }}</p>
{% endif %}
</div>
<div
class="item-footer"
style="display: flex; justify-content: space-between; align-items: center; font-size: 0.85rem"
>
<a href="{{ page.permalink }}" style="color: var(--accent-color)">Read more...</a>
{% if page.taxonomies.tags %}
<div
class="item-tags"
style="display: flex; align-items: center; gap: 0.5rem; color: var(--text-muted)"
>
<svg
viewBox="0 0 24 24"
width="14"
height="14"
stroke="currentColor"
stroke-width="2"
fill="none"
>
<path
d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"
></path>
<line x1="7" y1="7" x2="7.01" y2="7"></line>
</svg>
<span>
{% for tag in page.taxonomies.tags %} {{ tag }}{% if not loop.last %}, {% endif %} {% endfor
%}
</span>
</div>
{% endif %}
</div>
</article>
+11 -12
View File
@@ -1,16 +1,15 @@
{% if page.lower or page.higher %}
<nav class="post-nav" aria-label="Post navigation">
{% if page.higher %}
<a class="post-nav-link post-nav-next hover-scale" href="{{ page.higher.permalink }}">
<span class="post-nav-label">&larr; Next</span>
<span class="post-nav-title">{{ page.higher.title }}</span>
</a>
{% endif %}
{% if page.lower %}
<a class="post-nav-link post-nav-prev hover-scale" href="{{ page.lower.permalink }}">
<span class="post-nav-label">Prev &rarr;</span>
<span class="post-nav-title">{{ page.lower.title }}</span>
</a>
{% endif %}
{% if page.higher %}
<a class="post-nav-link post-nav-next hover-scale" href="{{ page.higher.permalink }}">
<span class="post-nav-label">&larr; Next</span>
<span class="post-nav-title">{{ page.higher.title }}</span>
</a>
{% endif %} {% if page.lower %}
<a class="post-nav-link post-nav-prev hover-scale" href="{{ page.lower.permalink }}">
<span class="post-nav-label">Prev &rarr;</span>
<span class="post-nav-title">{{ page.lower.title }}</span>
</a>
{% endif %}
</nav>
{% endif %}
+122 -19
View File
@@ -1,21 +1,124 @@
<div class="social-share">
<span class="share-label">Share:</span>
<a href="https://twitter.com/intent/tweet?text={{ page.title | urlencode }}&url={{ page.permalink | urlencode }}" target="_blank" rel="noopener noreferrer" class="share-btn x" aria-label="Share on X">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"/></svg>
</a>
<a href="https://bsky.app/intent/compose?text={{ page.title | urlencode }}%20{{ page.permalink | urlencode }}" target="_blank" rel="noopener noreferrer" class="share-btn bluesky" aria-label="Share on Bluesky">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"/></svg>
</a>
<a href="https://www.linkedin.com/shareArticle?mini=true&url={{ page.permalink | urlencode }}&title={{ page.title | urlencode }}" target="_blank" rel="noopener noreferrer" class="share-btn linkedin" aria-label="Share on LinkedIn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>
</a>
<a href="https://reddit.com/submit?url={{ page.permalink | urlencode }}&title={{ page.title | urlencode }}" target="_blank" rel="noopener noreferrer" class="share-btn reddit" aria-label="Share on Reddit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M12 0C5.373 0 0 5.373 0 12c0 3.314 1.343 6.314 3.515 8.485l-2.286 2.286C.775 23.225 1.097 24 1.738 24H12c6.627 0 12-5.373 12-12S18.627 0 12 0Zm4.388 3.199c1.104 0 1.999.895 1.999 1.999 0 1.105-.895 2-1.999 2-.946 0-1.739-.657-1.947-1.539v.002c-1.147.162-2.032 1.15-2.032 2.341v.007c1.776.067 3.4.567 4.686 1.363.473-.363 1.064-.58 1.707-.58 1.547 0 2.802 1.254 2.802 2.802 0 1.117-.655 2.081-1.601 2.531-.088 3.256-3.637 5.876-7.997 5.876-4.361 0-7.905-2.617-7.998-5.87-.954-.447-1.614-1.415-1.614-2.538 0-1.548 1.255-2.802 2.803-2.802.645 0 1.239.218 1.712.585 1.275-.79 2.881-1.291 4.64-1.365v-.01c0-1.663 1.263-3.034 2.88-3.207.188-.911.993-1.595 1.959-1.595Zm-8.085 8.376c-.784 0-1.459.78-1.506 1.797-.047 1.016.64 1.429 1.426 1.429.786 0 1.371-.369 1.418-1.385.047-1.017-.553-1.841-1.338-1.841Zm7.406 0c-.786 0-1.385.824-1.338 1.841.047 1.017.634 1.385 1.418 1.385.785 0 1.473-.413 1.426-1.429-.046-1.017-.721-1.797-1.506-1.797Zm-3.703 4.013c-.974 0-1.907.048-2.77.135-.147.015-.241.168-.183.305.483 1.154 1.622 1.964 2.953 1.964 1.33 0 2.47-.81 2.953-1.964.057-.137-.037-.29-.184-.305-.863-.087-1.795-.135-2.769-.135Z"/></svg>
</a>
<a href="https://news.ycombinator.com/submitlink?u={{ page.permalink | urlencode }}&t={{ page.title | urlencode }}" target="_blank" rel="noopener noreferrer" class="share-btn hackernews" aria-label="Share on Hacker News">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M0 24V0h24v24H0zM6.95 5.55L11 13v5h2v-5l4.05-7.45h-2.25L12 10.65 9.2 5.55H6.95z"/></svg>
</a>
<a href="mailto:?subject={{ page.title | urlencode }}&body={{ page.permalink | urlencode }}" target="_blank" rel="noopener noreferrer" class="share-btn email" aria-label="Share via Email">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
</a>
<span class="share-label">Share:</span>
<a
href="https://twitter.com/intent/tweet?text={{ page.title | urlencode }}&url={{ page.permalink | urlencode }}"
target="_blank"
rel="noopener noreferrer"
class="share-btn x"
aria-label="Share on X"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="18"
height="18"
fill="currentColor"
>
<path
d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"
/>
</svg>
</a>
<a
href="https://bsky.app/intent/compose?text={{ page.title | urlencode }}%20{{ page.permalink | urlencode }}"
target="_blank"
rel="noopener noreferrer"
class="share-btn bluesky"
aria-label="Share on Bluesky"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="18"
height="18"
fill="currentColor"
>
<path
d="M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"
/>
</svg>
</a>
<a
href="https://www.linkedin.com/shareArticle?mini=true&url={{ page.permalink | urlencode }}&title={{ page.title | urlencode }}"
target="_blank"
rel="noopener noreferrer"
class="share-btn linkedin"
aria-label="Share on LinkedIn"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="18"
height="18"
stroke="currentColor"
stroke-width="2"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"
></path>
<rect x="2" y="9" width="4" height="12"></rect>
<circle cx="4" cy="4" r="2"></circle>
</svg>
</a>
<a
href="https://reddit.com/submit?url={{ page.permalink | urlencode }}&title={{ page.title | urlencode }}"
target="_blank"
rel="noopener noreferrer"
class="share-btn reddit"
aria-label="Share on Reddit"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="20"
height="20"
fill="currentColor"
>
<path
d="M12 0C5.373 0 0 5.373 0 12c0 3.314 1.343 6.314 3.515 8.485l-2.286 2.286C.775 23.225 1.097 24 1.738 24H12c6.627 0 12-5.373 12-12S18.627 0 12 0Zm4.388 3.199c1.104 0 1.999.895 1.999 1.999 0 1.105-.895 2-1.999 2-.946 0-1.739-.657-1.947-1.539v.002c-1.147.162-2.032 1.15-2.032 2.341v.007c1.776.067 3.4.567 4.686 1.363.473-.363 1.064-.58 1.707-.58 1.547 0 2.802 1.254 2.802 2.802 0 1.117-.655 2.081-1.601 2.531-.088 3.256-3.637 5.876-7.997 5.876-4.361 0-7.905-2.617-7.998-5.87-.954-.447-1.614-1.415-1.614-2.538 0-1.548 1.255-2.802 2.803-2.802.645 0 1.239.218 1.712.585 1.275-.79 2.881-1.291 4.64-1.365v-.01c0-1.663 1.263-3.034 2.88-3.207.188-.911.993-1.595 1.959-1.595Zm-8.085 8.376c-.784 0-1.459.78-1.506 1.797-.047 1.016.64 1.429 1.426 1.429.786 0 1.371-.369 1.418-1.385.047-1.017-.553-1.841-1.338-1.841Zm7.406 0c-.786 0-1.385.824-1.338 1.841.047 1.017.634 1.385 1.418 1.385.785 0 1.473-.413 1.426-1.429-.046-1.017-.721-1.797-1.506-1.797Zm-3.703 4.013c-.974 0-1.907.048-2.77.135-.147.015-.241.168-.183.305.483 1.154 1.622 1.964 2.953 1.964 1.33 0 2.47-.81 2.953-1.964.057-.137-.037-.29-.184-.305-.863-.087-1.795-.135-2.769-.135Z"
/>
</svg>
</a>
<a
href="https://news.ycombinator.com/submitlink?u={{ page.permalink | urlencode }}&t={{ page.title | urlencode }}"
target="_blank"
rel="noopener noreferrer"
class="share-btn hackernews"
aria-label="Share on Hacker News"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="18"
height="18"
fill="currentColor"
>
<path d="M0 24V0h24v24H0zM6.95 5.55L11 13v5h2v-5l4.05-7.45h-2.25L12 10.65 9.2 5.55H6.95z" />
</svg>
</a>
<a
href="mailto:?subject={{ page.title | urlencode }}&body={{ page.permalink | urlencode }}"
target="_blank"
rel="noopener noreferrer"
class="share-btn email"
aria-label="Share via Email"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="18"
height="18"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
<polyline points="22,6 12,13 2,6"></polyline>
</svg>
</a>
</div>
+13 -13
View File
@@ -1,19 +1,19 @@
{% if page.toc %}
<details class="post-toc">
<summary>Table of Contents</summary>
<ul>
<summary>Table of Contents</summary>
<ul>
{% for h1 in page.toc %}
<li>
<a href="{{ h1.permalink | safe }}">{{ h1.title }}</a>
{% if h1.children %}
<ul>
{% for h2 in h1.children %}
<li><a href="{{ h2.permalink | safe }}">{{ h2.title }}</a></li>
{% endfor %}
</ul>
{% endif %}
</li>
<li>
<a href="{{ h1.permalink | safe }}">{{ h1.title }}</a>
{% if h1.children %}
<ul>
{% for h2 in h1.children %}
<li><a href="{{ h2.permalink | safe }}">{{ h2.title }}</a></li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</ul>
</details>
{% endif %}
+152 -122
View File
@@ -1,133 +1,163 @@
{% extends "base.html" %}
{% block title %}Posts{% if paginator.current_index > 1 %} — Page {{ paginator.current_index }}{% endif %} | {{ config.title }}{% endblock title %}
{% block description %}Browse all posts on {{ config.title }} — binary exploitation, reverse engineering, and offensive security.{% endblock description %}
{% block canonical %}{% if paginator.current_index == 1 %}{{ config.base_url }}/posts/{% else %}{{ paginator.first }}page/{{ paginator.current_index }}/{% endif %}{% endblock canonical %}
{% block og_title %}Posts{% if paginator.current_index > 1 %} — Page {{ paginator.current_index }}{% endif %} | {{ config.title }}{% endblock og_title %}
{% block og_description %}Browse all posts on {{ config.title }} — binary exploitation, reverse engineering, and offensive security.{% endblock og_description %}
{% block og_url %}{% if paginator.current_index == 1 %}{{ config.base_url }}/posts/{% else %}{{ paginator.first }}page/{{ paginator.current_index }}/{% endif %}{% endblock og_url %}
{% block head %}
{% extends "base.html" %} {% block title %}Posts{% if paginator.current_index > 1 %} — Page {{
paginator.current_index }}{% endif %} | {{ config.title }}{% endblock title %} {% block description
%}Browse all posts on {{ config.title }} — binary exploitation, reverse engineering, and offensive
security.{% endblock description %} {% block canonical %}{% if paginator.current_index == 1 %}{{
config.base_url }}/posts/{% else %}{{ paginator.first }}page/{{ paginator.current_index }}/{% endif
%}{% endblock canonical %} {% block og_title %}Posts{% if paginator.current_index > 1 %} — Page {{
paginator.current_index }}{% endif %} | {{ config.title }}{% endblock og_title %} {% block
og_description %}Browse all posts on {{ config.title }} — binary exploitation, reverse engineering,
and offensive security.{% endblock og_description %} {% block og_url %}{% if paginator.current_index
== 1 %}{{ config.base_url }}/posts/{% else %}{{ paginator.first }}page/{{ paginator.current_index
}}/{% endif %}{% endblock og_url %} {% block head %}
<!-- SEO: CollectionPage JSON-LD -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ config.base_url }}/posts/#collection",
"name": "Posts | {{ config.title }}",
"description": "Browse all posts on {{ config.title }}",
"url": "{{ config.base_url }}/posts/",
"inLanguage": "en-US",
"isPartOf": {
"@type": "WebSite",
"@id": "{{ config.base_url }}/#website"
},
"hasPart": [
{% for page in paginator.pages %}
{
"@type": "BlogPosting",
"@id": "{{ page.permalink }}#article",
"headline": {{ page.title | json_encode }},
"url": "{{ page.permalink }}"
{% if page.date %},"datePublished": "{{ page.date | date(format="%+") }}"{% endif %}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ config.base_url }}/posts/#collection",
"name": "Posts | {{ config.title }}",
"description": "Browse all posts on {{ config.title }}",
"url": "{{ config.base_url }}/posts/",
"inLanguage": "en-US",
"isPartOf": {
"@type": "WebSite",
"@id": "{{ config.base_url }}/#website"
},
"hasPart": [
{% for page in paginator.pages %}
{
"@type": "BlogPosting",
"@id": "{{ page.permalink }}#article",
"headline": {{ page.title | json_encode }},
"url": "{{ page.permalink }}"
{% if page.date %},"datePublished": "{{ page.date | date(format="%+") }}"{% endif %}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
</script>
<!-- Pagination link hints for crawlers -->
{% if paginator.previous %}<link rel="prev" href="{{ paginator.previous }}">{% endif %}
{% if paginator.next %}<link rel="next" href="{{ paginator.next }}">{% endif %}
{% endblock head %}
{% block content %}
{% if paginator.previous %}<link rel="prev" href="{{ paginator.previous }}" />{% endif %} {% if
paginator.next %}<link rel="next" href="{{ paginator.next }}" />{% endif %} {% endblock head %} {%
block content %}
<div class="feed-page">
<div class="hero" style="padding-bottom: 2rem;">
<div class="hero-content">
<h1 class="hero-title">Posts</h1>
<p class="hero-subtitle">Exploring binary exploitation, reverse engineering, and offensive security.</p>
</div>
<div class="hero" style="padding-bottom: 2rem">
<div class="hero-content">
<h1 class="hero-title">Posts</h1>
<p class="hero-subtitle">
Exploring binary exploitation, reverse engineering, and offensive security.
</p>
</div>
</div>
<div class="post-feed">
{% for page in paginator.pages %}
<article class="feed-item hover-scale">
<a href="{{ page.permalink }}" class="item-link">
<time datetime="{{ page.date }}" class="item-date"
>{{ page.date | date(format="%b %d, %Y") }}</time
>
<h2 class="item-title">{{ page.title }}</h2>
{% if page.description %}
<p class="item-desc">{{ page.description }}</p>
{% endif %}
</a>
</article>
{% endfor %}
</div>
{% if paginator.number_pagers > 1 %}
<nav class="pagination" aria-label="Posts pagination">
{# ← Previous #} {% if paginator.previous %}
<a class="pagination-arrow" href="{{ paginator.previous }}" aria-label="Previous page">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</a>
{% else %}
<span class="pagination-arrow is-disabled" aria-disabled="true">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</span>
{% endif %} {# Page numbers with smart windowing #}
<div class="pagination-pages">
{% set cur = paginator.current_index %} {% set total = paginator.number_pagers %} {% for i in
range(start=1, end=total+1) %} {% set show = false %} {# Always show first and last #} {% if i
== 1 or i == total %}{% set show = true %}{% endif %} {# Show window of 2 around current #} {%
if i >= cur - 2 and i <= cur + 2 %}{% set show = true %}{% endif %} {% if show %} {# Show
ellipsis before this page if there's a gap #} {% if i > 1 and i > cur - 2 and i != 2 %} {# We
need gap indicator — output ellipsis if prev shown page was not adjacent #} {% if i == cur - 2
and cur - 3 > 1 %}
<span class="pagination-ellipsis">…</span>
{% elif i == total and cur + 2 < total - 1 %}
<span class="pagination-ellipsis">…</span>
{% endif %} {% endif %} {% if i == cur %}
<span class="pagination-num is-current" aria-current="page">{{ i }}</span>
{% else %} {% if i == 1 %}
<a class="pagination-num" href="{{ paginator.first }}" aria-label="Page {{ i }}">{{ i }}</a>
{% elif i == total %}
<a class="pagination-num" href="{{ paginator.last }}" aria-label="Page {{ i }}">{{ i }}</a>
{% else %}
<a class="pagination-num" href="{{ paginator.first }}page/{{ i }}/" aria-label="Page {{ i }}"
>{{ i }}</a
>
{% endif %} {% endif %} {% endif %} {% endfor %}
</div>
<div class="post-feed">
{% for page in paginator.pages %}
<article class="feed-item hover-scale">
<a href="{{ page.permalink }}" class="item-link">
<time datetime="{{ page.date }}" class="item-date">{{ page.date | date(format="%b %d, %Y") }}</time>
<h2 class="item-title">{{ page.title }}</h2>
{% if page.description %}
<p class="item-desc">{{ page.description }}</p>
{% endif %}
</a>
</article>
{% endfor %}
</div>
{% if paginator.number_pagers > 1 %}
<nav class="pagination" aria-label="Posts pagination">
{# ← Previous #}
{% if paginator.previous %}
<a class="pagination-arrow" href="{{ paginator.previous }}" aria-label="Previous page">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
</a>
{% else %}
<span class="pagination-arrow is-disabled" aria-disabled="true">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
</span>
{% endif %}
{# Page numbers with smart windowing #}
<div class="pagination-pages">
{% set cur = paginator.current_index %}
{% set total = paginator.number_pagers %}
{% for i in range(start=1, end=total+1) %}
{% set show = false %}
{# Always show first and last #}
{% if i == 1 or i == total %}{% set show = true %}{% endif %}
{# Show window of 2 around current #}
{% if i >= cur - 2 and i <= cur + 2 %}{% set show = true %}{% endif %}
{% if show %}
{# Show ellipsis before this page if there's a gap #}
{% if i > 1 and i > cur - 2 and i != 2 %}
{# We need gap indicator — output ellipsis if prev shown page was not adjacent #}
{% if i == cur - 2 and cur - 3 > 1 %}
<span class="pagination-ellipsis">…</span>
{% elif i == total and cur + 2 < total - 1 %}
<span class="pagination-ellipsis">…</span>
{% endif %}
{% endif %}
{% if i == cur %}
<span class="pagination-num is-current" aria-current="page">{{ i }}</span>
{% else %}
{% if i == 1 %}
<a class="pagination-num" href="{{ paginator.first }}" aria-label="Page {{ i }}">{{ i }}</a>
{% elif i == total %}
<a class="pagination-num" href="{{ paginator.last }}" aria-label="Page {{ i }}">{{ i }}</a>
{% else %}
<a class="pagination-num" href="{{ paginator.first }}page/{{ i }}/" aria-label="Page {{ i }}">{{ i }}</a>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
</div>
{# Next → #}
{% if paginator.next %}
<a class="pagination-arrow" href="{{ paginator.next }}" aria-label="Next page">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
</a>
{% else %}
<span class="pagination-arrow is-disabled" aria-disabled="true">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
</span>
{% endif %}
</nav>
<p class="pagination-meta">Page {{ paginator.current_index }} of {{ paginator.number_pagers }}</p>
{# Next → #} {% if paginator.next %}
<a class="pagination-arrow" href="{{ paginator.next }}" aria-label="Next page">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</a>
{% else %}
<span class="pagination-arrow is-disabled" aria-disabled="true">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</span>
{% endif %}
</nav>
<p class="pagination-meta">Page {{ paginator.current_index }} of {{ paginator.number_pagers }}</p>
{% endif %}
</div>
{% endblock content %}
+51 -36
View File
@@ -1,43 +1,58 @@
{% extends "base.html" %}
{% block title %}Tags | {{ config.title }}{% endblock title %}
{% block description %}Browse all tags on {{ config.title }} — topics include binary exploitation, reverse engineering, and offensive security.{% endblock description %}
{% block canonical %}{{ config.base_url }}/tags/{% endblock canonical %}
{% block og_title %}Tags | {{ config.title }}{% endblock og_title %}
{% block og_description %}Browse all tags on {{ config.title }}{% endblock og_description %}
{% block og_url %}{{ config.base_url }}/tags/{% endblock og_url %}
{% block head %}
{% extends "base.html" %} {% block title %}Tags | {{ config.title }}{% endblock title %} {% block
description %}Browse all tags on {{ config.title }} — topics include binary exploitation, reverse
engineering, and offensive security.{% endblock description %} {% block canonical %}{{
config.base_url }}/tags/{% endblock canonical %} {% block og_title %}Tags | {{ config.title }}{%
endblock og_title %} {% block og_description %}Browse all tags on {{ config.title }}{% endblock
og_description %} {% block og_url %}{{ config.base_url }}/tags/{% endblock og_url %} {% block head
%}
<!-- SEO: Tags CollectionPage JSON-LD -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ config.base_url }}/tags/#collection",
"name": "Tags | {{ config.title }}",
"description": "Browse all tags on {{ config.title }}",
"url": "{{ config.base_url }}/tags/",
"inLanguage": "en-US",
"isPartOf": {
"@type": "WebSite",
"@id": "{{ config.base_url }}/#website"
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ config.base_url }}/tags/#collection",
"name": "Tags | {{ config.title }}",
"description": "Browse all tags on {{ config.title }}",
"url": "{{ config.base_url }}/tags/",
"inLanguage": "en-US",
"isPartOf": {
"@type": "WebSite",
"@id": "{{ config.base_url }}/#website"
}
}
}
</script>
{% endblock head %}
{% endblock head %} {% block content %}
<div
class="tags-page"
style="max-width: 65ch; margin: 0 auto; padding-top: 4rem; text-align: center"
>
<header class="page-header" style="margin-bottom: 3rem">
<h1 class="page-title" style="font-size: 2rem; font-weight: 600">All Tags</h1>
</header>
{% block content %}
<div class="tags-page" style="max-width: 65ch; margin: 0 auto; padding-top: 4rem; text-align: center;">
<header class="page-header" style="margin-bottom: 3rem;">
<h1 class="page-title" style="font-size: 2rem; font-weight: 600;">All Tags</h1>
</header>
<div class="tags-container" style="display: flex; flex-wrap: wrap; gap: 2rem; justify-content: center; align-items: center;">
{% for term in terms %}
<a href="{{ term.permalink }}" class="hover-scale" style="font-size: 1.1rem; color: var(--text-color); font-weight: 400; text-decoration: none; display: inline-flex; align-items: flex-start;">
{{ term.name }}<sup style="font-size: 0.7em; color: var(--text-muted); margin-left: 0.15rem; margin-top: 0.1rem;">{{ term.pages | length }}</sup>
</a>
{% endfor %}
</div>
<div
class="tags-container"
style="display: flex; flex-wrap: wrap; gap: 2rem; justify-content: center; align-items: center"
>
{% for term in terms %}
<a
href="{{ term.permalink }}"
class="hover-scale"
style="
font-size: 1.1rem;
color: var(--text-color);
font-weight: 400;
text-decoration: none;
display: inline-flex;
align-items: flex-start;
"
>
{{ term.name }}<sup
style="font-size: 0.7em; color: var(--text-muted); margin-left: 0.15rem; margin-top: 0.1rem"
>{{ term.pages | length }}</sup
>
</a>
{% endfor %}
</div>
</div>
{% endblock content %}
+69 -53
View File
@@ -1,61 +1,77 @@
{% extends "base.html" %}
{% block title %}Tag: {{ term.name }} | {{ config.title }}{% endblock title %}
{% block description %}Posts tagged "{{ term.name }}" on {{ config.title }}{% endblock description %}
{% block canonical %}{{ term.permalink }}{% endblock canonical %}
{% block og_title %}Tag: {{ term.name }} | {{ config.title }}{% endblock og_title %}
{% block og_description %}Posts tagged "{{ term.name }}" on {{ config.title }}{% endblock og_description %}
{% block og_url %}{{ term.permalink }}{% endblock og_url %}
{% block head %}
{% extends "base.html" %} {% block title %}Tag: {{ term.name }} | {{ config.title }}{% endblock
title %} {% block description %}Posts tagged "{{ term.name }}" on {{ config.title }}{% endblock
description %} {% block canonical %}{{ term.permalink }}{% endblock canonical %} {% block og_title
%}Tag: {{ term.name }} | {{ config.title }}{% endblock og_title %} {% block og_description %}Posts
tagged "{{ term.name }}" on {{ config.title }}{% endblock og_description %} {% block og_url %}{{
term.permalink }}{% endblock og_url %} {% block head %}
<!-- SEO: Tag CollectionPage JSON-LD -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ term.permalink }}#collection",
"name": "Posts tagged \"{{ term.name }}\" | {{ config.title }}",
"description": "Posts tagged \"{{ term.name }}\" on {{ config.title }}",
"url": "{{ term.permalink }}",
"inLanguage": "en-US",
"isPartOf": {
"@type": "WebSite",
"@id": "{{ config.base_url }}/#website"
},
"hasPart": [
{% for page in term.pages %}
{
"@type": "BlogPosting",
"@id": "{{ page.permalink }}#article",
"headline": {{ page.title | json_encode }},
"url": "{{ page.permalink }}"
{% if page.date %},"datePublished": "{{ page.date | date(format="%+") }}"{% endif %}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"@id": "{{ term.permalink }}#collection",
"name": "Posts tagged \"{{ term.name }}\" | {{ config.title }}",
"description": "Posts tagged \"{{ term.name }}\" on {{ config.title }}",
"url": "{{ term.permalink }}",
"inLanguage": "en-US",
"isPartOf": {
"@type": "WebSite",
"@id": "{{ config.base_url }}/#website"
},
"hasPart": [
{% for page in term.pages %}
{
"@type": "BlogPosting",
"@id": "{{ page.permalink }}#article",
"headline": {{ page.title | json_encode }},
"url": "{{ page.permalink }}"
{% if page.date %},"datePublished": "{{ page.date | date(format="%+") }}"{% endif %}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
</script>
{% endblock head %}
{% block content %}
{% endblock head %} {% block content %}
<div class="post-detail">
<header class="post-header" style="margin-bottom: 3rem;">
<h1 class="post-title" style="font-size: clamp(2rem, 4vw, 3.5rem);">
<span style="color: var(--text-muted);">#</span>{{ term.name }}
</h1>
</header>
<header class="post-header" style="margin-bottom: 3rem">
<h1 class="post-title" style="font-size: clamp(2rem, 4vw, 3.5rem)">
<span style="color: var(--text-muted)">#</span>{{ term.name }}
</h1>
</header>
<div class="archive-list" style="max-width: 65ch; margin: 0 auto;">
<div class="archive">
<ul style="list-style-type: none; padding-left: 0; margin: 0; display: flex; flex-direction: column; gap: 0.75rem;">
{% for page in term.pages %}
<li class="hover-scale" style="display: flex; gap: 1rem; align-items: baseline;">
<time datetime="{{ page.date }}" style="color: var(--text-muted); font-size: 0.95rem; white-space: nowrap; font-family: var(--font-mono);">{{ page.date | date(format="%d-%b") }}</time>
<a href="{{ page.permalink }}" style="color: var(--accent-color); font-weight: 500; font-size: 1.05rem;">{{ page.title }}</a>
</li>
{% endfor %}
</ul>
</div>
<div class="archive-list" style="max-width: 65ch; margin: 0 auto">
<div class="archive">
<ul
style="
list-style-type: none;
padding-left: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.75rem;
"
>
{% for page in term.pages %}
<li class="hover-scale" style="display: flex; gap: 1rem; align-items: baseline">
<time
datetime="{{ page.date }}"
style="
color: var(--text-muted);
font-size: 0.95rem;
white-space: nowrap;
font-family: var(--font-mono);
"
>{{ page.date | date(format="%d-%b") }}</time
>
<a
href="{{ page.permalink }}"
style="color: var(--accent-color); font-weight: 500; font-size: 1.05rem"
>{{ page.title }}</a
>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endblock content %}
+1 -1
View File
@@ -9,4 +9,4 @@ enabled = true
command = "./build.sh"
[assets]
directory = "./public"
directory = "./public"
+1 -1
View File
@@ -3,7 +3,7 @@ title = "Pwn For Fun And Profit"
description = "Hacking the Narrative, One Byte at a Time."
compile_sass = true
build_search_index = false
taxonomies = [ {name = "tags"} ]
taxonomies = [{ name = "tags" }]
generate_feeds = true
feed_filenames = ["rss.xml"]
generate_robots_txt = true