qsgen3/THEMES-HOWTO.md
Stig-Ørjan Smelror 81ffa53d70 Add comprehensive theme documentation and improve migration script
- Add THEMES-HOWTO.md: Complete guide for creating and customizing themes
- Remove theme sections from how-it-works.md to avoid duplication
- Update migration script to place all blog posts in single directory
- Streamline documentation structure for better organization
2025-05-31 03:00:50 +02:00

19 KiB

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
  2. Theme Directory Structure
  3. Creating Your First Theme
  4. Layout Templates
  5. Static Assets (CSS, JS, Images)
  6. Theme Configuration
  7. Advanced Theme Features
  8. Theme Best Practices
  9. 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

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:

/* 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:

# Theme configuration
site_theme="my-theme"
site_theme_css_file="css/style.css"

Step 4: Test Your Theme

./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)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>$site_name$ - $title$</title>
    <meta name="author" content="$author$">
    <meta name="description" content="$description$">
    $if(date)$<meta name="date" content="$date$">$endif$
    $for(css)$
        <link rel="stylesheet" href="$css$">
    $endfor$
</head>
<body>
    <header>
        <h1><a href="/">$site_name$</a></h1>
        <p>$site_tagline$</p>
    </header>
    <main>
        <article>
            <header>
                <h1>$title$</h1>
                $if(author)$<p class="author">By: $author$</p>$endif$
                $if(date)$<p class="date">Published: $date$</p>$endif$
            </header>
            $body$
        </article>
    </main>
    <footer>
        <p>&copy; $current_year$ $site_name$. Generated by qsgen3.</p>
    </footer>
</body>
</html>

Index Page Layout (layouts/index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>$site_name$ - $site_tagline$</title>
    <meta name="description" content="$site_tagline$">
    $for(css)$
        <link rel="stylesheet" href="$css$">
    $endfor$
</head>
<body>
    <header>
        <h1>$site_name$</h1>
        <p>$site_tagline$</p>
    </header>
    <main>
        $body$
    </main>
    <footer>
        <p>&copy; $current_year$ $site_name$. Generated by qsgen3.</p>
    </footer>
</body>
</html>

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:

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:

// 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:

<script src="/static/js/theme.js"></script>

Images and Other Assets

Place images in themes/my-theme/static/images/ and reference them in your CSS or templates:

.logo {
    background-image: url('/static/images/logo.png');
}
<img src="/static/images/hero.jpg" alt="Hero image">

Theme Configuration

Required Configuration

In your site.conf:

# 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

# 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:

# Custom theme variables
theme_color_primary="#3498db"
theme_color_secondary="#2c3e50"
theme_font_family="'Roboto', sans-serif"

Use in templates:

<style>
:root {
    --primary-color: $theme_color_primary$;
    --secondary-color: $theme_color_secondary$;
    --font-family: $theme_font_family$;
}
</style>

Advanced Theme Features

Responsive Design

Create responsive themes using CSS media queries:

/* 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

/* 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

/* 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 version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
    <title>$site_name$</title>
    <description>$site_tagline$</description>
    <link>$site_url$</link>
    <atom:link href="$site_url$/rss.xml" rel="self" type="application/rss+xml"/>
    <language>en-us</language>
    <lastBuildDate>$build_date$</lastBuildDate>
    
    $for(posts)$
    <item>
        <title>$it.post_title$</title>
        <description>$it.post_description$</description>
        <link>$it.post_url$</link>
        <guid>$it.post_url$</guid>
        <pubDate>$it.post_date_rfc$</pubDate>
    </item>
    $endfor$
</channel>
</rss>

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:

/* 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:

/* 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:

# 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:

<!-- minimal-test.html -->
<!DOCTYPE html>
<html>
<head>
    <title>$title$</title>
    <style>body { font-family: Arial; margin: 2rem; }</style>
</head>
<body>
    <h1>$title$</h1>
    $body$
</body>
</html>

3. Validate Generated HTML

Check that your generated HTML is valid:

# Use HTML validator tools
htmlhint output/*.html
# or
w3c-validator output/index.html

4. Check File Permissions

Ensure qsgen3 can read your theme files:

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

mkdir -p themes/modern-blog/{layouts,static/{css,js,images}}

2. Create Main CSS (themes/modern-blog/static/css/style.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)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>$site_name$ - $title$</title>
    <meta name="author" content="$author$">
    <meta name="description" content="$description$">
    $if(date)$<meta name="date" content="$date$">$endif$
    $for(css)$
        <link rel="stylesheet" href="$css$">
    $endfor$
</head>
<body>
    <header>
        <div class="container">
            <h1><a href="/">$site_name$</a></h1>
            <p>$site_tagline$</p>
        </div>
    </header>
    <main>
        <div class="container">
            <article>
                <header>
                    <h1>$title$</h1>
                    <div class="meta">
                        $if(author)$<span class="author">By: $author$</span>$endif$
                        $if(date)$<span class="date">Published: $date$</span>$endif$
                    </div>
                </header>
                $body$
            </article>
        </div>
    </main>
    <footer>
        <div class="container">
            <p>&copy; $current_year$ $site_name$. Generated by qsgen3.</p>
            <p><a href="$site_url$">$site_url$</a></p>
        </div>
    </footer>
</body>
</html>

4. Configure Site (site.conf)

site_theme="modern-blog"
site_theme_css_file="css/style.css"

5. Generate Site

./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.