- 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
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
- Theme Basics
- Theme Directory Structure
- Creating Your First Theme
- Layout Templates
- Static Assets (CSS, JS, Images)
- Theme Configuration
- Advanced Theme Features
- Theme Best Practices
- 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:
- Theme Detection: Looks for the theme directory at
themes/{theme_name}/
- Layout Override: If the theme has a
layouts/
directory, it replaces the default layouts - Static File Copying: Copies static files from both root
static/
and themestatic/
directories - 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>© $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>© $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:
- Root Static Files: Copies from
static/
tooutput/static/
- Theme Static Files: Copies from
themes/{theme}/static/
tooutput/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:
site_theme_css_file
setting insite.conf
(recommended)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'sstatic/
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:
- Check the qsgen3 documentation and examples
- Review existing themes in the
themes/
directory - Examine the qsgen3 source code for theme processing logic
- Create a minimal reproduction case
- 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>© $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.