- Remove legacy themes (black-orange, minimal) with old .tpl structure - Create new minimal theme with proper qsgen3 structure: - Complete layouts/ directory with all required templates - Modern CSS with responsive design and clean styling - Proper theme documentation in README.md - Update THEMES-HOWTO.md to accurately reflect theme behavior: - Clarify complete layout override (no fallback to defaults) - Document included minimal theme example - Correct layout processing documentation - Update site.conf to use new minimal theme CSS path - Ensure themes are complete packages when specified
22 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 completely 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
Important: When a theme provides layouts, qsgen3 uses only the theme's layouts. There is no fallback to default layouts for individual templates. If you want to use a theme, ensure it provides all necessary layout files (index.html
, post.html
, page.html
, rss.xml
).
Included Example Theme
qsgen3 includes a minimal
theme that demonstrates the proper theme structure:
themes/minimal/
├── layouts/ # Complete set of templates
│ ├── index.html # Homepage layout
│ ├── post.html # Blog post layout
│ ├── page.html # Static page layout
│ └── rss.xml # RSS feed template
├── static/ # Theme assets
│ └── css/
│ └── style.css # Clean, minimal styling
└── README.md # Theme documentation
To use the minimal theme:
- Set
site_theme="minimal"
in yoursite.conf
- Set
site_theme_css_file="css/style.css"
- Run
./bin/qsgen3
Theme Directory Structure
Standard Theme Structure
themes/
└── your-theme-name/
├── layouts/ # Optional: Custom layout templates
│ ├── index.html # Homepage layout (overrides default)
│ ├── post.html # Blog post layout (overrides default)
│ ├── page.html # Static page layout (overrides default)
│ └── rss.xml # RSS feed template (overrides default)
└── static/ # Theme's static assets
├── css/
│ └── style.css # Main theme CSS
├── js/
│ └── theme.js # Theme JavaScript
└── images/
└── logo.png # Theme images
Important: By default, qsgen3 uses the layouts/
directory in your project root (as specified by paths_layouts_dir
in site.conf
). When a theme provides its own layouts/
directory, it completely overrides the default layouts directory.
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
How Layout Override Works
- Default: qsgen3 uses templates from your project's
layouts/
directory (configurable viapaths_layouts_dir
) - Theme Override: If
themes/your-theme/layouts/
exists, qsgen3 uses only those templates - No Fallback: If a theme provides layouts, there is no fallback to default layouts. The theme must provide all required templates.
Note: The project root layouts/
directory serves as default templates for projects not using themes, or as a fallback when no theme is specified.
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!
Summary
This guide covers everything you need to know about creating themes for qsgen3. Key points to remember:
- Complete Override: When a theme provides layouts, it completely replaces default layouts
- No Partial Fallback: Themes must provide all necessary templates (
index.html
,post.html
,page.html
,rss.xml
) - Static File Merging: Theme static files are copied after root static files, allowing themes to override default assets
- CSS Automation: The main theme CSS file is automatically linked in generated HTML
Understanding this processing flow is key to creating effective themes that work reliably across different configurations.