Compare commits
27 Commits
2e136b5625
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 72c39d33f0 | |||
| 8de384f428 | |||
| 7118cd33ea | |||
| 13b3a496a8 | |||
| 001597ad82 | |||
| 51d69487b9 | |||
| 98278c443a | |||
| b65424a810 | |||
| 158613cb52 | |||
| 65ea907c73 | |||
| b3ad6d7114 | |||
| 7c819bc923 | |||
| e001c18cf6 | |||
| 44cb4045ba | |||
| ea7688016a | |||
| 2682ee7e69 | |||
| b293e98184 | |||
| f9f5625d5a | |||
| f8037aa233 | |||
| 0ba6c52d33 | |||
| 58fb765ec4 | |||
| 74e7f63ad5 | |||
| cf4a2e4e2f | |||
| a10c6f8a4f | |||
| b7c838213b | |||
| b69ee86b66 | |||
| 2cf7aa2575 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
.venv
|
||||
.idea
|
||||
.DS_Store
|
||||
|
||||
# Generated HTML files from markdown
|
||||
site/*.html
|
||||
!site/index.html
|
||||
63
CLAUDE.md
Normal file
63
CLAUDE.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a static website project ("Puercito Fiction" / "Hola") deployed using nginx in a Docker container. The site is automatically deployed to a VM via Gitea Actions when changes are pushed to the main branch.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Static Site**: HTML/CSS files located in `site/` directory
|
||||
- **Web Server**: nginx Alpine container serving static files
|
||||
- **Deployment**: Docker Compose orchestration with automated CI/CD via Gitea Actions
|
||||
- **nginx Configuration**: Custom config in `nginx.conf` with gzip compression, security headers, static asset caching, and SPA fallback routing
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
# Build and run the Docker container locally
|
||||
docker-compose up -d --build
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Stop the container
|
||||
docker-compose down
|
||||
|
||||
# Access the site
|
||||
# http://localhost:8080
|
||||
```
|
||||
|
||||
### Testing Changes
|
||||
|
||||
After making changes to files in `site/`, rebuild and restart:
|
||||
```bash
|
||||
docker-compose down && docker-compose up -d --build
|
||||
```
|
||||
|
||||
### Deployment
|
||||
|
||||
Deployment is automatic on push to main branch via `.gitea/workflows/deploy.yml`. The workflow:
|
||||
1. Checks out code on VM
|
||||
2. Pulls latest changes
|
||||
3. Rebuilds and restarts containers via `docker-compose`
|
||||
|
||||
Manual deployment requires SSH access to the VM with credentials stored in Gitea secrets.
|
||||
|
||||
## File Structure
|
||||
|
||||
- `site/` - Static website files (HTML, CSS)
|
||||
- `nginx.conf` - nginx server configuration
|
||||
- `Dockerfile` - Container image definition (nginx:alpine base)
|
||||
- `docker-compose.yml` - Container orchestration config
|
||||
- `.gitea/workflows/deploy.yml` - CI/CD pipeline configuration
|
||||
|
||||
## Important Notes
|
||||
|
||||
- The site runs on port 8080 locally (mapped from container port 80)
|
||||
- nginx config includes SPA fallback routing (`try_files $uri $uri/ /index.html`)
|
||||
- Static assets are cached for 1 year with immutable cache control
|
||||
- Health checks monitor nginx availability every 30 seconds
|
||||
11
content/about.md
Normal file
11
content/about.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# About
|
||||
|
||||
Puercito Fiction is a series of creative projects linked from this same
|
||||
homepage. For now, that is mainly web comics. The first of which is
|
||||
[She's Perfect, Actually!](https://shes-perfect-actually.puercito.net),
|
||||
a slice-of-gag strip following two silly ladies in a relationship.
|
||||
Really, one web comic.
|
||||
|
||||
The person behind it all is happily hiding behind a curtain. Please pay no
|
||||
attention them.
|
||||
|
||||
0
content/comics.md
Normal file
0
content/comics.md
Normal file
0
content/contact.md
Normal file
0
content/contact.md
Normal file
0
content/links.md
Normal file
0
content/links.md
Normal file
53
content/now.md
Normal file
53
content/now.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# "What are you up to now?"
|
||||
|
||||
This page describes what I am currently up to. You can find other
|
||||
"Now" pages on [nownownow.com](https://nownownow.com).
|
||||
|
||||
> Updated on Thursday, 20th of November, 2025
|
||||
|
||||
# Getting Started
|
||||
|
||||
I am doing the preparation for Puercito Fiction: setting up the website(s), signing up for accounts,
|
||||
figuring out a publishing workflow, etc.
|
||||
|
||||
Once all that is settled, I will focus on sharing what I am working on. By that, I mean more word-of-mouth stuff (e.g. family/friends, group chats, small
|
||||
communities).
|
||||
|
||||
# Comics
|
||||
|
||||
I am publishing [She's Perfect, Actually!](https://shes-perfect-actually.puercito.net), a gag web comic about
|
||||
a couple of silly ladies in a relationship.
|
||||
|
||||
This is moreso practice for getting into the regular habit of planning, drawing, and sharing entries on a regular basis. That said,
|
||||
I do like the idea of the strip morphing into something a little more narrative driven and even experimental
|
||||
at some point. For now, I am keeping things focused by making one strip per week. This is the easiest way to
|
||||
manage with a day job consisting of weird hours and other hobbies I have outside this site.
|
||||
|
||||
## Eventually
|
||||
|
||||
I will be putting out a proper, narrative web comic. The current conundrum is figuring out which one. I am caught
|
||||
between two ideas:
|
||||
|
||||
* A dramedy about a college freshmen who finds herself navigating life after they inexplicably being turned into a boy.
|
||||
* A parable about a widow being banished from her village in post-Crusades Greece and being tormented by an unseen pursuer.
|
||||
|
||||
This being the internet, I wager which one of these would be more interesting to folks. Neither premise is intended to be
|
||||
particularly long-lasting. Though, I am aware how infamous such intentions are for webcomics. That is why I am carefully
|
||||
planning ahead.
|
||||
|
||||
These stories will be told vignette style, I reckon. Time is a precious thing and I want to tell the parts I care about.
|
||||
|
||||
This is all to say that both stories will be told! It is just a matter of which order and when. Watch this space.
|
||||
|
||||
# Planning
|
||||
|
||||
As you may have guessed, I have a lot on my mind for what to deliver. Write now I'm creating outlines and schedules.
|
||||
Pretty soon I will be executing!
|
||||
|
||||
# Philosophy
|
||||
|
||||
I, like many people, am completely over platforms. Thus, I am striving to use smaller alternatives, ideally local.
|
||||
Where I can, I am self-hosting solutions. For everything else, I am producing it myself.
|
||||
|
||||
More than anything, I am harkening back to a time where the internet was more informal, less important, and less
|
||||
disruptive. A tad naive, but its a nice thought.
|
||||
30
content/projects.md
Normal file
30
content/projects.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Projects
|
||||
|
||||
Outside of comics, I have a some things going on. More creative stuff, mostly technical.
|
||||
|
||||
# Witch Seasoning
|
||||
|
||||
I have been working on writing a novel. Actually, I have the first draft completed. 40% of its content has been
|
||||
cut down from a word total of 130K words. Now I am onto the revising the work independently: consolidating plot
|
||||
points, refining language, etc.
|
||||
|
||||
Once I am onto a second draft, I think I'll open up expressions of interest for beta readers. Watch this space!
|
||||
|
||||
# Sunday
|
||||
|
||||
Sunday is a convenient website template for webcomics. It does _all_ the things.
|
||||
If you want to know in detail what it offers, checkout the [readme](https://git.puercito.net/mi/sunday).
|
||||
Though, you're probably better off seeing it in action with [She's Perfect, Actually!](https://shes-perfect-actually.puercito.net)
|
||||
|
||||
This project was born from a need to have easily replicated features for my (eventual) web comics. Originally,
|
||||
I was going to use [Rarebit](https://rarebit.neocities.org/). But when I found myself tinkering the code, I
|
||||
found I was practically rewriting the whole thing. So, I started from scratch. It is still a wonderful tool,
|
||||
though! Far friendlier to beginners than Sunday is, if I'm being honest.
|
||||
|
||||
# puercito.net
|
||||
|
||||
I consider the management of this site (and all subdomains) a project in and of itself. It's all sitting on a virtual machine where I manually
|
||||
configure all the things. That's a heap of responsibility this day in age when you consider how many platforms automate
|
||||
this stuff on your behalf. But I don't want to deal with them!
|
||||
|
||||
btw, the source code for this particular page is [open source](https://git.puercito.net/mi/hola).
|
||||
83
content/uses.md
Normal file
83
content/uses.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# "What do you use?"
|
||||
|
||||
It's always fun to see what tools other folks are using. This is my
|
||||
page to do the same. What you see is what I use for my various projects.
|
||||
|
||||
> Updated on Thursday, 20th of November, 2025
|
||||
|
||||
# Art
|
||||
|
||||
For all of my digital drawing and painting, I use Procreate for iPad with an Apple Pencil. _Surprise, surprise._ Currently, I am
|
||||
getting used to the [Comic Junkies Toolkit for Procreate](https://www.retrosupply.co/products/comic-junkies-toolkit-for-procreate)
|
||||
by RetroSupply Co.
|
||||
|
||||
I _do_ have a [Huion Kamvas Pro 16](https://store.huion.com/au/products/kamvas-pro-16?srsltid=AfmBOop330FPZUb_-0OODk4V3Et1I4l8Fow6zcyjevSchmh_xz1M-rGy)
|
||||
that I should be using (nice find from Facebook Marketplace). But my ambition to build a new desk
|
||||
is in the way of that. I will probably start using [Krita](https://krita.org/en/) once that is finally done.
|
||||
|
||||
My physical painting is done with either oil paints or gouache.
|
||||
|
||||
## Anti-AI
|
||||
|
||||
It is by no means bulletproof, but [Glaze](https://glaze.cs.uchicago.edu/index.html) by the University of Chicago
|
||||
is a promising tool for protecting images for AI training. Hopefully this paves the way for future platforms
|
||||
that are more effective.
|
||||
|
||||
# Technical Stuff
|
||||
|
||||
## This Website
|
||||
|
||||
It's actually just an HTML website. _However,_ the page content is
|
||||
generated from Markdown. The execution is done through a lil' Python
|
||||
script.
|
||||
|
||||
## The Comics
|
||||
|
||||
My comics are served via a custom template, [Sunday](/projects.html#sunday).
|
||||
The source code are stored is stored in my own [Gitea](https://about.gitea.com) instance.
|
||||
Images and other assets are tracked with [Git LFS](https://git-lfs.com/).
|
||||
|
||||
## Infrastructure
|
||||
|
||||
Getting technical, everything under `puercito.net` is hosted in a virtual machine over at [BinaryLane](https://binarylane.com.au).
|
||||
The sites are proxied on [Traefik Proxy](https://traefik.io/traefik), served through [Docker](https://docker.com).
|
||||
[BunnyCDN](https://bunny.net/) distributes the content globally with included anti-DDoS. My domains are purchased through
|
||||
[Namecheap](https://namecheap.com) who provides identity privacy. Their [PrivateEmail](https://www.namecheap.com/hosting/email/)
|
||||
is a cost-effective solution.
|
||||
|
||||
I access the infrastructure with [TwinGate](https://www.twingate.com/), a really simple VPN-like thingy.
|
||||
|
||||
## Code
|
||||
|
||||
I subscribe annually to [JetBrains](https://www.jetbrains.com/). Mostly, PyCharm is the IDE I spend my time in
|
||||
since that is my preferred platform. Occasionally I find myself in WebStorm. I don't do so much actual writing
|
||||
of code so much as handing my requirements to [Claude Code](https://www.claude.com/product/claude-code) for
|
||||
execution.
|
||||
|
||||
> To be **absolutely** clear, I _only_ use Claude for technical work!
|
||||
|
||||
## Software
|
||||
|
||||
My writing is done in [Scrivener](https://www.literatureandlatte.com/scrivener/overview). My works are backed up
|
||||
to a free-tier DropBox account. Google Drive is where I store everything.
|
||||
|
||||
The newsletter is handled through [PencilBooth](https://pencilbooth.com/), a provider specifically tiered to visual artists.
|
||||
|
||||
I use a self-hosted instance of [Wekan](https://wekan.fi/) for the limited task management I do.
|
||||
|
||||
YouTube is where I go to listen to ambient stuff while working on my projects. I am particular fan of
|
||||
[Aurora Heaven](https://www.youtube.com/@aurora.heaven18). Them is some vibes.
|
||||
|
||||
## Hardware
|
||||
|
||||
My laptop is a 2024 MacBook Pro 13" with an Apple M4 processor, 24GB memory. It usually sits on my lap. When I am
|
||||
at my desk, I use my Logitech [POP Keys](https://www.logitech.com/en-au/shop/p/pop-keys-wireless-mechanical) and
|
||||
[POP Mouse](https://www.logitech.com/en-au/shop/p/pop-wireless-mouse) gifted to me for Valentine's Day. I have a single
|
||||
external monitor (not counting the Huion). The laptop sits closed and slotted into a timber stand I hand-made. Why yes,
|
||||
I am not a multi-monitor person.
|
||||
|
||||
For focusing, I have a pair of [Sony WH1000XM3](https://store.sony.com.au/archived-headphones-noisecancelling/WH1000XM3B.html)
|
||||
Wireless Noise Cancelling Headphones (White) to block out (external) distraction. The pads wore after several years of use. So,
|
||||
I installed some [replacements from Amazon](https://www.amazon.com.au/dp/B0CTR2NKJS).
|
||||
|
||||
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
markdown>=3.4.0
|
||||
220
scripts/build.py
Executable file
220
scripts/build.py
Executable file
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Markdown to HTML converter for Puercito Fiction site.
|
||||
Converts markdown files from content/ directory to HTML files in site/ directory.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import markdown
|
||||
|
||||
|
||||
# HTML template for generated pages
|
||||
HTML_TEMPLATE = """<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{title} - Puercito Fiction</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Permanent+Marker&family=Special+Elite&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<style>
|
||||
body.page {{
|
||||
background-color: {bg_color};
|
||||
background-image: none;
|
||||
align-items: flex-start;
|
||||
padding: 2rem;
|
||||
}}
|
||||
|
||||
.page-container {{
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
}}
|
||||
|
||||
.back-link {{
|
||||
font-family: 'Special Elite', monospace;
|
||||
font-size: 1rem;
|
||||
color: #292929;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin-bottom: 2rem;
|
||||
transition: color 0.3s ease;
|
||||
}}
|
||||
|
||||
.back-link:hover {{
|
||||
color: #E6507D;
|
||||
}}
|
||||
|
||||
.page-content {{
|
||||
font-family: 'Special Elite', monospace;
|
||||
line-height: 1.8;
|
||||
color: #191919;
|
||||
}}
|
||||
|
||||
.page-content h1 {{
|
||||
font-family: 'Permanent Marker', cursive;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #191919;
|
||||
}}
|
||||
|
||||
.page-content h2 {{
|
||||
font-family: 'Permanent Marker', cursive;
|
||||
font-size: 1.8rem;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #191919;
|
||||
}}
|
||||
|
||||
.page-content h3 {{
|
||||
font-family: 'Permanent Marker', cursive;
|
||||
font-size: 1.4rem;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #191919;
|
||||
font-weight: 700;
|
||||
}}
|
||||
|
||||
.page-content p {{
|
||||
margin-bottom: 1rem;
|
||||
}}
|
||||
|
||||
.page-content strong {{
|
||||
font-weight: 700;
|
||||
}}
|
||||
|
||||
.page-content a {{
|
||||
color: #E6507D;
|
||||
text-decoration: underline;
|
||||
}}
|
||||
|
||||
.page-content a:hover {{
|
||||
color: #c93d65;
|
||||
}}
|
||||
|
||||
.page-content ul, .page-content ol {{
|
||||
margin-left: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}}
|
||||
|
||||
.page-content li {{
|
||||
margin-bottom: 0.5rem;
|
||||
}}
|
||||
|
||||
.page-content blockquote {{
|
||||
margin: 1.5rem 0;
|
||||
padding: 1rem 1.5rem;
|
||||
border-left: 4px solid #E6507D;
|
||||
background-color: rgba(230, 80, 125, 0.05);
|
||||
font-style: italic;
|
||||
}}
|
||||
|
||||
.page-content blockquote p {{
|
||||
margin-bottom: 0.5rem;
|
||||
}}
|
||||
|
||||
.page-content blockquote p:last-child {{
|
||||
margin-bottom: 0;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body class="page">
|
||||
<div class="page-container">
|
||||
<a href="index.html" class="back-link">← Back to Home</a>
|
||||
<div class="page-content">
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Page background colors (muted primary and secondary colors)
|
||||
PAGE_COLORS = {
|
||||
'about': '#f5e3d9', # muted peach
|
||||
'comics': '#e3d9f5', # muted lavender
|
||||
'projects': '#f5f5d9', # muted yellow
|
||||
'links': '#d9f5e3', # muted teal
|
||||
'contact': '#f5d9d9', # muted coral
|
||||
'now': '#d9e3f5', # muted blue
|
||||
'uses': '#f5dfe3', # muted peach-pink
|
||||
}
|
||||
|
||||
|
||||
def convert_markdown_to_html(markdown_file: Path, output_dir: Path):
|
||||
"""
|
||||
Convert a single markdown file to HTML.
|
||||
|
||||
Args:
|
||||
markdown_file: Path to the markdown file
|
||||
output_dir: Directory to save the HTML file
|
||||
"""
|
||||
# Read markdown content
|
||||
with open(markdown_file, 'r', encoding='utf-8') as f:
|
||||
md_content = f.read()
|
||||
|
||||
# Convert markdown to HTML
|
||||
md = markdown.Markdown(extensions=['extra', 'codehilite', 'meta'])
|
||||
html_content = md.convert(md_content)
|
||||
|
||||
# Extract title from filename or first heading
|
||||
title = markdown_file.stem.capitalize()
|
||||
|
||||
# Get background color for this page (default to white if not specified)
|
||||
page_name = markdown_file.stem
|
||||
bg_color = PAGE_COLORS.get(page_name, '#ffffff')
|
||||
|
||||
# Fill template
|
||||
full_html = HTML_TEMPLATE.format(
|
||||
title=title,
|
||||
content=html_content,
|
||||
bg_color=bg_color
|
||||
)
|
||||
|
||||
# Output file path
|
||||
output_file = output_dir / f"{markdown_file.stem}.html"
|
||||
|
||||
# Write HTML file
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(full_html)
|
||||
|
||||
print(f"✓ Converted {markdown_file.name} → {output_file.name}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main build script."""
|
||||
# Define directories
|
||||
content_dir = Path('content')
|
||||
site_dir = Path('site')
|
||||
|
||||
# Ensure directories exist
|
||||
if not content_dir.exists():
|
||||
print(f"Error: {content_dir} directory not found")
|
||||
return
|
||||
|
||||
if not site_dir.exists():
|
||||
print(f"Creating {site_dir} directory...")
|
||||
site_dir.mkdir(parents=True)
|
||||
|
||||
# Find all markdown files
|
||||
md_files = list(content_dir.glob('*.md'))
|
||||
|
||||
if not md_files:
|
||||
print(f"No markdown files found in {content_dir}")
|
||||
return
|
||||
|
||||
print(f"Found {len(md_files)} markdown file(s)")
|
||||
print("-" * 50)
|
||||
|
||||
# Convert each markdown file
|
||||
for md_file in md_files:
|
||||
convert_markdown_to_html(md_file, site_dir)
|
||||
|
||||
print("-" * 50)
|
||||
print(f"Build complete! HTML files saved to {site_dir}/")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
118
scripts/watch.py
Normal file
118
scripts/watch.py
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Watch script for Puercito Fiction site.
|
||||
Monitors markdown files in content/ directory and automatically rebuilds when changes are detected.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
# Add parent directory to path so we can import build script
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
from build import convert_markdown_to_html, PAGE_COLORS
|
||||
|
||||
|
||||
class MarkdownChangeHandler(FileSystemEventHandler):
|
||||
"""Handler for markdown file changes."""
|
||||
|
||||
def __init__(self, content_dir: Path, site_dir: Path):
|
||||
self.content_dir = content_dir
|
||||
self.site_dir = site_dir
|
||||
self.last_modified = {}
|
||||
|
||||
def on_modified(self, event):
|
||||
"""Called when a file is modified."""
|
||||
if event.is_directory:
|
||||
return
|
||||
|
||||
file_path = Path(event.src_path)
|
||||
|
||||
# Only process .md files
|
||||
if file_path.suffix != '.md':
|
||||
return
|
||||
|
||||
# Debounce: ignore if file was modified less than 0.5 seconds ago
|
||||
current_time = time.time()
|
||||
if file_path in self.last_modified:
|
||||
if current_time - self.last_modified[file_path] < 0.5:
|
||||
return
|
||||
|
||||
self.last_modified[file_path] = current_time
|
||||
|
||||
print(f"\n📝 Change detected: {file_path.name}")
|
||||
self._rebuild_file(file_path)
|
||||
|
||||
def on_created(self, event):
|
||||
"""Called when a file is created."""
|
||||
if event.is_directory:
|
||||
return
|
||||
|
||||
file_path = Path(event.src_path)
|
||||
|
||||
# Only process .md files
|
||||
if file_path.suffix != '.md':
|
||||
return
|
||||
|
||||
print(f"\n✨ New file created: {file_path.name}")
|
||||
self._rebuild_file(file_path)
|
||||
|
||||
def _rebuild_file(self, markdown_file: Path):
|
||||
"""Rebuild a single markdown file."""
|
||||
try:
|
||||
convert_markdown_to_html(markdown_file, self.site_dir)
|
||||
print(f"✅ Build successful at {time.strftime('%H:%M:%S')}")
|
||||
except Exception as e:
|
||||
print(f"❌ Build failed: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main watch script."""
|
||||
# Define directories
|
||||
script_dir = Path(__file__).parent
|
||||
project_dir = script_dir.parent
|
||||
content_dir = project_dir / 'content'
|
||||
site_dir = project_dir / 'site'
|
||||
|
||||
# Ensure directories exist
|
||||
if not content_dir.exists():
|
||||
print(f"❌ Error: {content_dir} directory not found")
|
||||
return
|
||||
|
||||
if not site_dir.exists():
|
||||
print(f"Creating {site_dir} directory...")
|
||||
site_dir.mkdir(parents=True)
|
||||
|
||||
# Initial build
|
||||
print("🏗️ Running initial build...")
|
||||
print("-" * 50)
|
||||
md_files = list(content_dir.glob('*.md'))
|
||||
for md_file in md_files:
|
||||
convert_markdown_to_html(md_file, site_dir)
|
||||
print("-" * 50)
|
||||
print(f"✅ Initial build complete! Built {len(md_files)} file(s)\n")
|
||||
|
||||
# Setup file watcher
|
||||
event_handler = MarkdownChangeHandler(content_dir, site_dir)
|
||||
observer = Observer()
|
||||
observer.schedule(event_handler, str(content_dir), recursive=False)
|
||||
observer.start()
|
||||
|
||||
print("👀 Watching for changes in content/ directory...")
|
||||
print(" Press Ctrl+C to stop\n")
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n👋 Stopping watch script...")
|
||||
observer.stop()
|
||||
|
||||
observer.join()
|
||||
print("✅ Watch script stopped")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
19
site/images/icons/external.svg
Normal file
19
site/images/icons/external.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 95 95" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||
<g transform="matrix(1,0,0,1,-202,-302)">
|
||||
<g transform="matrix(1,0,0,1.33483,-100.76,-132.962)">
|
||||
<g id="external">
|
||||
<g transform="matrix(0.379294,0.284151,-0.49503,0.370855,441.997,206.474)">
|
||||
<path d="M153.268,210.759L198.269,249.283L108.267,249.283L153.268,210.759Z" style="stroke:black;stroke-width:3.21px;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.815276,0,0,0.610769,164.676,135.973)">
|
||||
<path d="M274.284,323.076L212.698,385.381" style="fill:none;stroke:black;stroke-width:14.72px;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.909309,0,0,0.68964,122.98,117.085)">
|
||||
<path d="M293.162,359.953L293.162,376.767C293.162,389.004 283.227,398.939 270.99,398.939L226.647,398.939C214.41,398.939 204.475,389.004 204.475,376.767L204.475,332.423C204.475,320.186 214.41,310.252 226.647,310.252L246.213,310.252" style="fill:none;stroke:black;stroke-width:13.12px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
22
site/images/icons/instagram.svg
Normal file
22
site/images/icons/instagram.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 92 91" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||
<g transform="matrix(1,0,0,1,-4,-205)">
|
||||
<g transform="matrix(1,0,0,1.33483,-100.76,-132.962)">
|
||||
<g id="instagram" transform="matrix(1,0,0,0.749157,0.759778,25.4431)">
|
||||
<g transform="matrix(1.01767,0,0,1,-3.43838,0)">
|
||||
<path d="M194.607,327.784L194.607,371.565C194.607,383.647 184.968,393.456 173.096,393.456L128.555,393.456C116.683,393.456 107.045,383.647 107.045,371.565L107.045,327.784C107.045,315.703 116.683,305.894 128.555,305.894L173.096,305.894C184.968,305.894 194.607,315.703 194.607,327.784Z" style="stroke:black;stroke-width:1.98px;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.01697,0,0,1.03858,-4.36275,-15.2693)">
|
||||
<path d="M184.225,334.884L184.225,366.62C184.225,375.378 176.964,382.488 168.02,382.488L135.406,382.488C126.462,382.488 119.201,375.378 119.201,366.62L119.201,334.884C119.201,326.127 126.462,319.016 135.406,319.016L168.02,319.016C176.964,319.016 184.225,326.127 184.225,334.884Z" style="fill:none;stroke:white;stroke-width:4.86px;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.788925,0,0,0.788925,30.7311,75.1905)">
|
||||
<circle cx="151.267" cy="348.286" r="15.731" style="fill:none;stroke:white;stroke-width:6.34px;"/>
|
||||
</g>
|
||||
<g transform="matrix(0.420046,0,0,0.420046,73.2469,196.477)">
|
||||
<circle cx="224.676" cy="321.02" r="6.275" style="fill:rgb(255,249,249);stroke:white;stroke-width:11.9px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
14
site/images/icons/mail.svg
Normal file
14
site/images/icons/mail.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 94 80" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||
<g transform="matrix(1,0,0,1,-203,-208)">
|
||||
<g transform="matrix(1,0,0,1.33483,-100.76,-132.962)">
|
||||
<g id="mail-" serif:id="mail " transform="matrix(1,0,0,0.749157,0.759778,25.4431)">
|
||||
<path d="M394.221,316.119L394.221,380.765C394.221,382.659 392.684,384.196 390.79,384.196L310.169,384.196C308.276,384.196 306.738,382.659 306.738,380.765L306.738,316.119C306.738,314.225 308.276,312.688 310.169,312.688L390.79,312.688C392.684,312.688 394.221,314.225 394.221,316.119Z" style="stroke:black;stroke-width:5px;"/>
|
||||
<g transform="matrix(-0.93402,1.14384e-16,-8.20038e-17,-0.669612,676.454,616.213)">
|
||||
<path d="M349.886,397.009L396.957,457.668L302.815,457.668L349.886,397.009Z" style="fill:none;stroke:white;stroke-width:6.15px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -6,13 +6,90 @@
|
||||
<title>Puercito Fiction</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Permanent+Marker&family=Inter:wght@300;400&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Permanent+Marker&family=Special+Elite&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script>
|
||||
// Randomize background color on page load
|
||||
const colors = [
|
||||
'#f5d9e3', // muted rose
|
||||
'#f5e3d9', // muted peach
|
||||
'#e3d9f5', // muted lavender
|
||||
'#f5f5d9', // muted yellow
|
||||
'#d9f5e3', // muted teal
|
||||
'#f5d9d9', // muted coral
|
||||
'#d9e3f5', // muted blue
|
||||
'#f5dfe3' // muted peach-pink
|
||||
];
|
||||
const randomColor = colors[Math.floor(Math.random() * colors.length)];
|
||||
document.documentElement.style.setProperty('--bg-color', randomColor);
|
||||
|
||||
// Randomize byline on page load
|
||||
const bylines = [
|
||||
'vamos a la casa',
|
||||
'population: 3 (7 if you count the dogs, 8 with the cat)',
|
||||
'don\'t look now it\'s right behind you',
|
||||
'time will forget (it\'s mutual)',
|
||||
'blink and you\'ll miss it (please don\'t)',
|
||||
'*whispers* everyone knows your business',
|
||||
'too abstract for maps',
|
||||
'who needs productivity when you can nap',
|
||||
'arros con gris y puerco lechon por favor',
|
||||
'established since eventually, perhaps',
|
||||
'this little piggie went weeeeeeeeeeeeeee',
|
||||
'I know you are but what am I (you are wonderful)',
|
||||
'androgynously being',
|
||||
'don\t do anything I wouldn\'t do',
|
||||
'wow you fit the whole thing in',
|
||||
'ask me about my very big hat',
|
||||
'what do you call a phone with a moustache',
|
||||
'I would bet on the 100 chihuahuas versus that t-rex',
|
||||
'sure you\'re a dog but what breed',
|
||||
'it\s dark in here',
|
||||
'are you gonna eat that',
|
||||
'my aunt did the same thing once',
|
||||
'did you hear the one about the silent vocalist',
|
||||
'what the puppy',
|
||||
'how now brown cow',
|
||||
'merp merp merp'
|
||||
];
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const randomByline = bylines[Math.floor(Math.random() * bylines.length)];
|
||||
document.querySelector('.byline').textContent = randomByline;
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Hola</h1>
|
||||
<p>Stay tuned for what's coming.</p>
|
||||
</main>
|
||||
<body style="background-color: var(--bg-color);">
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Puercito Fiction</h1>
|
||||
</header>
|
||||
|
||||
<nav>
|
||||
<a href="about.html">about</a>
|
||||
<a href="comics.html">comics</a>
|
||||
<a href="projects.html">projects</a>
|
||||
<a href="https://pencilbooth.com/puercito" target="_blank" rel="noopener noreferrer">newsletter<img src="images/icons/external.svg" alt="" class="external-icon"></a>
|
||||
<a href="links.html">links</a>
|
||||
<a href="contact.html">contact</a>
|
||||
<a href="now.html">now</a>
|
||||
<a href="uses.html">uses</a>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<p class="byline">where comic strips come to gag</p>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="social-links">
|
||||
<a href="https://instagram.com/puercito" target="_blank" rel="noopener noreferrer" aria-label="Instagram">
|
||||
<img src="images/icons/instagram.svg" alt="Instagram" width="20" height="20">
|
||||
</a>
|
||||
<a href="mailto:hello@puercito.net" aria-label="Email">
|
||||
<img src="images/icons/mail.svg" alt="Email" width="20" height="20">
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
122
site/styles.css
122
site/styles.css
@@ -5,29 +5,127 @@
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-family: 'Special Elite', monospace;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: #fef6e4;
|
||||
color: #191919;
|
||||
background-color: #f5d9e3;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 20rem;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Permanent Marker', cursive;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 400;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #191919;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.byline {
|
||||
font-family: 'Special Elite', monospace;
|
||||
font-size: 0.875rem;
|
||||
color: #666666;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
column-gap: 2rem;
|
||||
row-gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
nav a {
|
||||
font-family: 'Special Elite', monospace;
|
||||
font-size: 1rem;
|
||||
color: #292929;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
padding: 0.25rem 0;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
nav a::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 1px;
|
||||
background-color: #E6507D;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
color: #E6507D;
|
||||
}
|
||||
|
||||
nav a:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.external-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-left: 0.25rem;
|
||||
vertical-align: middle;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
nav a:hover .external-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
main {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Permanent Marker', cursive;
|
||||
font-size: 3rem;
|
||||
font-weight: 400;
|
||||
margin-bottom: 1rem;
|
||||
footer {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
font-weight: 300;
|
||||
.social-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.social-links a {
|
||||
color: #333333;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.social-links a:hover {
|
||||
color: #E6507D;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.social-links svg,
|
||||
.social-links img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user