switched theme

This commit is contained in:
2026-04-02 13:14:18 +03:00
parent 0c26d89eae
commit e31dbbb16c
192 changed files with 215173 additions and 1463 deletions
+2 -1
View File
@@ -1 +1,2 @@
/public
public
node_modules
+1
View File
@@ -0,0 +1 @@
node scripts/validate-post.mjs
+9
View File
@@ -0,0 +1,9 @@
{
"default": true,
"MD013": false,
"MD033": false,
"MD041": false,
"MD024": {
"siblings_only": true
}
}
+12
View File
@@ -0,0 +1,12 @@
# Ignore Zola compiled output and dependencies
public/
node_modules/
# Ignore Zola HTML templates (Tera syntax breaks standard HTML parsing)
templates/**/*.html
templates/**/*.json
# Ignore minified static files
static/**/*.min.js
static/**/*.min.css
static/js/cookieconsent.umd.js
+9
View File
@@ -0,0 +1,9 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "all",
"printWidth": 100,
"plugins": ["prettier-plugin-toml"]
}
+1 -8
View File
@@ -1,8 +1 @@
# TODO
- [ ] create new post script
- [ ] add google analytics
- [ ] move goatcounter config to config.toml
- [ ] modularize open graph(og) tags
- [ ] add favicon to config
- [ ] add latex support
# andwati.xyz
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Executable
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
ZOLA_VERSION=0.22.1
echo "Downloading Zola ${ZOLA_VERSION}..."
curl -sLJO "https://github.com/getzola/zola/releases/download/v${ZOLA_VERSION}/zola-v${ZOLA_VERSION}-x86_64-unknown-linux-gnu.tar.gz"
echo "Extracting Zola..."
tar -xf zola-v${ZOLA_VERSION}-x86_64-unknown-linux-gnu.tar.gz
echo "Building site with Zola..."
./zola build
echo "Build complete! Generated files:"
ls -la public/
-23
View File
@@ -1,23 +0,0 @@
title = "mockingspectre"
description = "mockingspectre"
base_url = "https://andwati.xyz"
minify_html = true
compile_sass = true
generate_feeds = true
feed_filenames = ["rss.xml"]
taxonomies = [{ name = "tags", feed = false }]
[markdown]
highlight_code = true
highlight_theme = "gruvbox-dark"
[link_checker]
skip_prefixes = [""]
skip_anchor_prefixes = ["https://github.com/"]
[extra]
author = "Ian Andwati"
meta = [
{ property = "og:title", content = "the og title" },
{ property = "og:description", content = "the og description" },
]
+3 -3
View File
@@ -1,6 +1,6 @@
+++
title = "0xmockinspectre"
description = "offending binaries"
template = "index.html"
page_template = "page.html"
+++
spend my free time working on hobby projects and sometimes I write about my experiences. In particular, I enjoy learning about offensive security and reverse engineering.
spend my free time working on hobby projects and sometimes I write about my experiences. In particular, I enjoy learning about offensive security and reverse engineering.
+6
View File
@@ -0,0 +1,6 @@
+++
title = "About"
template = "page.html"
+++
spend my free time working on hobby projects and sometimes I write about my experiences. In particular, I enjoy learning about offensive security and reverse engineering.
+2 -2
View File
@@ -1,4 +1,4 @@
+++
sort_by = "date"
template= "archive.html"
title = "Archive"
template = "archive.html"
+++
-6
View File
@@ -1,6 +0,0 @@
+++
title = "Contact"
description = "Let's get in touch."
+++
spend my free time working on hobby projects and sometimes I write about my experiences. In particular, I enjoy learning about offensive security and reverse engineering.
+3 -3
View File
@@ -1,7 +1,7 @@
+++
title = "Posts"
template = "posts.html"
page_template = "post.html"
sort_by = "date"
insert_anchor_links = "left"
template = "posts.html"
page_template = "page.html"
paginate_by = 10
+++
@@ -1,5 +1,5 @@
+++
title ="Install PostgreSQL on Arch | Manjaro | Garuda Linux"
title = "Install PostgreSQL on Arch | Manjaro | Garuda Linux"
description = "Getting PostgreSQL up and running on Arch Linux-based distros"
date = 2023-02-16T15:34:14.160Z
author = "mockingspectre"
+21 -26
View File
@@ -4,7 +4,7 @@ description = "Integrating netlify cms"
date = 2022-11-24T15:34:14.160Z
author = "mockingspectre"
[taxonomies]
tags = ["blog", "hugo", "netlify","netlify-cms","github"]
tags = ["blog", "hugo", "netlify", "netlify-cms", "github"]
+++
Migrating to a better content management system through Netlify CMS
@@ -83,32 +83,27 @@ backend:
# This line should *not* be indented
publish_mode: editorial_workflow
media_folder: "static/images/uploads" # Media files will be stored in the repo under static/images/uploads
public_folder: "/images/uploads" # The src attribute for uploaded media will begin with /images/uploads
media_folder: 'static/images/uploads' # Media files will be stored in the repo under static/images/uploads
public_folder: '/images/uploads' # The src attribute for uploaded media will begin with /images/uploads
collections:
- name: "post" # Used in routes, e.g., /admin/collections/blog
label: "posts" # Used in the UI
folder: "content/post" # The path to the folder where the documents are stored
- name: 'post' # Used in routes, e.g., /admin/collections/blog
label: 'posts' # Used in the UI
folder: 'content/post' # The path to the folder where the documents are stored
create: true # Allow users to create new documents in this collection
slug: "{{year}}-{{month}}-{{day}}-{{slug}}" # Filename template, e.g., YYYY-MM-DD-title.md
slug: '{{year}}-{{month}}-{{day}}-{{slug}}' # Filename template, e.g., YYYY-MM-DD-title.md
fields: # The fields for each document, usually in front matter
- { label: "Layout", name: "layout", widget: "hidden", default: "blog" }
- { label: "Title", name: "title", widget: "string" }
- { label: "Draft", name: "draft", widget: "boolean", default: true }
- { label: "Publish Date", name: "date", widget: "datetime" }
- { label: "Last Modified", name: "lastmod", widget: "datetime" }
- { label: "Body", name: "body", widget: "markdown" }
- { label: "Keywords", name: "keywords", widget: "list" }
- { label: "Tags", name: "tags", widget: "list" }
- { label: "Categories", name: "categories", widget: "list" }
- { label: "Comments", name: "comment", widget: "boolean", default: true }
- {
label: "Table of Contents",
name: "toc",
widget: "boolean",
default: true,
}
- { label: 'Layout', name: 'layout', widget: 'hidden', default: 'blog' }
- { label: 'Title', name: 'title', widget: 'string' }
- { label: 'Draft', name: 'draft', widget: 'boolean', default: true }
- { label: 'Publish Date', name: 'date', widget: 'datetime' }
- { label: 'Last Modified', name: 'lastmod', widget: 'datetime' }
- { label: 'Body', name: 'body', widget: 'markdown' }
- { label: 'Keywords', name: 'keywords', widget: 'list' }
- { label: 'Tags', name: 'tags', widget: 'list' }
- { label: 'Categories', name: 'categories', widget: 'list' }
- { label: 'Comments', name: 'comment', widget: 'boolean', default: true }
- { label: 'Table of Contents', name: 'toc', widget: 'boolean', default: true }
```
**Step 5**
@@ -137,10 +132,10 @@ Lastly, we will add the following script before the closing `<body>` tag of our
```html
<script>
if (window.netlifyIdentity) {
window.netlifyIdentity.on("init", (user) => {
window.netlifyIdentity.on('init', (user) => {
if (!user) {
window.netlifyIdentity.on("login", () => {
document.location.href = "/admin/";
window.netlifyIdentity.on('login', () => {
document.location.href = '/admin/';
});
}
});
+17 -17
View File
@@ -30,7 +30,7 @@ module.exports = function add(a, b) {
// export an object
module.exports = {
name: "Alice",
name: 'Alice',
age: 25,
};
```
@@ -40,11 +40,11 @@ To import a module in Node.js, you can use the require function and pass the pat
```javascript
// main.js
// import a function
const add = require("./my-module.js");
const add = require('./my-module.js');
console.log(add(2, 3)); // 5
// import an object
const person = require("./my-module.js");
const person = require('./my-module.js');
console.log(person.name); // Alice
console.log(person.age); // 25
```
@@ -74,7 +74,7 @@ export class Person {
// export an object
export const person = {
name: "Alice",
name: 'Alice',
age: 25,
};
```
@@ -84,17 +84,17 @@ To import a module in ES6, you can use the `import` keyword followed by the name
```javascript
// main.js
// import a function
import { add } from "./my-module.js";
import { add } from './my-module.js';
console.log(add(2, 3)); // 5
// import a class
import { Person } from "./my-module.js";
const person = new Person("Bob", 30);
import { Person } from './my-module.js';
const person = new Person('Bob', 30);
console.log(person.name); // Bob
console.log(person.age); // 30
// import an object
import { person as Alice } from "./my-module.js";
import { person as Alice } from './my-module.js';
console.log(Alice.name); // Alice
console.log(Alice.age); // 25
```
@@ -138,7 +138,7 @@ module.exports = function add(a, b) {
// export an object
module.exports = {
name: "Alice",
name: 'Alice',
age: 25,
};
@@ -158,17 +158,17 @@ To import a default export, you can use the import keyword without curly braces
```javascript
// main.js
// import a default export
import add from "./my-module.js";
import add from './my-module.js';
console.log(add(2, 3)); // 5
// import another default export
import person from "./my-module.js";
import person from './my-module.js';
console.log(person.name); // Alice
console.log(person.age); // 25
// import another default export
import Person from "./my-module.js";
const person = new Person("Bob", 30);
import Person from './my-module.js';
const person = new Person('Bob', 30);
console.log(person.name); // Bob
console.log(person.age); // 30
```
@@ -178,17 +178,17 @@ To import a module.exports value, you can use the require function and assign an
```javascript
// main.js
// import a module.exports value
const add = require("./my-module.js");
const add = require('./my-module.js');
console.log(add(2, 3)); // 5
// import another module.exports value
const person = require("./my-module.js");
const person = require('./my-module.js');
console.log(person.name); // Alice
console.log(person.age); // 25
// import another module.exports value
const Person = require("./my-module.js");
const person = new Person("Bob", 30);
const Person = require('./my-module.js');
const person = new Person('Bob', 30);
console.log(person.name); // Bob
console.log(person.age); // 30
```
+12 -13
View File
@@ -6,14 +6,13 @@ date = 2025-08-03
tags = ["ctf", "rev", "web", "forensics"]
+++
Over the weekend, I had the opportunity to participate in the mmuctf 2025. It was an individual jeopardy style CTF targeted towards beginners and intermediate people in cybersecurity. This is a write-up to explore some challenges I did, or at least almost did.
## Welcome
>Through the shadows of the cave, at the door I had a zeal to get a welcome flag, but I couldn't see it though
I could hear it calling for me to get it. Happy hacking
{: .prompt-info }
> Through the shadows of the cave, at the door I had a zeal to get a welcome flag, but I couldn't see it though
> I could hear it calling for me to get it. Happy hacking
> {: .prompt-info }
This was a sanity check flag. I believe competitions pose such challenges that you can't get zero points. It's the same as flags in Discord servers, a flag after completing the feedback form, or just other free points.
@@ -27,7 +26,7 @@ mmuctf{welcome_flag_adios_Much@cho}
## My First App
>Our intern dev swears this app’s secure...
> Our intern dev swears this app’s secure...
This was a web challenge with an account creation form. Let's fire up Burp. The account creation request didn't seem suspicious for now, so I sent it to the repeater to abuse it some more. The response seemed interesting, the user id was in the query parameter: `GET /profile.php?id=5 HTTP/1.1`. This was a nice candidate for an Insecure Direct Object Reference(IDOR). I tried repeating the request with values from 0-4. The flag was at 1, the admin user's account.
@@ -37,7 +36,7 @@ mmuctf{1d0r_4dm1n_4cc3ss_1s_c0mpr0m1s3d}
## Ledilect
>Sometimes the path you take is just a jump away.
> Sometimes the path you take is just a jump away.
This was a simple one, I knew what the challenge wanted, but I couldn't get myself to exploit it, definitely a skill issue. The webapp had a landing page that had a button that made this request:
@@ -59,7 +58,7 @@ Looking at that, I suspected an open redirect vulnerability, but I don't know an
## Meet my X
>In the�days оf cοmfort i met this girl at the Jobless соrner. Аt the�doоrs and windows of iddleness i approached, oops she smiled and thats how i knew she was the one. I guess thatsss whyyy i learned on getting Flags because be warned she later left me at first it feels like a green flag i guess no life was there on blood(heart_break)
> In the�days оf cοmfort i met this girl at the Jobless соrner. Аt the�doоrs and windows of iddleness i approached, oops she smiled and thats how i knew she was the one. I guess thatsss whyyy i learned on getting Flags because be warned she later left me at first it feels like a green flag i guess no life was there on blood(heart_break)
This challenge involved homoglyphs, a character or a sequence of characters that looks very similar or identical to another character or sequence of characters, but has a different meaning, encoding, or origin.
@@ -73,8 +72,8 @@ mmuctf{oops_twitter_steg_1s_fun}
## Shadow
>Look beyond the appearance, perhaps their numerical essence will guide you to the flag.
Can you reveal what lurks in the shadow?
> Look beyond the appearance, perhaps their numerical essence will guide you to the flag.
> Can you reveal what lurks in the shadow?
This was another stego challenge; there is an image with what looks like a color palette. The description hints at 'numerical essence', so it might be something to deal with the hex values of the colors. I used this site <https://imagecolorpicker.com/> to extract the hex values.
@@ -99,7 +98,7 @@ mmuctf{4lw4y5_h1dd3n_1n5d3_m3}
## Revealme
>We found this mysterious executable on an old USB stick... Can you figure out how to get it to reveal the flag?
> We found this mysterious executable on an old USB stick... Can you figure out how to get it to reveal the flag?
The provided file is an ELF executable binary.
@@ -173,7 +172,7 @@ mmuctf{r3v3rs1ng_st4rt3r}
## Flicker
>An ordinary looking Android app... or is it?
> An ordinary looking Android app... or is it?
The provided app is an Android APK. Let's fire up jadx-gui. The package name is `com.example.blink`. There is an interesting class `r2d2`. In the onCreate method, there is an image string variable that seems to contain base64 image data.
@@ -190,8 +189,8 @@ CTF{PUCKMAN}
## Open_Secrets
>Someone trusted the wrong site and network didn’t forget. Can you piece together what was left behind?
Some people still think locks are optional on digital doors.*
> Someone trusted the wrong site and network didn’t forget. Can you piece together what was left behind?
> Some people still think locks are optional on digital doors.\*
The provided file was a network capture pcap file. For people who don't know, pcap (a packet capture) consists of an application programming interface (API) for capturing network traffic.The challenge title and filename hint at unencrypted traffic. Open up Wireshark and apply an HTTP filter. There is an HTTP stream that leaks the flag:
+1 -1
View File
@@ -4,7 +4,7 @@ description = "Syncing Obsidian in Windows with Windows Task Scheduler with git"
author = "mockingspectre"
date = 2023-10-01
[taxonomies]
tags = ["obsidian", "git","sync", "windows", "cron", "task scheduler", "powershell"]
tags = ["obsidian", "git", "sync", "windows", "cron", "task scheduler", "powershell"]
+++
Syncing Obsidian in Windows with Windows Task Scheduler and git
File diff suppressed because one or more lines are too long
@@ -1,10 +1,10 @@
+++
title= "Semantic Versioning(SemVer) and Conventional Commits"
title = "Semantic Versioning(SemVer) and Conventional Commits"
description = "Adopting best practices in your git and build workflows."
date= 2023-01-24T15:34:14.160Z
date = 2023-01-24T15:34:14.160Z
author = "mockingspectre"
[taxonomies]
tags= ["git", "semver","workflow", "conventional commits"]
tags = ["git", "semver", "workflow", "conventional commits"]
+++
Adopting best practices in your git and build workflows.
@@ -31,7 +31,7 @@ Semantic versioning is based on three numbers that indicate the versions of the
#### React(v18.2.0)
![react](/images/uploads/react.png "React versioning")
![react](/images/uploads/react.png 'React versioning')
- **Major -> 18**
- **Minor -> 2**
+4 -7
View File
@@ -1,10 +1,10 @@
+++
title= "Subresource Integrity"
title = "Subresource Integrity"
description = "Understanding Subresource Integrity"
date= 2023-04-29
date = 2023-04-29
author = "mockingspectre"
[taxonomies]
tags= ["subresource integrity", "csp", "cors"]
tags = ["subresource integrity", "csp", "cors"]
+++
Understanding Subresource Integrity
@@ -43,10 +43,7 @@ Using Subresource Integrity (SRI) involves a few steps, but it is relatively str
- Add the hash as an attribute to the tag that loads the external resource. For example, for a JavaScript file, you would add the integrity attribute to the `<script>` tag like this:
```html
<script
src="https://example.com/example.js"
integrity="sha256-ABC123XYZ456"
></script>
<script src="https://example.com/example.js" integrity="sha256-ABC123XYZ456"></script>
```
> An integrity value may contain multiple hashes separated by whitespace. A resource will be loaded if it matches one of those hashes.
@@ -0,0 +1,78 @@
+++
title = "The Single Byte That Kills Your Exploit: Understanding Endianness"
date = 2025-11-09
description = "Problem: Computers speak different languages, like people"
[taxonomies]
tags = ["blog"]
+++
If you’ve ever crafted a perfect shellcode and ROP chain only to have your exploit immediately crash with a SIGSEGV(a signal sent by the operating system to a program when it attempts to access a protected or invalid memory location) or EIP(a 32-bit CPU register in the x86 architecture that holds the memory address of the next machine instruction to be executed) pointing to garbage, you’ve likely met the silent killer of beginners: Endianness.
Endianness is fundamental; it defines the order in which a multi-byte value (like a 4-byte integer or an 8-byte memory address) is stored in a machine’s memory. For us in the pwn game, this translates directly to whether the addresses we pack into our exploit payloads are interpreted correctly by the target program. A single misplaced byte means the difference between a shell and a stack smash(stack buffer overflow**)**.
## Little vs. Big: The Two Orders
There are two primary byte orders
Little-Endian (LE): The Least Significant Byte (LSB) is stored first, at the lowest memory address. This is the byte order used by the vast majority of personal computers, including all modern Intel/AMD (x86/x64) architectures.
Big-Endian (BE): The Most Significant Byte (MSB) is stored first, at the lowest memory address. This is often called Network Byte Order (NBO) and is common in network protocols, as well as some older or embedded architectures like PowerPC, MIPS, and SPARC.
## Cross-architecture exploitation (LE <-> BE)
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.
pwntools and other frameworks allow you to set `context.arch` and `context.endian` So packers and disassemblers behave correctly.
## How to detect the target’s endianness quickly
Run the small C program above on the target if you have code execution. Use `readelf`, `file`, or `objdump` On binaries from the target filesystem, some formats indicate endianness.
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
(gdb) x/4xb 0x601000
0x601000: 0x44 0x43 0x42 0x41 # little-endian: ‘DCBA’
```
## 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.
Verify pointer size. Are you packing 32-bit or 64-bit addresses?
Check alignment and padding. Are there extra bytes inserted by the protocol or `scanf`/`fgets` behavior?
Confirm endianness on the target; don’t assume x86 for embedded devices.
Use tool packing (`struct.pack`, `pwntools`) instead of hand-reversing where possible.
If attacking over the network, confirm whether fields are consumed in network byte order.
## 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.
## 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.
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/>
Disclaimer: This article was written with AI assistance, for a bit of brainstorming and proofreading.
+11 -10
View File
@@ -4,11 +4,12 @@ description = "Tailwindcss in vuejs "
author = "mockingspectre"
date = 2021-06-12
[taxonomies]
tags = ["javascript", "vuejs" , "tailwindcss"]
tags = ["javascript", "vuejs", "tailwindcss"]
+++
A guide on how to use [Tailwind CSS](https://tailwindcss.com/) in your Vue project.
>**Note**
> **Note**
> This article was originaly published on my old medium blog. I moved it here for archival purposes.
Tailwind is a utility-first CSS framework for rapidly building custom user interfaces. It is an un-opinionated framework which means that it doesn't come with custom configurations like Bootstrap or Bulma(Yeah, I know, no one should tell you how to live your life).
@@ -59,7 +60,7 @@ module.exports = {
extend: {},
},
plugins: [],
}
};
```
It will also create a `postcss.config.js` file that includes tailwindcss and autoprefixer already configured.Postcss is needed to process tailwind:
@@ -71,7 +72,7 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
}
};
```
## Configure Tailwind to remove unused styles in production
@@ -80,7 +81,7 @@ In your tailwind.config.js file, configure the purge option with the paths to al
```js
// tailwind.config.js
module.exports = {
module.exports = {
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
darkMode: false, // or 'media' or 'class'
theme: {
@@ -90,7 +91,7 @@ In your tailwind.config.js file, configure the purge option with the paths to al
extend: {},
},
plugins: [],
}
};
```
## Include Tailwind in your CSS
@@ -112,11 +113,11 @@ Finally, ensure your CSS file is being imported in your `./src/main.js` file:
```js
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import { createApp } from 'vue';
import App from './App.vue';
import './index.css';
createApp(App).mount('#app')
createApp(App).mount('#app');
```
You're finished! Now when you run npm run dev, Tailwind CSS will be ready to use in your Vue app! You can now proceed to create awesome stuff
+149
View File
@@ -0,0 +1,149 @@
+++
title = "What is pwn?"
date = 2025-09-03
description = "No not that one"
[taxonomies]
tags = ["blog"]
+++
> Binary Exploitation is a broad topic within Cyber Security which really comes down to finding a vulnerability in the program and exploiting it to gain control of a shell or modifying the program's functions ~ctf101
In most challenges, you’ll be given a Linux binary to exploit, and occasionally a Windows executable. The goal is to find a way to get the program to run in a way that was not intended.
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 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)
---
## 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.”_
### **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.
---
### **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`.
---
### **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.
---
### **Episode 4: Exploiting Control Flow**
- 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`.
---
### **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.
---
### **Episode 7: Modern Protections & Bypasses (Part 2)**
- 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.
---
### **Episode 9: Heap Exploitation (Advanced)**
- Unlink attacks and unsafe unlink.
- Fastbin dup tricks.
- The “House of” techniques:
- House of Force
- House of Spirit
- House of Lore
- House of Einherjar
- Tcache poisoning in modern glibc.
---
### **Episode 10: Advanced Exploitation Techniques**
- 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.
---
### **Episode 12: Modern Defenses & Mitigations**
- 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).
---
### **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.
+68
View File
@@ -0,0 +1,68 @@
{
"version": "0.2",
"language": "en",
"words": [
"DCBA",
"Einherjar",
"Fastbin",
"Ghidra",
"Heartbleed",
"Kernighan",
"Pgadget",
"Pwntools",
"Ritchie",
"SIGSEGV",
"SROP",
"Sandboxing",
"Shellshock",
"Sigreturn",
"Tcache",
"Userland",
"andwati",
"angr",
"bruteforcing",
"buttondown",
"catppuccin",
"cookieconsent",
"disassemblers",
"dlresolve",
"elasticlunr",
"endianness",
"fgets",
"frontmatter",
"honggfuzz",
"katex",
"kebab",
"libc",
"lightbox",
"listmonk",
"malloc",
"markdownlint",
"mockingspectre",
"objdump",
"precommit",
"pwn",
"pwndbg",
"pwnforfunandprofit",
"pwntools",
"radare",
"readelf",
"scanf",
"seccomp",
"shellcode",
"shortcodes",
"subdirectories",
"syscalls",
"utterances",
"webmention",
"zola"
],
"ignorePaths": [
"node_modules",
"package.json",
"pnpm-lock.yaml",
"public",
"static/js/**",
"static/css/**"
]
}
+50
View File
@@ -0,0 +1,50 @@
---
name: full-output-enforcement
description: Overrides default LLM truncation behavior. Enforces complete code generation, bans placeholder patterns, and handles token-limit splits cleanly. Apply to any task requiring exhaustive, unabridged output.
---
# Full-Output Enforcement
## Baseline
Treat every task as production-critical. A partial output is a broken output. Do not optimize for brevity — optimize for completeness. If the user asks for a full file, deliver the full file. If the user asks for 5 components, deliver 5 components. No exceptions.
## Banned Output Patterns
The following patterns are hard failures. Never produce them:
**In code blocks:** `// ...`, `// rest of code`, `// implement here`, `// TODO`, `/* ... */`, `// similar to above`, `// continue pattern`, `// add more as needed`, bare `...` standing in for omitted code
**In prose:** "Let me know if you want me to continue", "I can provide more details if needed", "for brevity", "the rest follows the same pattern", "similarly for the remaining", "and so on" (when replacing actual content), "I'll leave that as an exercise"
**Structural shortcuts:** Outputting a skeleton when the request was for a full implementation. Showing the first and last section while skipping the middle. Replacing repeated logic with one example and a description. Describing what code should do instead of writing it.
## Execution Process
1. **Scope** — Read the full request. Count how many distinct deliverables are expected (files, functions, sections, answers). Lock that number.
2. **Build** — Generate every deliverable completely. No partial drafts, no "you can extend this later."
3. **Cross-check** — Before output, re-read the original request. Compare your deliverable count against the scope count. If anything is missing, add it before responding.
## Handling Long Outputs
When a response approaches the token limit:
- Do not compress remaining sections to squeeze them in.
- Do not skip ahead to a conclusion.
- Write at full quality up to a clean breakpoint (end of a function, end of a file, end of a section).
- End with:
```
[PAUSED — X of Y complete. Send "continue" to resume from: next section name]
```
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
+182
View File
@@ -0,0 +1,182 @@
---
name: redesign-existing-projects
description: Upgrades existing websites and apps to premium quality. Audits current design, identifies generic AI patterns, and applies high-end design standards without breaking functionality. Works with any CSS framework or vanilla CSS.
---
# Redesign Skill
## How This Works
When applied to an existing project, follow this sequence:
1. **Scan** — Read the codebase. Identify the framework, styling method (Tailwind, vanilla CSS, styled-components, etc.), and current design patterns.
2. **Diagnose** — Run through the audit below. List every generic pattern, weak point, and missing state you find.
3. **Fix** — Apply targeted upgrades working with the existing stack. Do not rewrite from scratch. Improve what's there.
## Design Audit
### Typography
Check for these problems and fix them:
- **Browser default fonts or Inter everywhere.** Replace with a font that has character. Good options: `Geist`, `Outfit`, `Cabinet Grotesk`, `Satoshi`. For editorial/creative projects, pair a serif header with a sans-serif body.
- **Headlines lack presence.** Increase size for display text, tighten letter-spacing, reduce line-height. Headlines should feel heavy and intentional.
- **Body text too wide.** Limit paragraph width to roughly 65 characters. Increase line-height for readability.
- **Only Regular (400) and Bold (700) weights used.** Introduce Medium (500) and SemiBold (600) for more subtle hierarchy.
- **Numbers in proportional font.** Use a monospace font or enable tabular figures (`font-variant-numeric: tabular-nums`) for data-heavy interfaces.
- **Missing letter-spacing adjustments.** Use negative tracking for large headers, positive tracking for small caps or labels.
- **All-caps subheaders everywhere.** Try lowercase italics, sentence case, or small-caps instead.
- **Orphaned words.** Single words sitting alone on the last line. Fix with `text-wrap: balance` or `text-wrap: pretty`.
### Color and Surfaces
- **Pure `#000000` background.** Replace with off-black, dark charcoal, or tinted dark (`#0a0a0a`, `#121212`, or a dark navy).
- **Oversaturated accent colors.** Keep saturation below 80%. Desaturate accents so they blend with neutrals instead of screaming.
- **More than one accent color.** Pick one. Remove the rest. Consistency beats variety.
- **Mixing warm and cool grays.** Stick to one gray family. Tint all grays with a consistent hue (warm or cool, not both).
- **Purple/blue "AI gradient" aesthetic.** This is the most common AI design fingerprint. Replace with neutral bases and a single, considered accent.
- **Generic `box-shadow`.** Tint shadows to match the background hue. Use colored shadows (e.g., dark blue shadow on a blue background) instead of pure black at low opacity.
- **Flat design with zero texture.** Add subtle noise, grain, or micro-patterns to backgrounds. Pure flat vectors feel sterile.
- **Perfectly even gradients.** Break the uniformity with radial gradients, noise overlays, or mesh gradients instead of standard linear 45-degree fades.
- **Inconsistent lighting direction.** Audit all shadows to ensure they suggest a single, consistent light source.
- **Random dark sections in a light mode page (or vice versa).** A single dark-background section breaking an otherwise light page looks like a copy-paste accident. Either commit to a full dark mode or keep a consistent background tone throughout. If contrast is needed, use a slightly darker shade of the same palette — not a sudden jump to `#111` in the middle of a cream page.
- **Empty, flat sections with no visual depth.** Sections that are just text on a plain background feel unfinished. Add high-quality background imagery (blurred, overlaid, or masked), subtle patterns, or ambient gradients. Use reliable placeholder sources like `https://picsum.photos/seed/{name}/1920/1080` when real assets are not available. Experiment with background images behind hero sections, feature blocks, or CTAs — even a subtle full-width photo at low opacity adds presence.
### Layout
- **Everything centered and symmetrical.** Break symmetry with offset margins, mixed aspect ratios, or left-aligned headers over centered content.
- **Three equal card columns as feature row.** This is the most generic AI layout. Replace with a 2-column zig-zag, asymmetric grid, horizontal scroll, or masonry layout.
- **Using `height: 100vh` for full-screen sections.** Replace with `min-height: 100dvh` to prevent layout jumping on mobile browsers (iOS Safari viewport bug).
- **Complex flexbox percentage math.** Replace with CSS Grid for reliable multi-column structures.
- **No max-width container.** Add a container constraint (around 1200-1440px) with auto margins so content doesn't stretch edge-to-edge on wide screens.
- **Cards of equal height forced by flexbox.** Allow variable heights or use masonry when content varies in length.
- **Uniform border-radius on everything.** Vary the radius: tighter on inner elements, softer on containers.
- **No overlap or depth.** Elements sit flat next to each other. Use negative margins to create layering and visual depth.
- **Symmetrical vertical padding.** Top and bottom padding are always identical. Adjust optically — bottom padding often needs to be slightly larger.
- **Dashboard always has a left sidebar.** Try top navigation, a floating command menu, or a collapsible panel instead.
- **Missing whitespace.** Double the spacing. Let the design breathe. Dense layouts work for data dashboards, not for marketing pages.
- **Buttons not bottom-aligned in card groups.** When cards have different content lengths, CTAs end up at random heights. Pin buttons to the bottom of each card so they form a clean horizontal line regardless of content above.
- **Feature lists starting at different vertical positions.** In pricing tables or comparison cards, the list of features should start at the same Y position across all columns. Use consistent spacing above the list or fixed-height title/price blocks.
- **Inconsistent vertical rhythm in side-by-side elements.** When placing cards, columns, or panels next to each other, align shared elements (titles, descriptions, prices, buttons) across all items. Misaligned baselines make the layout look broken.
- **Mathematical alignment that looks optically wrong.** Centering by the math doesn't always look centered to the eye. Icons next to text, play buttons in circles, or text in buttons often need 1-2px optical adjustments to feel right.
### Interactivity and States
- **No hover states on buttons.** Add background shift, slight scale, or translate on hover.
- **No active/pressed feedback.** Add a subtle `scale(0.98)` or `translateY(1px)` on press to simulate a physical click.
- **Instant transitions with zero duration.** Add smooth transitions (200-300ms) to all interactive elements.
- **Missing focus ring.** Ensure visible focus indicators for keyboard navigation. This is an accessibility requirement, not optional.
- **No loading states.** Replace generic circular spinners with skeleton loaders that match the layout shape.
- **No empty states.** An empty dashboard showing nothing is a missed opportunity. Design a composed "getting started" view.
- **No error states.** Add clear, inline error messages for forms. Do not use `window.alert()`.
- **Dead links.** Buttons that link to `#`. Either link to real destinations or visually disable them.
- **No indication of current page in navigation.** Style the active nav link differently so users know where they are.
- **Scroll jumping.** Anchor clicks jump instantly. Add `scroll-behavior: smooth`.
- **Animations using `top`, `left`, `width`, `height`.** Switch to `transform` and `opacity` for GPU-accelerated, smooth animation.
### Content
- **Generic names like "John Doe" or "Jane Smith".** Use diverse, realistic-sounding names.
- **Fake round numbers like `99.99%`, `50%`, `$100.00`.** Use organic, messy data: `47.2%`, `$99.00`, `+1 (312) 847-1928`.
- **Placeholder company names like "Acme Corp", "Nexus", "SmartFlow".** Invent contextual, believable brand names.
- **AI copywriting cliches.** Never use "Elevate", "Seamless", "Unleash", "Next-Gen", "Game-changer", "Delve", "Tapestry", or "In the world of...". Write plain, specific language.
- **Exclamation marks in success messages.** Remove them. Be confident, not loud.
- **"Oops!" error messages.** Be direct: "Connection failed. Please try again."
- **Passive voice.** Use active voice: "We couldn't save your changes" instead of "Mistakes were made."
- **All blog post dates identical.** Randomize dates to appear real.
- **Same avatar image for multiple users.** Use unique assets for every distinct person.
- **Lorem Ipsum.** Never use placeholder latin text. Write real draft copy.
- **Title Case On Every Header.** Use sentence case instead.
### Component Patterns
- **Generic card look (border + shadow + white background).** Remove the border, or use only background color, or use only spacing. Cards should exist only when elevation communicates hierarchy.
- **Always one filled button + one ghost button.** Add text links or tertiary styles to reduce visual noise.
- **Pill-shaped "New" and "Beta" badges.** Try square badges, flags, or plain text labels.
- **Accordion FAQ sections.** Use a side-by-side list, searchable help, or inline progressive disclosure.
- **3-card carousel testimonials with dots.** Replace with a masonry wall, embedded social posts, or a single rotating quote.
- **Pricing table with 3 towers.** Highlight the recommended tier with color and emphasis, not just extra height.
- **Modals for everything.** Use inline editing, slide-over panels, or expandable sections instead of popups for simple actions.
- **Avatar circles exclusively.** Try squircles or rounded squares for a less generic look.
- **Light/dark toggle always a sun/moon switch.** Use a dropdown, system preference detection, or integrate it into settings.
- **Footer link farm with 4 columns.** Simplify. Focus on main navigational paths and legally required links.
### Iconography
- **Lucide or Feather icons exclusively.** These are the "default" AI icon choice. Use Phosphor, Heroicons, or a custom set for differentiation.
- **Rocketship for "Launch", shield for "Security".** Replace cliche metaphors with less obvious icons (bolt, fingerprint, spark, vault).
- **Inconsistent stroke widths across icons.** Audit all icons and standardize to one stroke weight.
- **Missing favicon.** Always include a branded favicon.
- **Stock "diverse team" photos.** Use real team photos, candid shots, or a consistent illustration style instead of uncanny stock imagery.
### Code Quality
- **Div soup.** Use semantic HTML: `<nav>`, `<main>`, `<article>`, `<aside>`, `<section>`.
- **Inline styles mixed with CSS classes.** Move all styling to the project's styling system.
- **Hardcoded pixel widths.** Use relative units (`%`, `rem`, `em`, `max-width`) for flexible layouts.
- **Missing alt text on images.** Describe image content for screen readers. Never leave `alt=""` or `alt="image"` on meaningful images.
- **Arbitrary z-index values like `9999`.** Establish a clean z-index scale in the theme/variables.
- **Commented-out dead code.** Remove all debug artifacts before shipping.
- **Import hallucinations.** Check that every import actually exists in `package.json` or the project dependencies.
- **Missing meta tags.** Add proper `<title>`, `description`, `og:image`, and social sharing meta tags.
### Strategic Omissions (What AI Typically Forgets)
- **No legal links.** Add privacy policy and terms of service links in the footer.
- **No "back" navigation.** Dead ends in user flows. Every page needs a way back.
- **No custom 404 page.** Design a helpful, branded "page not found" experience.
- **No form validation.** Add client-side validation for emails, required fields, and format checks.
- **No "skip to content" link.** Essential for keyboard users. Add a hidden skip-link.
- **No cookie consent.** If required by jurisdiction, add a compliant consent banner.
## Upgrade Techniques
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.
- **Colored, tinted shadows.** Shadows that carry the hue of the background rather than using generic black.
## Fix Priority
Apply changes in this order for maximum visual impact with minimum risk:
1. **Font swap** — biggest instant improvement, lowest risk
2. **Color palette cleanup** — remove clashing or oversaturated colors
3. **Hover and active states** — makes the interface feel alive
4. **Layout and spacing** — proper grid, max-width, consistent padding
5. **Replace generic components** — swap cliche patterns for modern alternatives
6. **Add loading, empty, and error states** — makes it feel finished
7. **Polish typography scale and spacing** — the premium final touch
## Rules
- Work with the existing tech stack. Do not migrate frameworks or styling libraries.
- Do not break existing functionality. Test after every change.
- 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.
+263
View File
@@ -0,0 +1,263 @@
---
name: design-taste-frontend
description: Senior UI/UX Engineer. Architect digital interfaces overriding default LLM biases. Enforces metric-based rules, strict component architecture, CSS hardware acceleration, and balanced design engineering.
---
# 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)
**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`).
## 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]`.
**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.
**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.
**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.
**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.
**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.
## 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.
## 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).
## 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.
### 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')`.
### 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.
## 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.
### 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.
### 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.
### 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.
### 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.
## 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).
### 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.
### 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.
### 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.
### 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.
### 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.
### 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.
### 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.
## 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.
### 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.
### 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.
4. **The Wide Data Stream:** A horizontal "Infinite Carousel" of data cards or metrics. Ensure the loop is seamless (using `x: ["0%", "-100%"]`) with a speed that feels effortless.
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?
+24
View File
@@ -0,0 +1,24 @@
{
"name": "andwati.xyz",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"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.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
"devDependencies": {
"husky": "^9.1.7",
"markdownlint-cli": "^0.48.0",
"prettier": "^3.8.1",
"prettier-plugin-toml": "^2.0.6",
"wrangler": "^4.71.0"
}
}
+1527
View File
File diff suppressed because it is too large Load Diff
+174
View File
@@ -0,0 +1,174 @@
// 404 Terminal Page
// Scoped custom properties switch with html.dark
.term-window {
// Dark mode values (default — site defaults to dark)
--tw-bg: #0d0d0d;
--tw-bar: #1c1c1c;
--tw-border: #2a2a2a;
--tw-text: #cdd6f4;
--tw-prompt: #10b981;
--tw-error: #f38ba8;
--tw-muted: #6c7086;
--tw-link: #89b4fa;
--tw-cursor: #10b981;
}
html:not(.dark) .term-window {
// Light mode: Catppuccin Latte palette
--tw-bg: #eff1f5;
--tw-bar: #e6e9ef;
--tw-border: #ccd0da;
--tw-text: #4c4f69;
--tw-prompt: #40a02b;
--tw-error: #d20f39;
--tw-muted: #7c7f93;
--tw-link: #1e66f5;
--tw-cursor: #40a02b;
}
.notfound-wrap {
min-height: 70dvh;
display: flex;
align-items: center;
justify-content: center;
padding: 3rem 0;
}
.term-window {
width: 100%;
max-width: 680px;
background: var(--tw-bg);
border-radius: 10px;
border: 1px solid var(--tw-border);
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.4);
overflow: hidden;
font-family: var(--font-mono);
font-size: clamp(0.78rem, 1.5vw, 0.9rem);
transition:
background-color 0.3s ease,
border-color 0.3s ease;
}
.term-titlebar {
background: var(--tw-bar);
padding: 0.7rem 1rem;
display: flex;
align-items: center;
gap: 0.75rem;
border-bottom: 1px solid var(--tw-border);
user-select: none;
}
.term-dots {
display: flex;
gap: 0.45rem;
span {
display: block;
width: 12px;
height: 12px;
border-radius: 50%;
}
.dot-red {
background: #ff5f57;
}
.dot-yellow {
background: #febc2e;
}
.dot-green {
background: #28c840;
}
}
.term-titlebar-label {
flex: 1;
text-align: center;
font-size: 0.72rem;
color: var(--tw-muted);
letter-spacing: 0.03em;
}
.term-body {
padding: 1.5rem;
line-height: 1.65;
color: var(--tw-text);
min-height: 260px;
}
.term-row {
display: flex;
align-items: baseline;
gap: 0;
white-space: nowrap;
overflow: hidden;
}
.term-ps1 {
color: var(--tw-prompt);
flex-shrink: 0;
white-space: nowrap;
pointer-events: none;
}
.term-input {
color: var(--tw-text);
overflow: hidden;
}
.term-out {
margin-bottom: 0.9rem;
}
.term-out-error {
color: var(--tw-error);
}
.term-out-muted {
color: var(--tw-muted);
margin-bottom: 0.9rem;
}
.term-out-ls {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1.5rem;
margin-bottom: 0.9rem;
a {
color: var(--tw-link);
text-decoration: none;
font-weight: 500;
&::before {
content: '→ ';
color: var(--tw-prompt);
}
&:hover {
color: var(--tw-prompt);
text-decoration: underline;
}
}
}
.term-cursor {
display: inline-block;
width: 0.55em;
height: 1.1em;
background: var(--tw-cursor);
vertical-align: text-bottom;
animation: blink-cursor 1s step-end infinite;
margin-left: 0.15ch;
}
@keyframes blink-cursor {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
+626
View File
@@ -0,0 +1,626 @@
// Article details and content styling
.reading-progress-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
background-color: transparent;
z-index: 1000;
pointer-events: none;
}
.reading-progress-bar {
height: 100%;
width: 0%;
background-color: var(--accent-color);
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5); /* Glowing emerald accent */
transition: width 0.1s cubic-bezier(0.25, 0.8, 0.25, 1);
}
// Post Detail
.post-detail {
max-width: 800px;
margin: 0 auto;
padding-top: 4rem;
}
.post-header {
margin-bottom: 4rem;
}
.post-title {
margin-bottom: 1rem;
}
.post-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.4rem 0.75rem;
color: var(--text-muted);
font-size: 0.9rem;
font-style: italic;
}
.post-meta-item {
display: inline-flex;
align-items: center;
gap: 0.35rem;
}
.post-meta-label {
font-style: normal;
font-size: 0.75rem;
font-family: var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--accent-color);
opacity: 0.8;
}
.post-meta-sep {
color: var(--border-color);
font-style: normal;
user-select: none;
}
.post-meta-updated time {
color: var(--text-muted);
}
// Heading anchor links
.post-body {
h2,
h3 {
position: relative;
.heading-anchor {
display: inline-block;
margin-left: 0.5em;
color: var(--accent-color);
font-family: var(--font-mono);
font-weight: 400;
font-size: 0.75em;
opacity: 0;
text-decoration: none;
vertical-align: middle;
transition: opacity 0.15s ease;
user-select: none;
}
&:hover .heading-anchor {
opacity: 1;
}
}
}
// ──────────────────────────────────────────
// Series Navigation Box
// ──────────────────────────────────────────
.series-nav {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-left: 3px solid var(--accent-color);
border-radius: 12px;
padding: 1.25rem 1.5rem;
margin-bottom: 2rem;
}
.series-nav-header {
display: flex;
align-items: baseline;
flex-wrap: wrap;
gap: 0.4rem 0.75rem;
margin-bottom: 1rem;
}
.series-nav-eyebrow {
font-family: var(--font-mono);
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--accent-color);
opacity: 0.8;
}
.series-nav-title {
font-weight: 600;
font-size: 0.95rem;
color: var(--heading-color, var(--text-color));
flex: 1;
}
.series-nav-badge {
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--text-muted);
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 99px;
padding: 0.1em 0.6em;
white-space: nowrap;
}
.series-nav-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.series-nav-item {
display: flex;
align-items: baseline;
gap: 0.75rem;
&.is-current {
.series-nav-index {
color: var(--accent-color);
}
.series-nav-label {
color: var(--text-color);
font-weight: 600;
}
}
}
.series-nav-index {
font-family: var(--font-mono);
font-size: 0.7rem;
color: var(--text-muted);
opacity: 0.6;
flex-shrink: 0;
width: 1.5ch;
text-align: right;
}
.series-nav-label {
font-size: 0.9rem;
color: var(--text-muted);
text-decoration: none;
&:hover {
color: var(--accent-color);
}
}
.post-toc {
background: var(--surface-color);
border: 1px solid var(--border-color);
padding: 1.5rem;
border-radius: 12px;
margin-bottom: 3rem;
summary {
font-weight: 600;
cursor: pointer;
list-style: none; /* Hide default arrow */
display: flex;
align-items: center;
gap: 0.5rem;
&::before {
content: '↓';
color: var(--accent-color);
transition: transform 0.2s ease;
}
}
&[open] summary::before {
transform: rotate(180deg);
}
ul {
margin-top: 1rem;
margin-bottom: 0;
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);
}
}
}
.post-tags-share {
display: flex;
flex-direction: column;
gap: 2rem;
margin-top: 4rem;
@media (min-width: 640px) {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
}
.post-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
.tag {
background: var(--surface-color);
border: 1px solid var(--border-color);
padding: 0.25rem 0.75rem;
border-radius: 99px;
font-size: 0.85rem;
font-family: var(--font-mono);
}
}
.social-share {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem;
.share-label {
font-weight: 500;
color: var(--text-color);
margin-right: 0.25rem;
}
.share-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--surface-color);
border: 1px solid var(--border-color);
color: var(--text-muted);
transition: var(--transition-smooth);
cursor: pointer;
&:hover {
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;
}
}
}
}
.post-nav {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
margin-top: 3rem;
padding-top: 2rem;
border-top: 1px solid var(--border-color);
}
.post-nav-link {
display: flex;
flex-direction: column;
gap: 0.35rem;
align-items: center;
text-align: center;
max-width: 34ch;
}
.post-nav-label {
color: var(--accent-color);
font-size: 0.9rem;
font-weight: 500;
}
.post-nav-title {
font-size: 1rem;
line-height: 1.35;
color: var(--text-muted);
transition: color 0.2s ease;
}
.post-nav-link:hover .post-nav-title,
.post-nav-link:hover .post-nav-label {
color: var(--accent-color);
}
@media (max-width: 640px) {
.post-nav-title {
font-size: 0.95rem;
}
}
@media (min-width: 900px) {
.post-nav {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 2rem;
align-items: start;
}
.post-nav-prev {
justify-self: start;
align-items: flex-start;
text-align: left;
}
.post-nav-next {
justify-self: end;
align-items: flex-end;
text-align: right;
}
}
// ──────────────────────────────────────────
// Sidebar TOC Layout
// ──────────────────────────────────────────
.post-layout {
display: contents;
@media (min-width: 1200px) {
display: grid;
grid-template-columns: minmax(0, 800px) 240px;
gap: 3rem;
align-items: start;
max-width: 1100px;
margin: 0 auto;
}
}
@media (min-width: 1200px) {
.post-detail {
max-width: none;
margin: 0;
}
// Hide inline TOC — sidebar takes over
.post-toc {
display: none !important;
}
}
.post-toc-sidebar {
display: none;
@media (min-width: 1200px) {
display: block;
position: sticky;
top: 5.5rem;
max-height: calc(100vh - 7rem);
overflow-y: auto;
padding-top: 4rem; // align with article content start
scrollbar-width: thin;
scrollbar-color: var(--border-color) transparent;
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
}
}
.toc-nav {
ul {
list-style: none;
padding: 0;
margin: 0;
// Nested level (h2 children)
ul {
padding-left: 1rem;
margin-top: 0.25rem;
}
}
li {
margin-bottom: 0.35rem;
}
a {
display: block;
color: var(--text-muted);
font-size: 0.85rem;
line-height: 1.4;
padding: 0.2rem 0;
border-left: 2px solid transparent;
padding-left: 0.75rem;
transition:
color 0.2s ease,
border-color 0.2s ease;
text-decoration: none;
&:hover {
color: var(--accent-color);
}
&.toc-active {
color: var(--accent-color);
border-left-color: var(--accent-color);
}
}
}
.toc-sidebar-title {
font-size: 0.75rem;
font-family: var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-muted);
margin: 0 0 1rem 0;
padding-left: 0.75rem;
}
// ──────────────────────────────────────────
// Media Wrappers (YouTube, etc)
// ──────────────────────────────────────────
.youtube-wrapper {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--border-color);
margin: 2rem 0;
background: #000;
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
}
// ──────────────────────────────────────────
// Callouts / Admonitions
// ──────────────────────────────────────────
.callout {
margin: 2rem 0;
padding: 1.25rem 1.5rem;
border-radius: 6px;
border-left: 4px solid var(--callout-color, var(--border-color));
background: var(--surface-color);
color: var(--text-color);
font-size: 0.95rem;
line-height: 1.6;
.callout-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
font-weight: 600;
color: var(--callout-color, var(--heading-color));
}
.callout-icon {
width: 1.25rem;
height: 1.25rem;
flex-shrink: 0;
}
.callout-title {
font-family: var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 0.85rem;
}
.callout-content {
> :first-child {
margin-top: 0;
}
> :last-child {
margin-bottom: 0;
}
}
// Variants
&.callout-note {
--callout-color: #3b82f6; // Blue
background: rgba(59, 130, 246, 0.05);
}
&.callout-warning {
--callout-color: #f59e0b; // Amber
background: rgba(245, 158, 11, 0.05);
}
&.callout-tip {
--callout-color: #10b981; // Emerald
background: rgba(16, 185, 129, 0.05);
}
&.callout-danger {
--callout-color: #ef4444; // Red
background: rgba(239, 68, 68, 0.05);
}
}
// ──────────────────────────────────────────
// Related Posts
// ──────────────────────────────────────────
.related-posts-section {
margin-top: 4rem;
padding-top: 2rem;
border-top: 1px dashed var(--border-color);
h3 {
margin-bottom: 2rem;
font-size: 1.5rem;
color: var(--heading-color);
letter-spacing: -0.02em;
}
.related-posts-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
@media (min-width: 768px) {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2.5rem;
}
// Override some constraints from feed-page cards when inside the grid
.feed-item {
border-bottom: none !important;
padding-bottom: 0 !important;
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1.5rem !important;
height: 100%;
display: flex;
flex-direction: column;
transition: var(--transition-smooth);
&:hover {
border-color: var(--accent-color);
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
.item-snippet {
flex: 1;
}
}
}
}
+180
View File
@@ -0,0 +1,180 @@
// Post Feed Page Container
.feed-page {
max-width: 800px;
margin: 0 auto;
}
// Post Feed
.post-feed {
display: flex;
flex-direction: column;
gap: 3rem;
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;
}
.item-link {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.item-date {
font-size: 0.85rem;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.item-title {
margin: 0;
font-size: clamp(1.5rem, 2vw, 2rem);
transition: color 0.2s ease;
}
.item-link:hover .item-title {
color: var(--accent-color);
}
.item-desc {
margin: 0;
color: var(--text-muted);
}
// ──────────────────────────────────────────
// Pagination
// ──────────────────────────────────────────
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin-top: 5rem;
padding-top: 2rem;
border-top: 1px solid var(--border-color);
font-family: var(--font-mono);
}
.pagination-pages {
display: flex;
align-items: center;
gap: 0.25rem;
}
// Shared base for arrows + numbers
.pagination-arrow,
.pagination-num {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2.25rem;
height: 2.25rem;
padding: 0 0.5rem;
border-radius: 6px;
border: 1px solid var(--border-color);
background: var(--surface-color);
color: var(--text-muted);
font-size: 0.875rem;
text-decoration: none;
transition: var(--transition-smooth);
cursor: pointer;
user-select: none;
&:hover {
border-color: var(--accent-color);
color: var(--accent-color);
transform: translateY(-1px);
}
}
// Active / current page
.pagination-num.is-current {
background: var(--accent-color);
border-color: var(--accent-color);
color: #fff;
font-weight: 600;
cursor: default;
pointer-events: none;
&:hover {
transform: none;
}
}
// Disabled arrow (no prev/next page)
.pagination-arrow.is-disabled {
opacity: 0.3;
cursor: not-allowed;
pointer-events: none;
&:hover {
border-color: var(--border-color);
color: var(--text-muted);
transform: none;
}
}
// Ellipsis gap marker
.pagination-ellipsis {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2.25rem;
height: 2.25rem;
color: var(--text-muted);
font-size: 0.875rem;
letter-spacing: 0.05em;
}
// "Page N of M" helper text
.pagination-meta {
text-align: center;
margin-top: 1rem;
margin-bottom: 0;
font-size: 0.8rem;
color: var(--text-muted);
font-family: var(--font-mono);
letter-spacing: 0.05em;
}
+9
View File
@@ -0,0 +1,9 @@
// Footer
.site-footer {
padding: 3rem 0;
border-top: 1px solid var(--border-color);
color: var(--text-muted);
font-size: 0.9rem;
display: flex;
justify-content: space-between;
}
+152
View File
@@ -0,0 +1,152 @@
// Component: Header
.site-header {
padding: 1.25rem 0;
position: sticky;
top: 0;
z-index: 50;
background-color: var(--bg-color);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-color);
}
.nav-container {
display: flex;
justify-content: space-between;
align-items: center;
}
// Right side wrapper (links + controls)
.nav-right {
display: flex;
align-items: center;
gap: 1rem;
}
// Always-visible icon controls
.nav-controls {
display: flex;
align-items: center;
gap: 0.5rem;
}
.search-toggle {
background: none;
border: none;
color: var(--text-color);
cursor: pointer;
padding: 0.35rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: var(--transition-smooth);
&:hover {
color: var(--accent-color);
}
}
// Hamburger button — hidden on desktop, shown on mobile
.nav-toggle {
display: none;
background: none;
border: none;
color: var(--text-color);
cursor: pointer;
padding: 0.25rem;
align-items: center;
justify-content: center;
.close-icon {
display: none;
}
&[aria-expanded='true'] {
.hamburger-icon {
display: none;
}
.close-icon {
display: block;
}
}
}
@media (max-width: 640px) {
.nav-toggle {
display: flex;
}
// Hide the site name when the menu is open
.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 {
width: 100%;
}
.nav-right {
flex-wrap: wrap;
justify-content: flex-end;
}
.nav-links {
display: none !important;
flex-direction: column !important;
align-items: flex-start !important;
gap: 0 !important;
width: 100%;
order: 10; // push below nav-controls
padding: 1rem 0;
border-top: 1px solid var(--border-color);
margin-top: 0.5rem;
&.is-open {
display: flex !important;
}
.nav-item {
width: 100%;
padding: 0.75rem 0;
font-size: 1rem;
border-bottom: 1px solid var(--border-color);
&::after {
display: none;
}
}
}
}
.brand-text {
font-weight: 700;
font-size: 1.2rem;
letter-spacing: -0.01em;
}
.nav-links {
display: flex;
align-items: center;
gap: 1.5rem;
}
.nav-item {
font-weight: 500;
font-size: 0.95rem;
position: relative;
}
.nav-item::after {
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: -4px;
left: 0;
background-color: var(--accent-color);
transition: var(--transition-smooth);
}
.nav-item:hover::after {
width: 100%;
}
+14
View File
@@ -0,0 +1,14 @@
// Hero Section
.hero {
padding: 8rem 0 6rem 0;
max-width: 800px;
}
.hero-title {
color: var(--text-color);
}
.hero-subtitle {
font-size: clamp(1.1rem, 1.5vw, 1.3rem);
color: var(--text-muted);
}
+37
View File
@@ -0,0 +1,37 @@
// Layout Constraints
.site-container {
width: 100%;
max-width: 1400px;
margin: 0 auto;
padding: 0 5vw;
display: flex;
flex-direction: column;
min-height: 100dvh;
box-sizing: border-box;
}
// Constrain all media & tables to their container
img,
video,
iframe,
table,
svg {
max-width: 100%;
}
table {
display: block;
overflow-x: auto;
@media (max-width: 640px) {
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
}
// Main Content
.main-content {
flex-grow: 1;
padding-bottom: 5rem;
}
+58
View File
@@ -0,0 +1,58 @@
// Global Reset & Base
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-family: var(--font-sans);
font-size: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
background-color: var(--bg-color);
color: var(--text-color);
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition:
background-color 0.3s ease,
color 0.3s ease;
// Firefox
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
}
// Webkit
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--scrollbar-track);
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border-radius: 0; // hard edges = terminal feel
border: 1px solid var(--scrollbar-track);
&:hover {
background: var(--scrollbar-thumb-hover);
}
}
body {
line-height: 1.7;
min-height: 100dvh;
display: flex;
flex-direction: column;
overflow-wrap: break-word; // break long words instead of overflowing
word-break: break-word;
}
a {
color: inherit;
text-decoration: none;
transition: var(--transition-smooth);
}
+60
View File
@@ -0,0 +1,60 @@
// Scroll to Top Floating Button
.scroll-to-top {
position: fixed;
bottom: 2rem;
right: 2rem;
display: flex;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
border-radius: 50%;
background: var(--bg-color);
color: var(--accent-color);
border: 1px solid var(--accent-color);
cursor: pointer;
z-index: 100;
// Hidden state by default
opacity: 0;
visibility: hidden;
transform: translateY(1rem);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
// Visible state triggered by JS
&.visible {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
@media (hover: hover) {
&:hover {
background: var(--accent-color);
color: var(--bg-color);
transform: translateY(-0.25rem);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2); // Emerald glow matching accent
}
&:hover svg {
transform: translateY(-2px);
}
}
&:focus-visible {
outline: 2px solid var(--accent-color);
outline-offset: 4px;
}
svg {
transition: transform 0.2s ease;
}
// Responsive padding for mobile devices
@media (max-width: 768px) {
bottom: 1.5rem;
right: 1.5rem;
width: 2.75rem;
height: 2.75rem;
}
}
+151
View File
@@ -0,0 +1,151 @@
// Search Modal Styles
.search-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
z-index: 10000;
display: flex;
justify-content: center;
align-items: flex-start;
padding-top: 10vh;
opacity: 0;
visibility: hidden;
transition:
opacity 0.2s ease,
visibility 0.2s ease;
&.active {
opacity: 1;
visibility: visible;
.search-modal {
transform: translateY(0) scale(1);
}
}
}
.search-modal {
width: 90%;
max-width: 600px;
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 12px;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
transform: translateY(-20px) scale(0.98);
transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
display: flex;
flex-direction: column;
overflow: hidden;
}
.search-header {
display: flex;
align-items: center;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--border-color);
background: var(--surface-color);
svg {
color: var(--text-muted);
margin-right: 0.75rem;
}
}
#search-input {
flex: 1;
background: transparent;
border: none;
color: var(--text-color);
font-family: var(--font-sans);
font-size: 1.1rem;
outline: none;
&::placeholder {
color: var(--text-muted);
}
}
.search-footer {
padding: 0.5rem 1.25rem;
background: var(--surface-color);
border-top: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.75rem;
color: var(--text-muted);
kbd {
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 0.1rem 0.3rem;
font-family: var(--font-mono);
font-size: 0.7rem;
margin: 0 0.2rem;
}
}
.search-results {
max-height: 50vh;
overflow-y: auto;
padding: 0.5rem;
// Custom scrollbar
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: var(--border-color);
border-radius: 4px;
}
.search-result-item {
display: flex;
flex-direction: column;
padding: 0.75rem 1rem;
border-radius: 8px;
text-decoration: none;
color: var(--text-color);
transition: background-color 0.15s ease;
border-left: 2px solid transparent;
&:hover,
&.selected {
background-color: var(--surface-color);
border-left-color: var(--accent-color);
}
.result-title {
font-weight: 500;
color: var(--accent-color);
margin-bottom: 0.25rem;
}
.result-summary {
font-size: 0.85rem;
color: var(--text-muted);
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
.no-results {
padding: 2rem;
text-align: center;
color: var(--text-muted);
font-style: italic;
}
}
+31
View File
@@ -0,0 +1,31 @@
// Component: Theme Toggle
.theme-toggle {
background: none;
border: none;
color: var(--text-color);
cursor: pointer;
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: var(--transition-smooth);
svg {
width: 20px;
height: 20px;
}
}
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;
}
+55
View File
@@ -0,0 +1,55 @@
/*
* Cross-Document View Transitions
* Enabled via <meta name="view-transition" content="same-origin" />
*/
// Disable default CSS transitions on the whole page during a view transition
// so they don't fight with the View Transition API animation.
html::view-transition-group(*) {
animation-duration: 0.3s;
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}
// Custom animation for the old page leaving
@keyframes fade-out-scale {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.98);
}
}
// Custom animation for the new page entering
@keyframes fade-in-scale {
from {
opacity: 0;
transform: scale(1.02);
}
to {
opacity: 1;
transform: scale(1);
}
}
// Apply the custom animations to the root view transition pseudo-elements
html::view-transition-old(root) {
animation: fade-out-scale 0.25s cubic-bezier(0.4, 0, 1, 1) both;
}
html::view-transition-new(root) {
animation: fade-in-scale 0.35s cubic-bezier(0, 0, 0.2, 1) both;
// Delay slightly so the old page fades down first before the new one pops over
animation-delay: 0.05s;
}
// Respect user preference for reduced motion
@media (prefers-reduced-motion: reduce) {
html::view-transition-group(*),
html::view-transition-old(*),
html::view-transition-new(*) {
animation: none !important;
}
}
+182
View File
@@ -0,0 +1,182 @@
// Typography Scale (Fluid)
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.02em;
margin-bottom: 0.5em;
overflow-wrap: break-word;
word-break: break-word;
text-wrap: balance;
}
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;
}
h4 {
font-size: clamp(1.05rem, 1.5vw, 1.3rem);
}
h5 {
font-size: clamp(1rem, 1.25vw, 1.15rem);
}
h6 {
font-size: clamp(0.9rem, 1vw, 1rem);
}
p,
ul,
ol,
figure,
blockquote {
margin-bottom: 1.5rem;
}
p {
max-width: 65ch; // Optimal reading width
text-wrap: pretty;
}
// Footnotes
.footnote-reference {
font-size: 0.75em;
vertical-align: super;
line-height: 0;
a {
color: var(--accent-color);
font-family: var(--font-mono);
padding: 0 0.15em;
&:hover {
text-decoration: underline;
}
}
}
.footnote-definition {
display: flex;
gap: 0.75rem;
margin-bottom: 0.75rem;
font-size: 0.875rem;
color: var(--text-muted);
border-left: 2px solid var(--border-color);
padding-left: 1rem;
p {
margin: 0;
max-width: none;
}
}
.footnote-definition-label {
flex-shrink: 0;
font-family: var(--font-mono);
color: var(--accent-color);
font-size: 0.8em;
margin-top: 0.1em;
}
.footnote-backref {
font-family: var(--font-mono);
color: var(--accent-color);
font-size: 0.9em;
text-decoration: none;
opacity: 0.7;
transition: opacity 0.2s ease;
&:hover {
opacity: 1;
}
}
code,
pre {
font-family: var(--font-mono);
font-size: 0.9em;
font-feature-settings:
'liga' 1,
'calt' 1;
font-variant-ligatures: contextual;
}
pre {
position: relative;
padding: 1.25rem;
border-radius: 8px;
overflow-x: auto;
max-width: 100%;
border: 1px solid var(--border-color);
background: #1e1e2e;
// Scrollbars visible on desktop, hidden on mobile
@media (max-width: 640px) {
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
font-size: 0.82em;
}
}
.copy-code-btn {
position: absolute;
top: 0.5rem;
right: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: rgba(255, 255, 255, 0.1);
color: #cdd6f4;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
cursor: pointer;
opacity: 1;
transition:
background 0.2s ease,
color 0.2s ease;
svg {
width: 16px;
height: 16px;
}
&:hover {
background: rgba(255, 255, 255, 0.2);
color: #ffffff;
}
&.copied {
background: var(--accent-color);
border-color: var(--accent-color);
color: var(--bg-color);
}
}
// Button is always visible, no hover-to-reveal needed
code {
background: var(--surface-color);
padding: 0.2em 0.4em;
border-radius: 4px;
color: var(--accent-color);
border: 1px solid var(--border-color);
}
pre code {
background: transparent;
color: inherit;
padding: 0;
border: none;
}
+8
View File
@@ -0,0 +1,8 @@
// Utilities / Interactions
.hover-scale {
transition: var(--transition-spring);
&:active {
transform: scale(0.98);
}
}
+35
View File
@@ -0,0 +1,35 @@
// Premium Design Variables
:root {
--bg-color: #f9fafb;
--text-color: #1a1a1a;
--text-muted: #6b7280;
--accent-color: #10b981; // Emerald
--accent-hover: #059669; // Emerald darker
--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);
--scrollbar-track: #e5e7eb;
--scrollbar-thumb: #9ca3af;
--scrollbar-thumb-hover: #10b981;
}
// Dark Mode Overrides
html.dark {
--bg-color: #0a0a0a;
--text-color: #f9fafb;
--text-muted: #a1a1aa;
--surface-color: #141414;
--border-color: rgba(255, 255, 255, 0.1);
--shadow-color: rgba(0, 0, 0, 0.5);
--scrollbar-track: #0f0f0f;
--scrollbar-thumb: #2a2a2a;
--scrollbar-thumb-hover: #10b981;
}
+18
View File
@@ -0,0 +1,18 @@
// Main SCSS entry point
// Organized by feature and section for maintainability
@import 'variables';
@import 'reset';
@import 'typography';
@import 'layout';
@import 'header';
@import 'theme';
@import 'hero';
@import 'feed';
@import 'article';
@import 'footer';
@import 'search';
@import 'transitions';
@import 'utilities';
@import 'scroll-top';
@import '404';
-362
View File
@@ -1,362 +0,0 @@
/* TODO: Future considerations.
- Reset all margin and padding for * to 0.
- Use the <abbr> tag for abbreviations.
- Support footnotes or asides for notes sections.
- Serve fonts from the local server rather than Google's API.
- Support a "display title" in an [extra] section to allow for <br> elements
or other manual control over title display. */
@charset "utf-8";
@import url('https://fonts.googleapis.com/css?family=Noto+Sans|Noto+Serif|Noto+Sans+Mono');
:root {
/* Fonts and Text Sizes */
--default-serif-font: "Noto Serif", serif;
--default-sans-font: "Noto Sans", sans-serif;
--default-mono-font: "Noto Sans Mono", monospace;
--default-font-size: 14px;
--header-font-size: 2.0em;
/* Not sure I like this as the default, but it keeps inline code blocks from
overlapping. */
--default-line-height: 1.5;
/* Gruvbox Dark Colors */
--background-color: #1d2021;
--foreground-color: #ebdbb2;
--link-color: #458588;
--muted-color: #928374;
--primary-accent-color: #b16286;
--secondary-accent-color: #689d6a;
--code-background-color: #282828;
--inline-code-background-color: #3c3836;
}
.accent {
color: var(--primary-accent-color);
}
/* Required for sticky footer. */
html,
body {
height: 100%;
}
body {
display: flex;
/* Required for sticky footer. */
flex-direction: column;
/* Required for sticky footer. */
margin: 0;
line-height: var(--default-line-height);
font-size: var(--default-font-size);
font-family: var(--default-serif-font);
background-color: var(--background-color);
color: var(--foreground-color);
border-top: 0.5em solid var(--primary-accent-color);
}
main {
flex: 1 0 auto;
/* Required for sticky footer. */
}
/* FIXME: For reasons that utterly bewilder me, an <article> with this id needs
to be inside a <main> with no id. I have a strong feeling I've done
something wrong here. */
#content {
max-width: 48em;
margin-left: auto;
margin-right: auto;
padding-right: 1.4em;
padding-left: 1.4em;
padding-bottom: 1.4em;
}
header {
overflow: hidden;
/* FIXME: This WILL cut off titles that are too long! */
margin-top: 1.4em;
margin-bottom: 1.4em;
margin-left: 1em;
margin-right: 1em;
}
header>div.logo {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
header>h1 {
text-align: center;
font-size: var(--header-font-size);
font-family: var(--default-sans-font);
padding-top: 2.8rem;
margin-top: 0;
margin-bottom: 0.7rem;
}
header .post-meta {
text-align: center;
padding-top: 0.5em;
padding-bottom: 0.9em;
margin-bottom: 0;
}
.post-meta time {
// TODO: This color doesn't look great in with a light color scheme...
color: var(--muted-color);
}
h2,
h3,
h4,
h5,
h6 {
padding-top: 1.2rem;
margin-bottom: 0.2rem;
}
p {
padding-top: 0.4em;
margin-bottom: 1em;
}
blockquote {
/* Default text uses serif, so use sans-serif to differentiate quoted text. */
font-family: var(--default-sans-font);
font-style: italic;
/* Indent blockquotes, but align the start with where text in code begins. */
margin: 0 1.25rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
border-left: 3px solid var(--secondary-accent-color);
}
a {
color: var(--link-color);
text-decoration: none;
}
/* ---- Anchor links --------------------------------------------------------*/
/* Add a # to the left of a subheading when it references an internal link... */
:where(h2, h3, h4, h5, h6)>a[href^="#"]::before {
content: "#";
display: inline-block;
width: 1.2rem;
margin-left: -1.2rem;
opacity: 0;
}
/* ... but only display the internal anchor link when hovered over. */
:where(h2, h3, h4, h5, h6):hover>a[href^="#"]::before {
opacity: 1;
}
/* When internal links are linked directly, display with the accent color. */
:where(h2, h3, h4, h5, h6):target>a[href^="#"]::before {
opacity: 1;
color: var(--primary-accent-color);
}
/* ---- Media ---------------------------------------------------------------*/
img.logo {
width: 64px;
height: 64px;
}
img,
video {
/* Media should fill the width of its container if possible. */
width: 100%;
height: auto;
}
figure {
/* Don't add margins to figures. */
margin: 0;
}
figcaption {
/* De-emphasize figure captions. */
font-size: 0.85rem;
color: var(--muted-color);
}
/* ---- Code blocks ---------------------------------------------------------*/
pre {
display: block;
padding: 0.25rem 1.25rem;
font-size: 85%;
border-left: 0.25rem solid var(--primary-accent-color);
}
code {
padding: 0 0.4rem;
// background-color: #3c3836;
background-color: var(--inline-code-background-color);
/* Inline code blocks look less aggresive with a little rounding. */
border-radius: 2px;
}
pre,
code {
overflow: auto;
font-family: var(--default-mono-font);
}
/* Without this the <code> tag gains a little padding that offsets text by like
2 pixels. Let's *not* do that. */
pre>code {
padding: 0;
background-color: var(--code-background-color);
// Code inside a pre block doesn't need rounded borders.
border-radius: unset;
}
/* ---- Tables --------------------------------------------------------------*/
table {
/* FIXME: Use flexbox for centering? Frankly, this is still bad on mobile.
display: flex;
justify-content: center;
*/
display: block;
overflow-x: auto;
margin-left: auto;
margin-right: auto;
text-align: left;
border-collapse: collapse;
}
/* Headers in a row (column scoped) get a colored bottom border. */
th[scope="col"] {
padding-top: 0.4em;
border-bottom: 3px solid var(--secondary-accent-color);
}
/* Headers in a column (row scoped) get a colored right border. */
th[scope="row"] {
border-right: 3px solid var(--secondary-accent-color);
}
td {
text-align: center;
border: 1px solid var(--muted-color);
}
th,
td {
vertical-align: bottom;
padding-left: 0.4em;
padding-right: 0.4em;
border: 1px solid var(--muted-color);
}
/* ---- Footer, nav, and copyright ------------------------------------------*/
footer {
border-top: 0.5em solid var(--primary-accent-color);
text-align: center;
/* FIXME: Using the code background color could look awkward if a post ends
in a code snippet. */
background-color: var(--code-background-color);
padding: 1.4em;
flex-shrink: 0;
/* Required for sticky footer. */
}
footer a {
color: var(--secondary-accent-color);
}
/* Give footer nav links some breathing room. */
footer>nav>a {
// margin: 0 1.4em;
margin: 0 0.4em;
}
/* FIXME: This could probably go away with global margin/padding adjustments. */
footer p {
padding-top: 0;
}
footer p,
footer a {
font-family: var(--default-sans-font);
/* It doesn't seem as though Google fonts support direct usage of OpenType
features like 'c2sc' and 'smcp'.
FIXME: It's not clear why numerals don't seem to obey this property. */
font-variant-caps: all-small-caps;
}
/* ---- Media Queries ------------------------------------------------------ */
@media(min-width: 427px) {
:root {
--default-font-size: 16px
}
}
@media(min-width: 470px) {
:root {
--default-font-size: 17px;
}
/* Above this size, increase the header margins. Smaller sizes benefit from
extra breathing room in the title. */
header {
margin-left: 2.8em;
margin-right: 2.8em;
}
header>div.logo {
flex-direction: row;
}
img.logo {
margin-right: 0.8em;
}
}
@media(min-width: 625px) {
:root {
--default-font-size: 18px;
}
}
@media(min-width: 802px) {
:root {
--default-font-size: 19px;
}
}
@media(min-width: 1003px) {
:root {
--default-font-size: 20px;
}
}
@media(min-width: 1225px) {
:root {
--default-font-size: 21px;
}
/* On larger devices, give the anchor link some more space. */
:where(h2, h3, h4, h5, h6)>a[href^="#"]::before {
width: 1.8rem;
margin-left: -1.8rem;
}
}
@media(min-width: 1496px) {
:root {
--default-font-size: 22px;
}
}
+68
View File
@@ -0,0 +1,68 @@
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'node:child_process';
// Get staged files from git
const stagedFiles = execSync('git diff --cached --name-only --diff-filter=ACMR')
.toString()
.split('\n')
.filter(Boolean);
// Filter to only new or modified posts
const postFiles = stagedFiles.filter(
(file) => file.startsWith('content/posts/') && file.endsWith('.md') && !file.endsWith('_index.md'),
);
if (postFiles.length === 0) {
process.exit(0);
}
let hasError = false;
for (const file of postFiles) {
const basename = path.basename(file, '.md');
// 1. Slug validation (kebab-case: lowercase letters, numbers, and hyphens)
if (!/^[a-z0-9\-]+$/.test(basename)) {
console.error(`[ERROR] in ${file}:`);
console.error(` Filename must be kebab-case (e.g. 'my-cool-post.md').`);
hasError = true;
}
// 2. Front-matter validation
const content = fs.readFileSync(file, 'utf8');
// Zola uses +++ for TOML front-matter
const frontMatterMatch = content.match(/^\+\+\+([\s\S]*?)\+\+\+/);
if (!frontMatterMatch) {
console.error(`[ERROR] in ${file}:`);
console.error(` Missing TOML front-matter (must be enclosed in +++).`);
hasError = true;
continue;
}
const frontMatter = frontMatterMatch[1];
const hasTitle = /^title\s*=\s*".+"/m.test(frontMatter);
const hasDate = /^date\s*=\s*\d{4}-\d{2}-\d{2}/m.test(frontMatter);
if (!hasTitle) {
console.error(`[ERROR] in ${file}:`);
console.error(` Missing 'title' field in front-matter (e.g. title = "My Post").`);
hasError = true;
}
if (!hasDate) {
console.error(`[ERROR] in ${file}:`);
console.error(` Missing 'date' field in front-matter (e.g. date = 2024-01-01).`);
hasError = true;
}
}
if (hasError) {
console.error('\n[FATAL] Commit rejected due to validation errors. Please fix the issues above and try again.\n');
process.exit(1);
} else {
console.log('[SUCCESS] Post validation passed!');
}
BIN
View File
Binary file not shown.
-54
View File
@@ -1,54 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>50x Borked</title>
<meta name="author" content="Reilly Tucker Siemens">
<meta name="description" content="50x Borked">
<meta property="og:site_name" content="tuckersiemens.com">
<meta property="og:locale" content="en-us">
<meta property="og:title" content="50x Borked">
<meta property="og:description" content="50x Borked">
<meta property="og:image" content="https://tuckersiemens.com/self.jpg">
<meta property="twitter:card" content="summary">
<meta property="twitter:site" content="@TuckerSiemens">
<meta property="twitter:title" content="50x Borked">
<meta property="twitter:description" content="50x Borked">
<meta property="twitter:image" content="https://tuckersiemens.com/self.jpg">
<link rel="dns-prefetch" href="https://fonts.googleapis.com/">
<link rel="stylesheet" href="/site.css">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
</head>
<body>
<main id="content">
<header>
<h1>50[0234] Borked</h1>
</header>
<figure>
<video src="50x.mp4" type="video/mp4" autoplay loop controls></video>
<figcaption>
Video from <a href="https://www.sakugabooru.com/post/show/174648">sakugabooru</a>
</figcaption>
</figure>
</main>
<footer>
<nav>
<a href="/">Home</a>
⬡
<a href="/posts">Posts</a>
⬡
<a href="/tags">Tags</a>
⬡
<a href="/contact">Contact</a>
</nav>
<p>
©
2021 Reilly Tucker Siemens ⬡
<a href="https://github.com/reillysiemens/tuckersiemens.com">Source</a>
⬡
<a href="/rss.xml">Feed</a>
</p>
</footer>
</body>
</html>
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
+66
View File
@@ -0,0 +1,66 @@
/* Outfit */
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url('../fonts/outfit/outfit-v15-latin-300.woff2') format('woff2');
}
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('../fonts/outfit/outfit-v15-latin-regular.woff2') format('woff2');
}
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('../fonts/outfit/outfit-v15-latin-500.woff2') format('woff2');
}
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('../fonts/outfit/outfit-v15-latin-600.woff2') format('woff2');
}
@font-face {
font-family: 'Outfit';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('../fonts/outfit/outfit-v15-latin-700.woff2') format('woff2');
}
/* JetBrains Mono (official release — full ligature support) */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('../fonts/jetbrains-mono/JetBrainsMono-Regular.woff2') format('woff2');
}
@font-face {
font-family: 'JetBrains Mono';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url('../fonts/jetbrains-mono/JetBrainsMono-Italic.woff2') format('woff2');
}
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('../fonts/jetbrains-mono/JetBrainsMono-Bold.woff2') format('woff2');
}
@font-face {
font-family: 'JetBrains Mono';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url('../fonts/jetbrains-mono/JetBrainsMono-BoldItalic.woff2') format('woff2');
}
+1128
View File
File diff suppressed because it is too large Load Diff
-3
View File
@@ -1,3 +0,0 @@
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg">
<polygon points="64 4, 124 32, 124 96, 64 124, 4 96, 4 32" fill="#b16286"/>
</svg>

Before

Width:  |  Height:  |  Size: 151 B

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More