diff --git a/THEMES-HOWTO.md b/THEMES-HOWTO.md
new file mode 100644
index 0000000..38232ab
--- /dev/null
+++ b/THEMES-HOWTO.md
@@ -0,0 +1,822 @@
+# qsgen3 Themes How-To Guide
+
+This guide explains how to create and customize themes for qsgen3, based on how the code actually interprets and processes themes.
+
+## Table of Contents
+
+1. [Theme Basics](#theme-basics)
+2. [Theme Directory Structure](#theme-directory-structure)
+3. [Creating Your First Theme](#creating-your-first-theme)
+4. [Layout Templates](#layout-templates)
+5. [Static Assets (CSS, JS, Images)](#static-assets-css-js-images)
+6. [Theme Configuration](#theme-configuration)
+7. [Advanced Theme Features](#advanced-theme-features)
+8. [Theme Best Practices](#theme-best-practices)
+9. [Troubleshooting](#troubleshooting)
+
+## Theme Basics
+
+### What is a Theme?
+
+A qsgen3 theme is a collection of layout templates and static assets (CSS, JavaScript, images) that define the visual appearance and structure of your generated website. Themes allow you to:
+
+- Customize the HTML structure of your pages
+- Apply custom CSS styling
+- Include JavaScript functionality
+- Override default layouts with theme-specific designs
+
+### How qsgen3 Processes Themes
+
+When you specify a theme in your `site.conf`, qsgen3 follows this processing order:
+
+1. **Theme Detection**: Looks for the theme directory at `themes/{theme_name}/`
+2. **Layout Override**: If the theme has a `layouts/` directory, it replaces the default layouts
+3. **Static File Copying**: Copies static files from both root `static/` and theme `static/` directories
+4. **CSS Linking**: Automatically links the theme's main CSS file in generated HTML
+
+## Theme Directory Structure
+
+### Standard Theme Structure
+
+```
+themes/
+└── your-theme-name/
+ ├── layouts/ # Optional: Custom layout templates
+ │ ├── index.html # Homepage layout
+ │ ├── post.html # Blog post layout
+ │ ├── page.html # Static page layout
+ │ └── rss.xml # RSS feed template
+ └── static/ # Theme's static assets
+ ├── css/
+ │ └── style.css # Main theme CSS
+ ├── js/
+ │ └── theme.js # Theme JavaScript
+ └── images/
+ └── logo.png # Theme images
+```
+
+### Alternative Structure (Legacy Support)
+
+For themes that don't use the `static/` subdirectory:
+
+```
+themes/
+└── your-theme-name/
+ ├── layouts/ # Optional: Custom layout templates
+ ├── css/ # CSS files directly in theme root
+ │ └── style.css
+ ├── js/ # JavaScript files
+ └── images/ # Image files
+```
+
+## Creating Your First Theme
+
+### Step 1: Create the Theme Directory
+
+```bash
+mkdir -p themes/my-theme/static/css
+mkdir -p themes/my-theme/static/js
+mkdir -p themes/my-theme/layouts
+```
+
+### Step 2: Create a Basic CSS File
+
+Create `themes/my-theme/static/css/style.css`:
+
+```css
+/* Basic theme styles */
+body {
+ font-family: 'Arial', sans-serif;
+ line-height: 1.6;
+ margin: 0;
+ padding: 0;
+ background-color: #f4f4f4;
+}
+
+header {
+ background: #333;
+ color: white;
+ padding: 1rem;
+ text-align: center;
+}
+
+header h1 a {
+ color: white;
+ text-decoration: none;
+}
+
+main {
+ max-width: 800px;
+ margin: 2rem auto;
+ padding: 0 1rem;
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+}
+
+article {
+ padding: 2rem;
+}
+
+footer {
+ text-align: center;
+ padding: 1rem;
+ background: #333;
+ color: white;
+ margin-top: 2rem;
+}
+```
+
+### Step 3: Configure Your Site
+
+Update your `site.conf`:
+
+```bash
+# Theme configuration
+site_theme="my-theme"
+site_theme_css_file="css/style.css"
+```
+
+### Step 4: Test Your Theme
+
+```bash
+./bin/qsgen3
+```
+
+Your site should now use your custom theme!
+
+## Layout Templates
+
+### Understanding Pandoc Templates
+
+qsgen3 uses Pandoc templates with special variable syntax:
+
+- `$variable$` - Simple variable substitution
+- `$if(variable)$...$endif$` - Conditional blocks
+- `$for(list)$...$endfor$` - Loop over lists
+- `$body$` - The main content (converted from Markdown)
+
+### Available Variables
+
+#### Site-wide Variables (from site.conf)
+- `$site_name$` - Your site's name
+- `$site_tagline$` - Your site's tagline
+- `$site_url$` - Your site's URL
+- `$current_year$` - Current year (auto-generated)
+
+#### Content Variables (from Markdown frontmatter)
+- `$title$` - Page/post title
+- `$author$` - Content author
+- `$date$` - Publication date
+- `$description$` - Page description
+- `$body$` - The converted Markdown content
+
+#### Special Variables
+- `$css$` - CSS file paths (handled automatically)
+- `$math$` - Math rendering support (if enabled)
+
+### Creating Custom Layouts
+
+#### Basic Post Layout (`layouts/post.html`)
+
+```html
+
+
+
+
+
+ $site_name$ - $title$
+
+
+ $if(date)$$endif$
+ $for(css)$
+
+ $endfor$
+
+
+
+
+
+
+ $title$
+ $if(author)$By: $author$
$endif$
+ $if(date)$Published: $date$
$endif$
+
+ $body$
+
+
+
+
+
+```
+
+#### Index Page Layout (`layouts/index.html`)
+
+```html
+
+
+
+
+
+ $site_name$ - $site_tagline$
+
+ $for(css)$
+
+ $endfor$
+
+
+
+ $site_name$
+ $site_tagline$
+
+
+ $body$
+
+
+
+
+```
+
+## Static Assets (CSS, JS, Images)
+
+### How Static Files Are Processed
+
+qsgen3 copies static files in this order:
+
+1. **Root Static Files**: Copies from `static/` to `output/static/`
+2. **Theme Static Files**: Copies from `themes/{theme}/static/` to `output/static/` (overwrites root files)
+
+This means theme files take precedence over root static files.
+
+### CSS File Linking
+
+The main theme CSS file is automatically linked in all generated HTML pages using the `--css` flag passed to Pandoc. The CSS path is determined by:
+
+1. `site_theme_css_file` setting in `site.conf` (recommended)
+2. `site_theme_css_path` setting (legacy, deprecated)
+
+Example configuration:
+```bash
+site_theme="my-theme"
+site_theme_css_file="css/style.css" # Relative to theme's static/ directory
+```
+
+This results in:
+- CSS file copied to: `output/static/css/style.css`
+- HTML links to: `/static/css/style.css`
+
+### Adding Additional Assets
+
+#### JavaScript Files
+
+Create `themes/my-theme/static/js/theme.js`:
+
+```javascript
+// Theme-specific JavaScript
+document.addEventListener('DOMContentLoaded', function() {
+ console.log('My theme loaded!');
+
+ // Add smooth scrolling
+ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
+ anchor.addEventListener('click', function (e) {
+ e.preventDefault();
+ document.querySelector(this.getAttribute('href')).scrollIntoView({
+ behavior: 'smooth'
+ });
+ });
+ });
+});
+```
+
+Include in your layout:
+
+```html
+
+```
+
+#### Images and Other Assets
+
+Place images in `themes/my-theme/static/images/` and reference them in your CSS or templates:
+
+```css
+.logo {
+ background-image: url('/static/images/logo.png');
+}
+```
+
+```html
+
+```
+
+## Theme Configuration
+
+### Required Configuration
+
+In your `site.conf`:
+
+```bash
+# Minimum theme configuration
+site_theme="theme-name" # Name of theme directory
+site_theme_css_file="css/main.css" # Path to main CSS file
+```
+
+### Optional Configuration
+
+```bash
+# Optional: Override default paths
+paths_layouts_dir="layouts" # Will be overridden if theme has layouts/
+paths_static_dir="static" # Root static directory
+paths_output_dir="output" # Output directory
+```
+
+### Theme-Specific Variables
+
+You can add custom variables to your `site.conf` and use them in templates:
+
+```bash
+# Custom theme variables
+theme_color_primary="#3498db"
+theme_color_secondary="#2c3e50"
+theme_font_family="'Roboto', sans-serif"
+```
+
+Use in templates:
+```html
+
+```
+
+## Advanced Theme Features
+
+### Responsive Design
+
+Create responsive themes using CSS media queries:
+
+```css
+/* Mobile-first approach */
+.container {
+ max-width: 100%;
+ padding: 1rem;
+}
+
+@media (min-width: 768px) {
+ .container {
+ max-width: 750px;
+ margin: 0 auto;
+ }
+}
+
+@media (min-width: 1024px) {
+ .container {
+ max-width: 970px;
+ }
+}
+```
+
+### Dark Mode Support
+
+```css
+/* Default (light) theme */
+:root {
+ --bg-color: #ffffff;
+ --text-color: #333333;
+ --accent-color: #3498db;
+}
+
+/* Dark mode */
+@media (prefers-color-scheme: dark) {
+ :root {
+ --bg-color: #1a1a1a;
+ --text-color: #e0e0e0;
+ --accent-color: #5dade2;
+ }
+}
+
+body {
+ background-color: var(--bg-color);
+ color: var(--text-color);
+}
+```
+
+### Typography and Web Fonts
+
+```css
+/* Import Google Fonts */
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
+
+body {
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ font-weight: 400;
+ line-height: 1.6;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-weight: 600;
+ line-height: 1.2;
+}
+```
+
+### Custom RSS Template
+
+Create `layouts/rss.xml` for custom RSS styling:
+
+```xml
+
+
+
+ $site_name$
+ $site_tagline$
+ $site_url$
+
+ en-us
+ $build_date$
+
+ $for(posts)$
+ -
+ $it.post_title$
+ $it.post_description$
+ $it.post_url$
+ $it.post_url$
+ $it.post_date_rfc$
+
+ $endfor$
+
+
+```
+
+## Theme Best Practices
+
+### 1. Design Principles
+
+- **Mobile-First**: Design for mobile devices first, then enhance for larger screens
+- **Accessibility**: Use semantic HTML, proper contrast ratios, and keyboard navigation
+- **Performance**: Optimize images, minimize CSS/JS, use efficient selectors
+- **Consistency**: Maintain consistent spacing, typography, and color schemes
+
+### 2. File Organization
+
+```
+themes/my-theme/
+├── static/
+│ ├── css/
+│ │ ├── main.css # Main theme styles
+│ │ ├── components.css # Component-specific styles
+│ │ └── utilities.css # Utility classes
+│ ├── js/
+│ │ ├── theme.js # Main theme JavaScript
+│ │ └── components/ # Component-specific JS
+│ └── images/
+│ ├── icons/ # Icon files
+│ └── backgrounds/ # Background images
+└── layouts/
+ ├── index.html # Homepage layout
+ ├── post.html # Blog post layout
+ ├── page.html # Static page layout
+ └── rss.xml # RSS feed template
+```
+
+### 3. CSS Architecture
+
+Use a modular approach:
+
+```css
+/* main.css */
+@import url('base.css'); /* Reset and base styles */
+@import url('layout.css'); /* Layout components */
+@import url('components.css'); /* UI components */
+@import url('utilities.css'); /* Utility classes */
+```
+
+### 4. Performance Optimization
+
+- **Minimize HTTP Requests**: Combine CSS/JS files when possible
+- **Optimize Images**: Use appropriate formats (WebP, SVG) and sizes
+- **Use CSS Custom Properties**: For maintainable theming
+- **Lazy Load**: Implement lazy loading for images and non-critical resources
+
+### 5. Browser Compatibility
+
+Test your theme across different browsers and devices:
+
+```css
+/* Provide fallbacks for modern CSS features */
+.card {
+ background: #ffffff; /* Fallback */
+ background: var(--card-bg, #ffffff); /* Custom property */
+}
+
+.grid {
+ display: block; /* Fallback */
+ display: grid; /* Modern */
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+}
+```
+
+## Troubleshooting
+
+### Common Issues
+
+#### 1. CSS Not Loading
+
+**Problem**: Your theme's CSS isn't being applied.
+
+**Solutions**:
+- Check that `site_theme_css_file` path is correct relative to theme's `static/` directory
+- Verify the CSS file exists at `themes/{theme}/static/{css_path}`
+- Check qsgen3 logs for CSS-related warnings
+- Ensure the theme directory name matches `site_theme` setting
+
+#### 2. Layouts Not Being Used
+
+**Problem**: Your custom layouts aren't being applied.
+
+**Solutions**:
+- Verify layouts exist in `themes/{theme}/layouts/`
+- Check that layout filenames match expected names (`index.html`, `post.html`, `page.html`)
+- Review qsgen3 logs for layout-related messages
+
+#### 3. Static Files Not Copying
+
+**Problem**: Images, JS, or other static files aren't appearing in output.
+
+**Solutions**:
+- Check that files are in `themes/{theme}/static/` directory
+- Verify file permissions are readable
+- Look for rsync/cp errors in qsgen3 logs
+- Ensure no .gitignore rules are excluding files
+
+#### 4. Template Variables Not Working
+
+**Problem**: Variables like `$site_name$` aren't being substituted.
+
+**Solutions**:
+- Check that variables are defined in `site.conf`
+- Verify variable names match exactly (case-sensitive)
+- Ensure Pandoc template syntax is correct
+- Test with a minimal template to isolate issues
+
+### Debugging Tips
+
+#### 1. Enable Debug Logging
+
+Run qsgen3 with verbose output to see detailed processing information:
+
+```bash
+# Enable debug logging (if supported by your qsgen3 version)
+QSG_LOG_LEVEL=DEBUG ./bin/qsgen3
+```
+
+#### 2. Test with Minimal Theme
+
+Create a minimal theme to isolate issues:
+
+```html
+
+
+
+
+ $title$
+
+
+
+ $title$
+ $body$
+
+
+```
+
+#### 3. Validate Generated HTML
+
+Check that your generated HTML is valid:
+
+```bash
+# Use HTML validator tools
+htmlhint output/*.html
+# or
+w3c-validator output/index.html
+```
+
+#### 4. Check File Permissions
+
+Ensure qsgen3 can read your theme files:
+
+```bash
+find themes/my-theme -type f -not -perm -644
+find themes/my-theme -type d -not -perm -755
+```
+
+### Getting Help
+
+If you encounter issues not covered here:
+
+1. Check the qsgen3 documentation and examples
+2. Review existing themes in the `themes/` directory
+3. Examine the qsgen3 source code for theme processing logic
+4. Create a minimal reproduction case
+5. Report issues with detailed logs and configuration
+
+---
+
+## Example: Complete Theme Creation
+
+Here's a complete example of creating a modern, responsive theme called "modern-blog":
+
+### 1. Create Directory Structure
+
+```bash
+mkdir -p themes/modern-blog/{layouts,static/{css,js,images}}
+```
+
+### 2. Create Main CSS (`themes/modern-blog/static/css/style.css`)
+
+```css
+/* Modern Blog Theme */
+:root {
+ --primary-color: #2563eb;
+ --secondary-color: #64748b;
+ --background-color: #ffffff;
+ --text-color: #1e293b;
+ --border-color: #e2e8f0;
+ --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ line-height: 1.6;
+ color: var(--text-color);
+ background-color: var(--background-color);
+ margin: 0;
+ padding: 0;
+}
+
+.container {
+ max-width: 800px;
+ margin: 0 auto;
+ padding: 0 1rem;
+}
+
+header {
+ background: var(--background-color);
+ border-bottom: 1px solid var(--border-color);
+ padding: 2rem 0;
+}
+
+header h1 {
+ margin: 0;
+ font-size: 2rem;
+ font-weight: 700;
+}
+
+header h1 a {
+ color: var(--text-color);
+ text-decoration: none;
+}
+
+header p {
+ margin: 0.5rem 0 0 0;
+ color: var(--secondary-color);
+}
+
+main {
+ padding: 3rem 0;
+}
+
+article {
+ background: var(--background-color);
+ border-radius: 8px;
+ box-shadow: var(--shadow);
+ padding: 2rem;
+ margin-bottom: 2rem;
+}
+
+article header h1 {
+ margin: 0 0 1rem 0;
+ color: var(--primary-color);
+}
+
+.meta {
+ color: var(--secondary-color);
+ font-size: 0.9rem;
+ margin-bottom: 1.5rem;
+}
+
+.meta .author,
+.meta .date {
+ display: inline-block;
+ margin-right: 1rem;
+}
+
+footer {
+ background: var(--text-color);
+ color: var(--background-color);
+ text-align: center;
+ padding: 2rem 0;
+ margin-top: 3rem;
+}
+
+footer a {
+ color: var(--primary-color);
+}
+
+/* Responsive design */
+@media (max-width: 768px) {
+ .container {
+ padding: 0 0.5rem;
+ }
+
+ header {
+ padding: 1rem 0;
+ }
+
+ main {
+ padding: 1.5rem 0;
+ }
+
+ article {
+ padding: 1.5rem;
+ margin-bottom: 1rem;
+ }
+}
+```
+
+### 3. Create Post Layout (`themes/modern-blog/layouts/post.html`)
+
+```html
+
+
+
+
+
+ $site_name$ - $title$
+
+
+ $if(date)$$endif$
+ $for(css)$
+
+ $endfor$
+
+
+
+
+
+
+
+
+
+```
+
+### 4. Configure Site (`site.conf`)
+
+```bash
+site_theme="modern-blog"
+site_theme_css_file="css/style.css"
+```
+
+### 5. Generate Site
+
+```bash
+./bin/qsgen3
+```
+
+Your modern, responsive blog theme is now ready!
+
+---
+
+This guide covers everything you need to know about creating themes for qsgen3. Remember that themes are processed in a specific order, and understanding this processing flow is key to creating effective themes that work reliably across different configurations.
diff --git a/bin/qsgen3 b/bin/qsgen3
index c3af879..6f43bcb 100755
--- a/bin/qsgen3
+++ b/bin/qsgen3
@@ -620,11 +620,13 @@ _clean_output_dir() {
_log INFO "Found preserve file: $preserve_file"
_log DEBUG "Performing selective cleaning with file preservation"
- # Create temporary directory to store preserved files
- local temp_preserve_dir=$(mktemp -d)
+ # Use in-memory arrays to store file paths and content
+ local files_to_preserve=()
+ local preserved_file_paths=()
+ local preserved_file_contents=()
local files_preserved=0
- # Read preserve patterns and backup matching files
+ # Read preserve patterns and collect matching files
while IFS= read -r pattern || [[ -n "$pattern" ]]; do
# Skip empty lines and comments
[[ -z "$pattern" || "$pattern" == \#* ]] && continue
@@ -640,17 +642,16 @@ _clean_output_dir() {
if [[ -f "$file" ]]; then
# Get relative path from output directory
local rel_path="${file#$output_dir/}"
- local preserve_path="$temp_preserve_dir/$rel_path"
- # Create directory structure in temp location
- mkdir -p "$(dirname "$preserve_path")"
-
- # Copy file to preserve location
- if cp "$file" "$preserve_path"; then
+ # Read file content into memory
+ local file_content
+ if file_content=$(<"$file"); then
+ preserved_file_paths+=("$rel_path")
+ preserved_file_contents+=("$file_content")
files_preserved=$((files_preserved + 1))
- _log DEBUG "Preserved file: $rel_path"
+ _log DEBUG "Preserved file in memory: $rel_path"
else
- _log WARNING "Failed to preserve file: $rel_path"
+ _log WARNING "Failed to read file for preservation: $rel_path"
fi
fi
done < <(find "$output_dir" -name "$pattern" -type f -print0 2>/dev/null)
@@ -658,30 +659,37 @@ _clean_output_dir() {
done < "$preserve_file"
# Remove the output directory
- _log DEBUG "Removing output directory contents (preserving $files_preserved files)"
+ _log DEBUG "Removing output directory contents (preserving $files_preserved files in memory)"
rm -rf "$output_dir"
# Recreate output directory
mkdir -p "$output_dir"
if [[ $? -ne 0 ]]; then
_log ERROR "Failed to recreate output directory: $output_dir"
- rm -rf "$temp_preserve_dir"
return 1
fi
- # Restore preserved files
+ # Restore preserved files from memory
if [[ $files_preserved -gt 0 ]]; then
- _log DEBUG "Restoring $files_preserved preserved files"
- if [[ -d "$temp_preserve_dir" ]]; then
- # Copy preserved files back, maintaining directory structure
- (cd "$temp_preserve_dir" && find . -type f -exec cp --parents {} "$output_dir/" \; 2>/dev/null)
- _log INFO "Restored $files_preserved preserved files"
- fi
+ _log DEBUG "Restoring $files_preserved preserved files from memory"
+ for ((i=0; i<${#preserved_file_paths[@]}; i++)); do
+ local rel_path="${preserved_file_paths[i]}"
+ local file_content="${preserved_file_contents[i]}"
+ local full_path="$output_dir/$rel_path"
+
+ # Create directory structure if needed
+ mkdir -p "$(dirname "$full_path")"
+
+ # Write file content back
+ if printf '%s' "$file_content" > "$full_path"; then
+ _log DEBUG "Restored file from memory: $rel_path"
+ else
+ _log WARNING "Failed to restore file from memory: $rel_path"
+ fi
+ done
+ _log INFO "Restored $files_preserved preserved files from memory"
fi
- # Clean up temporary directory
- rm -rf "$temp_preserve_dir"
-
else
# No preserve file - do complete cleaning as before
_log DEBUG "No preserve file found - performing complete cleaning"
@@ -794,14 +802,6 @@ _copy_static_files() {
_generate_index_page() {
_log DEBUG "Entered _generate_index_page"
- local temp_yaml_file
- temp_yaml_file=$(mktemp -t qsgen3_index_yaml.XXXXXX)
- if [[ $? -ne 0 || -z "$temp_yaml_file" || ! -f "$temp_yaml_file" ]]; then
- _log ERROR "Failed to create temporary file for index YAML. Exiting."
- return 1
- fi
- trap "_log DEBUG \"Cleaning up temporary index YAML file: ${temp_yaml_file}\"; rm -f \"${temp_yaml_file}\"; trap - EXIT INT TERM HUP" EXIT INT TERM HUP
- _log DEBUG "Created temporary YAML file for index: $temp_yaml_file"
_log INFO "Generating index page..."
local content_posts_dir="${QSG_CONFIG[paths_content_dir]}/posts"
local output_index_file="${QSG_CONFIG[paths_output_dir]}/index.html"
@@ -892,119 +892,87 @@ _generate_index_page() {
if [[ ${#sorted_posts[@]} -eq 0 ]]; then
_log DEBUG "No posts found to add to index YAML."
- yaml_lines+=( "$(printf ' [] # Explicitly empty list')" )
+ yaml_lines+=( "$(printf ' [] # Explicit empty list for posts')" )
else
- for detail_line in "${sorted_posts[@]}"; do
- # Original detail_line format: "${sort_key}|${title}|${post_url}|${date:-N/A}|${summary:-}"
- # Using Zsh specific split for robustness with special characters in fields
- local parts_array=( ${(s[|])detail_line} )
-
- local post_title_val="${parts_array[2]}"
- local post_url_val="${parts_array[3]}"
- local post_date_val="${parts_array[4]}"
- local post_summary_val="${parts_array[5]}"
+ for post_line in "${sorted_posts[@]}"; do
+ # Parse the post_line: sort_key|title|url|date|summary
+ IFS='|' read -r sort_key title url date summary <<< "$post_line"
- yaml_lines+=( "$(printf ' - title: "%s"' "$(_yaml_escape_val "$post_title_val")")" )
- yaml_lines+=( "$(printf ' url: "%s"' "$(_yaml_escape_val "$post_url_val")")" )
- yaml_lines+=( "$(printf ' date: "%s"' "$(_yaml_escape_val "$post_date_val")")" )
- yaml_lines+=( "$(printf ' summary: "%s"' "$(_yaml_escape_val "$post_summary_val")")" )
+ yaml_lines+=( "$(printf ' - post_title: "%s"' "$(_yaml_escape_val "$title")")" )
+ yaml_lines+=( "$(printf ' post_url: "%s"' "$(_yaml_escape_val "$url")")" )
+ yaml_lines+=( "$(printf ' post_date: "%s"' "$(_yaml_escape_val "$date")")" )
+ yaml_lines+=( "$(printf ' post_summary: "%s"' "$(_yaml_escape_val "$summary")")" )
done
fi
- local temp_yaml_content
- temp_yaml_content="$(IFS=$'\n'; echo "${yaml_lines[*]}")"
- temp_yaml_content+=$'\n' # Ensure a final trailing newline for the whole block
- _log DEBUG "Generated comprehensive YAML for index (first 300 chars):\n${temp_yaml_content:0:300}..."
+ # Join YAML lines into a single string
+ local yaml_content
+ OIFS="$IFS"; IFS=$'\n'
+ yaml_content="${yaml_lines[*]}"
+ IFS="$OIFS"
- # Write YAML to temporary file
- _log DEBUG "Full comprehensive YAML for index before writing to file (raw, first 1000 chars to avoid excessive logging):\n${temp_yaml_content:0:1000}"
+ _log DEBUG "Generated YAML content for index (first 300 chars): ${yaml_content:0:300}..."
- if ! printf '%s' "$temp_yaml_content" > "$temp_yaml_file"; then
- _log ERROR "Failed to write comprehensive YAML to temporary file: $temp_yaml_file"
- return 1 # Trap will clean up temp_yaml_file
- fi
- _log DEBUG "Successfully wrote comprehensive YAML to $temp_yaml_file"
+ # Build Pandoc command array
+ local pandoc_cmd_index=("pandoc")
-
- _log DEBUG "In _generate_index_page: PROJECT_ROOT='$PROJECT_ROOT'"
- local pandoc_cmd_index=(
- pandoc
- "--metadata-file" "$temp_yaml_file"
- )
-
- # Add CSS if specified (pandoc_css_path_arg is derived in main)
- if [[ -n "${QSG_CONFIG[pandoc_css_path_arg]:-}" ]]; then
- pandoc_cmd_index+=("--css" "${QSG_CONFIG[pandoc_css_path_arg]}")
- _log DEBUG "Using CSS for index: ${QSG_CONFIG[pandoc_css_path_arg]}"
+ # Add CSS if theme CSS is available
+ if [[ -n "${QSG_CONFIG[pandoc_css_path_arg]}" ]]; then
+ local expected_css_file="${QSG_CONFIG[paths_output_dir]}${QSG_CONFIG[pandoc_css_path_arg]}"
+ if [[ -f "$expected_css_file" ]]; then
+ pandoc_cmd_index+=("--css" "${QSG_CONFIG[pandoc_css_path_arg]}")
+ _log DEBUG "Added CSS to index page: ${QSG_CONFIG[pandoc_css_path_arg]}"
+ else
+ _log WARNING "Expected CSS file not found: $expected_css_file. CSS will not be linked in index page."
+ fi
else
- _log DEBUG "No CSS specified for index page."
+ _log DEBUG "No theme CSS path configured. Index page will not include CSS."
fi
- # Add remaining Pandoc options for index page
- pandoc_cmd_index+=(
- "--template=${layout_index_file}"
- "--output=${output_index_file}"
- "--standalone"
- )
-
- _log INFO "Generating $output_index_file using template $layout_index_file with YAML from $temp_yaml_file"
+ # Add metadata via process substitution instead of temporary file
+ pandoc_cmd_index+=("--metadata-file" "/dev/stdin")
+ pandoc_cmd_index+=("--template=${layout_index_file}")
+ pandoc_cmd_index+=("--output=${output_index_file}")
+ pandoc_cmd_index+=("--standalone")
_log DEBUG "Pandoc command for index page will be constructed as follows:"
_log DEBUG "Base command: pandoc"
_log DEBUG "CSS arg if theme applies: --css ${QSG_CONFIG[pandoc_css_path_arg]:-(not set or expected CSS file not found in output)}"
- _log DEBUG "Metadata file arg: --metadata-file ${temp_yaml_file}"
+ _log DEBUG "Metadata file arg: --metadata-file /dev/stdin"
_log DEBUG "Template arg: --template=${layout_index_file}"
_log DEBUG "Output arg: --output=${output_index_file}"
_log DEBUG "Other args: --standalone"
_log DEBUG "Final pandoc_cmd_index array: ${(q+)pandoc_cmd_index}"
- local pandoc_run_stderr_file
- pandoc_run_stderr_file=$(mktemp -t pandoc_stderr_index.XXXXXX)
- if [[ -z "$pandoc_run_stderr_file" || ! -e "$pandoc_run_stderr_file" ]]; then # -e to check existence, could be pipe not file
- _log CRITICAL "Failed to create temporary file for Pandoc stderr (index). mktemp failed."
- # Attempt to clean up the main temp YAML file before exiting, if it was created.
- if [[ -n "$temp_yaml_file" && -f "$temp_yaml_file" ]]; then
- _log DEBUG "Cleaning up temporary index YAML file ($temp_yaml_file) due to mktemp failure for stderr file."
- rm -f "$temp_yaml_file"
- fi
- return 1
- fi
- # This temp stderr file is short-lived, trap for it isn't strictly necessary if cleaned up reliably.
-
local pandoc_exit_code=0
- # Execute Pandoc, redirecting its stderr to the temp file
+ local stderr_content=""
+
+ # Execute Pandoc with YAML content piped to stdin, capturing stderr in memory
_log DEBUG "Executing Pandoc command for index page with set -x..."
set -x # Enable command tracing
- if "${pandoc_cmd_index[@]}" 2> "$pandoc_run_stderr_file"; then
- pandoc_exec_status=0
- else
- pandoc_exec_status=$?
- fi
+
+ # Use process substitution to capture stderr without temporary files
+ exec 3>&1 # Save stdout to fd 3
+ stderr_content=$( { printf '%s\n' "$yaml_content" | "${pandoc_cmd_index[@]}" 1>&3; } 2>&1 )
+ pandoc_exit_code=$?
+ exec 3>&- # Close fd 3
+
set +x # Disable command tracing
- _log DEBUG "Pandoc execution finished. Original exit status from Pandoc: $pandoc_exec_status"
- pandoc_exit_code=$pandoc_exec_status # Use the captured status
-
- local stderr_content=""
- if [[ -s "$pandoc_run_stderr_file" ]]; then
- stderr_content=$(<"$pandoc_run_stderr_file")
- fi
- rm -f "$pandoc_run_stderr_file"
+ _log DEBUG "Pandoc execution finished. Original exit status from Pandoc: $pandoc_exit_code"
if [[ $pandoc_exit_code -ne 0 ]]; then
_log ERROR "Pandoc command failed for '$output_index_file' with exit code: $pandoc_exit_code."
if [[ -n "$stderr_content" ]]; then
_log ERROR "Pandoc stderr:\n$stderr_content"
fi
- _log ERROR "YAML content passed to Pandoc was in: $temp_yaml_file"
- _log ERROR "YAML content dump (first 500 chars):\n$(head -c 500 "$temp_yaml_file" 2>/dev/null || echo 'Failed to read YAML dump')"
+ _log ERROR "YAML content passed to Pandoc (first 500 chars):\n${yaml_content:0:500}"
return $pandoc_exit_code # Return Pandoc's non-zero exit code
elif [[ -n "$stderr_content" ]]; then
# Pandoc exited 0 but wrote to stderr. Check for known fatal error patterns.
if echo "$stderr_content" | grep -q -iE 'YAML parse exception|template error|could not find|error reading file'; then
_log ERROR "Pandoc reported a critical error for '$output_index_file' (exit code 0). Treating as failure."
_log ERROR "Pandoc stderr:\n$stderr_content"
- _log ERROR "YAML content passed to Pandoc was in: $temp_yaml_file"
- _log ERROR "YAML content dump (first 500 chars):\n$(head -c 500 "$temp_yaml_file" 2>/dev/null || echo 'Failed to read YAML dump')"
+ _log ERROR "YAML content passed to Pandoc (first 500 chars):\n${yaml_content:0:500}"
return 1 # Force a failure status
else
_log WARNING "Pandoc succeeded for '$output_index_file' (exit code 0) but produced stderr (non-critical):\n$stderr_content"
@@ -1039,7 +1007,7 @@ _process_markdown_files() {
if [[ -z "$source_file" ]]; then continue; fi
_log DEBUG "Processing Markdown file: $source_file"
- local relative_path_from_content_root="${source_file#$content_dir/}"
+ local relative_path_from_content_root="${source_file#${QSG_CONFIG[paths_content_dir]}/}"
if [[ "$content_dir" == "$source_file" ]]; then
relative_path_from_content_root=$(basename "$source_file")
elif [[ "$content_dir" == "/" && "$source_file" == /* ]]; then
@@ -1220,14 +1188,6 @@ _process_markdown_files() {
_generate_rss_feed() {
_log DEBUG "Entered _generate_rss_feed"
- local temp_rss_yaml_file
- temp_rss_yaml_file=$(mktemp -t qsgen3_rss_yaml.XXXXXX)
- if [[ $? -ne 0 || -z "$temp_rss_yaml_file" || ! -f "$temp_rss_yaml_file" ]]; then
- _log ERROR "Failed to create temporary file for RSS YAML. Exiting."
- return 1
- fi
- trap "_log DEBUG \"Cleaning up temporary RSS YAML file: ${temp_rss_yaml_file}\"; rm -f \"${temp_rss_yaml_file}\"; trap - EXIT INT TERM HUP" EXIT INT TERM HUP
- _log DEBUG "Created temporary YAML file for RSS: $temp_rss_yaml_file"
if [[ "${QSG_CONFIG[build_options_generate_rss]}" != "true" ]]; then
_log INFO "RSS feed generation is disabled in configuration. Skipping."
return 0
@@ -1316,92 +1276,78 @@ _generate_rss_feed() {
yaml_lines+=( "$(printf 'feed_title: "%s"' "$(_yaml_escape_val "$site_title_for_feed Feed")")" ) # For in RSS
yaml_lines+=( "$(printf 'current_rfc822_date: "%s"' "$(_yaml_escape_val "$current_rfc822_date")")" ) # For of the feed itself
yaml_lines+=( "$(printf 'current_year: "%s"' "$(date +%Y)")" )
- yaml_lines+=( "$(printf 'pagetitle: "%s"' "$(_yaml_escape_val "$site_title_for_feed Feed")")" ) # For Pandoc's HTML writer
-
yaml_lines+=( "$(printf 'posts:')" )
- if [[ ${#sorted_posts[@]} -eq 0 ]]; then
- _log INFO "No posts found to include in RSS feed after filtering."
- yaml_lines+=( "$(printf ' [] # Explicitly empty list')" )
- else
- for detail_line in "${sorted_posts[@]}"; do
- local parts=( ${(s[|])detail_line} ) # Zsh specific split
- local p_title="${parts[2]:-}"
- local p_url="${parts[3]:-}"
- local p_date_rfc822="${parts[4]:-}"
- local p_summary="${parts[5]:-}"
- yaml_lines+=( "$(printf ' - post_title: "%s"' "$(_yaml_escape_val "$p_title")")" )
- yaml_lines+=( "$(printf ' post_url: "%s"' "$(_yaml_escape_val "$p_url")")" )
- yaml_lines+=( "$(printf ' post_date_rfc822: "%s"' "$(_yaml_escape_val "$p_date_rfc822")")" )
- yaml_lines+=( "$(printf ' post_summary: "%s"' "$(_yaml_escape_val "$p_summary")")" )
- # Add other fields like guid if needed, e.g., using post_url as guid
- yaml_lines+=( "$(printf ' post_guid: "%s"' "$(_yaml_escape_val "$p_url")")" )
+ if [[ ${#sorted_posts[@]} -eq 0 ]]; then
+ _log DEBUG "No posts found to add to RSS YAML."
+ yaml_lines+=( "$(printf ' [] # Explicit empty list for posts')" )
+ else
+ for post_line in "${sorted_posts[@]}"; do
+ # Parse the post_line: sort_key|title|url|rfc822_date|summary
+ IFS='|' read -r sort_key title url rfc822_date summary <<< "$post_line"
+
+ yaml_lines+=( "$(printf ' - post_title: "%s"' "$(_yaml_escape_val "$title")")" )
+ yaml_lines+=( "$(printf ' post_url: "%s"' "$(_yaml_escape_val "$url")")" )
+ yaml_lines+=( "$(printf ' post_date_rfc822: "%s"' "$(_yaml_escape_val "$rfc822_date")")" )
+ yaml_lines+=( "$(printf ' post_summary: "%s"' "$(_yaml_escape_val "$summary")")" )
done
fi
- local temp_yaml_content
- temp_yaml_content="$(IFS=$'\n'; echo "${yaml_lines[*]}")"
- temp_yaml_content+=$'\n' # Ensure a final trailing newline
+ # Join YAML lines into a single string
+ local yaml_content
+ OIFS="$IFS"; IFS=$'\n'
+ yaml_content="${yaml_lines[*]}"
+ IFS="$OIFS"
- _log DEBUG "Generated comprehensive YAML for RSS (first 300 chars):\n${temp_yaml_content:0:300}..."
-
- if ! printf '%s' "$temp_yaml_content" > "$temp_rss_yaml_file"; then
- _log ERROR "Failed to write comprehensive RSS YAML to temporary file: $temp_rss_yaml_file"
- return 1
- fi
- _log DEBUG "Successfully wrote comprehensive RSS YAML to $temp_rss_yaml_file"
+ _log DEBUG "Generated YAML content for RSS (first 300 chars): ${yaml_content:0:300}..."
+ # Build Pandoc command array for RSS
local pandoc_cmd_rss=(
- pandoc
- "--metadata-file" "$temp_rss_yaml_file"
+ "pandoc"
+ "--to" "html5"
+ "--metadata" "pagetitle=RSS Feed"
+ "--metadata-file" "/dev/stdin"
"--template=${layout_rss_file}"
"--output=${output_rss_file}"
- "--to" "html5" # Use HTML5 writer for custom XML template processing
"--standalone"
)
- _log INFO "Generating $output_rss_file using template $layout_rss_file with YAML from $temp_rss_yaml_file"
- local pandoc_run_rss_stderr_file
- pandoc_run_rss_stderr_file=$(mktemp -t pandoc_stderr_rss.XXXXXX)
- if [[ -z "$pandoc_run_rss_stderr_file" || ! -e "$pandoc_run_rss_stderr_file" ]]; then
- _log CRITICAL "Failed to create temporary file for Pandoc stderr (RSS). mktemp failed."
- if [[ -n "$temp_rss_yaml_file" && -f "$temp_rss_yaml_file" ]]; then
- _log DEBUG "Cleaning up temporary RSS YAML file ($temp_rss_yaml_file) due to mktemp failure for stderr file."
- rm -f "$temp_rss_yaml_file"
- fi
- return 1
- fi
+ _log DEBUG "Pandoc command for RSS feed will be constructed as follows:"
+ _log DEBUG "Base command: pandoc --to html5 --metadata pagetitle=\"RSS Feed\""
+ _log DEBUG "Metadata file arg: --metadata-file /dev/stdin"
+ _log DEBUG "Template arg: --template=${layout_rss_file}"
+ _log DEBUG "Output arg: --output=${output_rss_file}"
+ _log DEBUG "Other args: --standalone"
+ _log DEBUG "Final pandoc_cmd_rss array: ${(q+)pandoc_cmd_rss}"
local pandoc_exit_code_rss=0
- # Provide a dummy input to Pandoc since content is from metadata, redirect stderr
- local pandoc_exec_status_rss
- if echo "" | "${pandoc_cmd_rss[@]}" 2> "$pandoc_run_rss_stderr_file"; then
- pandoc_exec_status_rss=0
- else
- pandoc_exec_status_rss=$?
- fi
- pandoc_exit_code_rss=$pandoc_exec_status_rss
-
local stderr_content_rss=""
- if [[ -s "$pandoc_run_rss_stderr_file" ]]; then
- stderr_content_rss=$(<"$pandoc_run_rss_stderr_file")
- fi
- rm -f "$pandoc_run_rss_stderr_file"
+
+ # Execute Pandoc with YAML content piped to stdin, capturing stderr in memory
+ _log DEBUG "Executing Pandoc command for RSS feed with set -x..."
+ set -x # Enable command tracing
+
+ # Use process substitution to capture stderr without temporary files
+ exec 3>&1 # Save stdout to fd 3
+ stderr_content_rss=$( { printf '%s\n' "$yaml_content" | "${pandoc_cmd_rss[@]}" 1>&3; } 2>&1 )
+ pandoc_exit_code_rss=$?
+ exec 3>&- # Close fd 3
+
+ set +x # Disable command tracing
+ _log DEBUG "Pandoc execution finished for RSS. Original exit status from Pandoc: $pandoc_exit_code_rss"
if [[ $pandoc_exit_code_rss -ne 0 ]]; then
_log ERROR "Pandoc command failed for '$output_rss_file' with exit code: $pandoc_exit_code_rss."
if [[ -n "$stderr_content_rss" ]]; then
_log ERROR "Pandoc stderr:\n$stderr_content_rss"
fi
- _log ERROR "YAML content passed to Pandoc for RSS was in: $temp_rss_yaml_file"
- _log ERROR "RSS YAML content dump (first 500 chars):\n$(head -c 500 "$temp_rss_yaml_file" 2>/dev/null || echo 'Failed to read RSS YAML dump')"
+ _log ERROR "YAML content passed to Pandoc for RSS (first 500 chars):\n${yaml_content:0:500}"
return $pandoc_exit_code_rss # Return Pandoc's non-zero exit code
elif [[ -n "$stderr_content_rss" ]]; then
if echo "$stderr_content_rss" | grep -q -iE 'YAML parse exception|template error|could not find|error reading file'; then
_log ERROR "Pandoc reported a critical error for '$output_rss_file' (exit code 0). Treating as failure."
_log ERROR "Pandoc stderr:\n$stderr_content_rss"
- _log ERROR "YAML content passed to Pandoc for RSS was in: $temp_rss_yaml_file"
- _log ERROR "RSS YAML content dump (first 500 chars):\n$(head -c 500 "$temp_rss_yaml_file" 2>/dev/null || echo 'Failed to read RSS YAML dump')"
+ _log ERROR "YAML content passed to Pandoc for RSS (first 500 chars):\n${yaml_content:0:500}"
return 1 # Force a failure status
else
_log WARNING "Pandoc succeeded for '$output_rss_file' (exit code 0) but produced stderr (non-critical):\n$stderr_content_rss"
diff --git a/how-it-works.md b/how-it-works.md
index 25eead7..69f1e0c 100644
--- a/how-it-works.md
+++ b/how-it-works.md
@@ -5,16 +5,14 @@
1. [Philosophy and Design Principles](#philosophy-and-design-principles)
2. [Project Structure](#project-structure)
3. [Configuration System](#configuration-system)
-4. [Theme System](#theme-system)
-5. [Creating New Themes](#creating-new-themes)
-6. [Content Processing Pipeline](#content-processing-pipeline)
-7. [Static File Handling](#static-file-handling)
-8. [Template System](#template-system)
-9. [Output Generation](#output-generation)
-10. [Command Line Interface](#command-line-interface)
-11. [Dependencies and Requirements](#dependencies-and-requirements)
-12. [Detailed Workflow](#detailed-workflow)
-13. [Troubleshooting and Debugging](#troubleshooting-and-debugging)
+4. [Content Processing Pipeline](#content-processing-pipeline)
+5. [Static File Handling](#static-file-handling)
+6. [Template System](#template-system)
+7. [Output Generation](#output-generation)
+8. [Command Line Interface](#command-line-interface)
+9. [Dependencies and Requirements](#dependencies-and-requirements)
+10. [Detailed Workflow](#detailed-workflow)
+11. [Troubleshooting and Debugging](#troubleshooting-and-debugging)
## Philosophy and Design Principles
@@ -54,11 +52,7 @@ project-root/
├── static/ # Static assets (CSS, images, etc.)
│ ├── css/
│ └── images/
-├── themes/ # Theme directory
-│ └── theme-name/ # Individual theme
-│ ├── layouts/ # Theme-specific templates (optional)
-│ └── css/ # Alternative theme asset location
-└── output/ # Generated site (created by qsgen3)
+├── output/ # Generated site (created by qsgen3)
├── index.html
├── rss.xml
├── posts/
@@ -77,10 +71,6 @@ site_name="My Awesome Site"
site_tagline="A brief description of my site"
site_url="http://localhost:8000"
-# Theme Configuration
-site_theme="minimal"
-site_theme_css_file="css/style.css"
-
# Directory Paths
paths_content_dir="content"
paths_output_dir="output"
@@ -103,535 +93,11 @@ build_options_process_drafts=false
### Key Configuration Variables
-- **`site_theme`**: Name of the active theme (directory name in `themes/`)
-- **`site_theme_css_file`**: Path to main CSS file relative to theme's static assets
+- **`site_name`**: Name of the site
- **`site_url`**: Base URL for the site (used in RSS and absolute links)
- **`paths_*`**: Directory paths (can be relative or absolute)
- **`build_options_*`**: Boolean flags for optional features
-## Theme System
-
-### Theme Architecture
-
-Themes in qsgen3 provide two main components:
-
-1. **Templates**: Pandoc HTML templates for different page types
-2. **Static Assets**: CSS, JavaScript, images, and other resources
-
-### Theme Directory Structure
-
-```
-themes/theme-name/
-├── layouts/ # Optional: Override default templates
-│ ├── index.html # Homepage template
-│ ├── post.html # Post template
-│ └── rss.xml # RSS template
-├── static/ # Preferred: Standard static assets location
-│ ├── css/
-│ └── js/
-└── css/ # Alternative: Direct CSS location
- └── style.css
-```
-
-### Theme Resolution Logic
-
-1. **Theme Selection**: Based on `site_theme` in `site.conf`
-2. **Layout Override**: If `themes/theme-name/layouts/` exists, it overrides `paths_layouts_dir`
-3. **Static Asset Source**:
- - First checks for `themes/theme-name/static/`
- - Falls back to `themes/theme-name/` (for themes with assets at root level)
-4. **CSS File Location**: Determined by `site_theme_css_file` relative to theme's static source
-
-### Theme Switching
-
-Switching themes is accomplished by:
-
-1. Changing `site_theme` in `site.conf`
-2. Updating `site_theme_css_file` to match the new theme's CSS structure
-3. Running qsgen3 to regenerate the site
-
-## Creating New Themes
-
-### Theme Development Overview
-
-Creating a new theme for qsgen3 involves designing templates and static assets that work together to provide a cohesive visual and functional experience. Themes can range from minimal CSS-only styling to complex designs with custom layouts and interactive elements.
-
-### Step-by-Step Theme Creation
-
-#### 1. Create Theme Directory Structure
-
-Start by creating a new directory in the `themes/` folder:
-
-```bash
-mkdir -p themes/my-theme
-cd themes/my-theme
-```
-
-Choose one of these organizational approaches:
-
-**Option A: Standard Structure (Recommended)**
-```
-themes/my-theme/
-├── layouts/ # Custom templates (optional)
-│ ├── index.html # Homepage template
-│ ├── post.html # Blog post template
-│ └── rss.xml # RSS feed template
-└── static/ # Static assets
- ├── css/
- │ └── style.css # Main stylesheet
- └── js/ # JavaScript files (optional)
-```
-
-**Option B: CSS-Only Structure**
-```
-themes/my-theme/
-└── css/
- └── style.css # Main stylesheet only
-```
-
-#### 2. Create the Main Stylesheet
-
-Create your theme's primary CSS file. This is the only asset that qsgen3 will automatically link:
-
-```css
-/* themes/my-theme/static/css/style.css */
-
-/* Reset and base styles */
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-
-body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- line-height: 1.6;
- color: #333;
- background-color: #fff;
-}
-
-/* Header styles */
-header {
- background: #2c3e50;
- color: white;
- padding: 1rem 0;
- margin-bottom: 2rem;
-}
-
-header h1 {
- max-width: 800px;
- margin: 0 auto;
- padding: 0 1rem;
-}
-
-/* Main content */
-main {
- max-width: 800px;
- margin: 0 auto;
- padding: 0 1rem;
-}
-
-/* Post styles */
-article {
- margin-bottom: 3rem;
- padding-bottom: 2rem;
- border-bottom: 1px solid #eee;
-}
-
-article h1, article h2 {
- color: #2c3e50;
- margin-bottom: 0.5rem;
-}
-
-article .meta {
- color: #666;
- font-size: 0.9rem;
- margin-bottom: 1rem;
-}
-
-/* Navigation and links */
-nav ul {
- list-style: none;
- display: flex;
- gap: 1rem;
-}
-
-nav a {
- color: #3498db;
- text-decoration: none;
-}
-
-nav a:hover {
- text-decoration: underline;
-}
-
-/* Responsive design */
-@media (max-width: 768px) {
- main {
- padding: 0 0.5rem;
- }
-
- nav ul {
- flex-direction: column;
- gap: 0.5rem;
- }
-}
-```
-
-#### 3. Create Custom Templates (Optional)
-
-If you want to override the default templates, create custom Pandoc templates:
-
-**Homepage Template (`layouts/index.html`)**
-```html
-
-
-
-
-
- $site_name$
- $if(css)$$endif$
-
-
-
- $site_name$
- $if(site_tagline)$$site_tagline$
$endif$
-
-
-
-
- Recent Posts
- $if(posts)$
-
- $for(posts)$
- -
-
-
- $if(it.post_date)$$endif$
- $if(it.post_author)$ by $it.post_author$$endif$
-
- $if(it.post_description)$$it.post_description$
$endif$
-
- $endfor$
-
- $else$
- No posts available.
- $endif$
-
-
-
-
-
-
-```
-
-**Post Template (`layouts/post.html`)**
-```html
-
-
-
-
-
- $title$ - $site_name$
- $if(css)$$endif$
- $if(description)$$endif$
-
-
-
-
- $if(site_tagline)$$site_tagline$
$endif$
-
-
-
-
-
-
-
- $body$
-
-
-
-
-
-
-
-
-
-```
-
-#### 4. Add JavaScript (Optional)
-
-If your theme requires JavaScript functionality, add it to the static assets:
-
-```javascript
-// themes/my-theme/static/js/theme.js
-
-document.addEventListener('DOMContentLoaded', function() {
- // Add smooth scrolling
- document.querySelectorAll('a[href^="#"]').forEach(anchor => {
- anchor.addEventListener('click', function (e) {
- e.preventDefault();
- const target = document.querySelector(this.getAttribute('href'));
- if (target) {
- target.scrollIntoView({
- behavior: 'smooth'
- });
- }
- });
- });
-
- // Add copy code button functionality
- document.querySelectorAll('pre code').forEach(block => {
- const button = document.createElement('button');
- button.textContent = 'Copy';
- button.className = 'copy-button';
- button.addEventListener('click', () => {
- navigator.clipboard.writeText(block.textContent);
- button.textContent = 'Copied!';
- setTimeout(() => button.textContent = 'Copy', 2000);
- });
- block.parentNode.appendChild(button);
- });
-});
-```
-
-**Important**: Remember that qsgen3 will not automatically include JavaScript files. You must add `
-```
-
-#### 5. Configure Theme Usage
-
-Update your `site.conf` to use the new theme:
-
-```bash
-# Theme Configuration
-site_theme="my-theme"
-site_theme_css_file="css/style.css" # Path relative to theme's static source
-```
-
-For CSS-only themes using the alternative structure:
-```bash
-site_theme="my-theme"
-site_theme_css_file="style.css" # Direct path if CSS is at theme root
-```
-
-#### 6. Test Your Theme
-
-Generate your site to test the theme:
-
-```bash
-./bin/qsgen3
-```
-
-Check the output:
-- Verify CSS is applied correctly
-- Test responsive design on different screen sizes
-- Validate HTML structure
-- Check that all assets are copied correctly
-
-### Theme Development Best Practices
-
-#### CSS Guidelines
-
-1. **Use Relative Units**: Prefer `rem`, `em`, and percentages over fixed pixels
-2. **Mobile-First Design**: Start with mobile styles, then add desktop enhancements
-3. **Semantic Selectors**: Use class names that describe content, not appearance
-4. **CSS Custom Properties**: Use CSS variables for consistent theming
-
-```css
-:root {
- --primary-color: #2c3e50;
- --secondary-color: #3498db;
- --text-color: #333;
- --background-color: #fff;
- --border-color: #eee;
- --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-}
-
-body {
- color: var(--text-color);
- background-color: var(--background-color);
- font-family: var(--font-family);
-}
-```
-
-#### Template Guidelines
-
-1. **Conditional Content**: Use Pandoc's conditional syntax for optional elements
-2. **Semantic HTML**: Use appropriate HTML5 semantic elements
-3. **Accessibility**: Include proper ARIA labels and alt text
-4. **Meta Tags**: Include essential meta tags for SEO and social sharing
-
-```html
-
-$if(description)$$endif$
-$if(author)$$endif$
-
-
-
-
-
-
- $body$
-
-
-
-```
-
-#### Asset Organization
-
-1. **Logical Structure**: Group related assets in appropriate directories
-2. **Naming Conventions**: Use consistent, descriptive file names
-3. **Optimization**: Optimize images and minimize CSS/JS when possible
-4. **Dependencies**: Document any external dependencies clearly
-
-### Advanced Theme Features
-
-#### Dark Mode Support
-
-Add CSS custom properties and media queries for dark mode:
-
-```css
-:root {
- --bg-color: #fff;
- --text-color: #333;
- --border-color: #eee;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --bg-color: #1a1a1a;
- --text-color: #e0e0e0;
- --border-color: #333;
- }
-}
-
-body {
- background-color: var(--bg-color);
- color: var(--text-color);
- transition: background-color 0.3s ease, color 0.3s ease;
-}
-```
-
-#### Print Styles
-
-Include print-specific styles:
-
-```css
-@media print {
- header, footer, nav {
- display: none;
- }
-
- body {
- font-size: 12pt;
- line-height: 1.4;
- }
-
- a[href]:after {
- content: " (" attr(href) ")";
- }
-}
-```
-
-#### Custom Fonts
-
-If using custom fonts, include them properly:
-
-```css
-/* Load fonts */
-@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
-
-/* Or use local fonts */
-@font-face {
- font-family: 'CustomFont';
- src: url('/static/fonts/custom-font.woff2') format('woff2'),
- url('/static/fonts/custom-font.woff') format('woff');
- font-display: swap;
-}
-```
-
-### Theme Distribution
-
-#### Documentation
-
-Create a `README.md` for your theme:
-
-```markdown
-# My Theme
-
-A clean, minimal theme for qsgen3.
-
-## Features
-- Responsive design
-- Dark mode support
-- Clean typography
-- Fast loading
-
-## Installation
-1. Copy theme to `themes/my-theme/`
-2. Update `site.conf`:
- ```
- site_theme="my-theme"
- site_theme_css_file="css/style.css"
- ```
-3. Run `./bin/qsgen3`
-
-## Customization
-- Edit CSS custom properties in `style.css`
-- Modify templates in `layouts/` directory
-- Add custom JavaScript in `static/js/`
-
-## Browser Support
-- Modern browsers (Chrome 90+, Firefox 88+, Safari 14+)
-- Graceful degradation for older browsers
-```
-
-#### Version Control
-
-If sharing your theme:
-1. Use semantic versioning
-2. Tag releases appropriately
-3. Include a changelog
-4. Provide example configurations
-
-### Troubleshooting Theme Development
-
-#### Common Issues
-
-1. **CSS Not Loading**: Check `site_theme_css_file` path matches actual file location
-2. **Templates Not Found**: Ensure template files are in `layouts/` directory
-3. **Assets Missing**: Verify static files are in correct directory structure
-4. **JavaScript Errors**: Remember to include `