mirror of
https://github.com/andwati/pwnforfunandprofit.xyz.git
synced 2026-10-18 02:12:52 +00:00
feat: Establish CI pipeline, pre-commit hooks, and code quality linting for improved developer experience.
This commit is contained in:
@@ -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`.
|
||||
|
||||
---
|
||||
|
||||
@@ -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"
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
node scripts/validate-post.mjs
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"default": true,
|
||||
"MD013": false,
|
||||
"MD033": false,
|
||||
"MD041": false,
|
||||
"MD024": {
|
||||
"siblings_only": true
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-toml"]
|
||||
}
|
||||
Vendored
+4
-4
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"chat.tools.terminal.autoApprove": {
|
||||
"zola": true
|
||||
}
|
||||
}
|
||||
"chat.tools.terminal.autoApprove": {
|
||||
"zola": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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/**"
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+658
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,7 +1,7 @@
|
||||
// Utilities / Interactions
|
||||
.hover-scale {
|
||||
transition: var(--transition-spring);
|
||||
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Executable
+68
@@ -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!');
|
||||
}
|
||||
+1452
-1
File diff suppressed because one or more lines are too long
Vendored
+1128
-1
File diff suppressed because one or more lines are too long
+1482
-6
File diff suppressed because one or more lines are too long
+41
-39
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+170
-1
@@ -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)
|
||||
);
|
||||
})();
|
||||
});
|
||||
|
||||
Vendored
+11562
-1
File diff suppressed because one or more lines are too long
Vendored
+190510
-407
File diff suppressed because one or more lines are too long
+53
-51
@@ -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
@@ -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
@@ -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 %}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<footer class="site-footer">
|
||||
<div class="footer-content">
|
||||
<p>© {{ now() | date(format="%Y") }} {{ config.extra.author | default(value="Author") }}</p>
|
||||
<div class="social-links">
|
||||
<!-- Social Links -->
|
||||
</div>
|
||||
<div class="footer-content">
|
||||
<p>
|
||||
© {{ now() | date(format="%Y") }} {{ config.extra.author | default(value="Author") }}
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<!-- Social Links -->
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">← 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 →</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">← 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 →</span>
|
||||
<span class="post-nav-title">{{ page.lower.title }}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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
@@ -9,4 +9,4 @@ enabled = true
|
||||
command = "./build.sh"
|
||||
|
||||
[assets]
|
||||
directory = "./public"
|
||||
directory = "./public"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user