Compare commits
5 Commits
4b95426256
...
main
Author | SHA1 | Date | |
---|---|---|---|
7521c744bd | |||
81ffa53d70 | |||
f72fe18873 | |||
ed7ed0ee18 | |||
5266559f26 |
40
.qsgen3_preserve.example
Normal file
40
.qsgen3_preserve.example
Normal file
@ -0,0 +1,40 @@
|
||||
# qsgen3 Preserve File
|
||||
#
|
||||
# This file specifies patterns for files that should be preserved during
|
||||
# output directory cleaning. This is useful for keeping shared articles
|
||||
# or other content that should remain accessible even after title changes.
|
||||
#
|
||||
# Format:
|
||||
# - One pattern per line
|
||||
# - Supports shell glob patterns (*, ?, [])
|
||||
# - Lines starting with # are comments
|
||||
# - Empty lines are ignored
|
||||
# - Patterns are relative to the output directory
|
||||
#
|
||||
# Examples:
|
||||
|
||||
# Preserve specific files by exact name
|
||||
# posts/my-important-shared-article.html
|
||||
# about.html
|
||||
|
||||
# Preserve files by pattern
|
||||
# posts/legacy-*.html
|
||||
# *.pdf
|
||||
|
||||
# Preserve entire directories
|
||||
# archive/*
|
||||
# downloads/*
|
||||
|
||||
# Preserve files with specific extensions in certain directories
|
||||
# posts/*.html
|
||||
# docs/*.pdf
|
||||
|
||||
# Example: Preserve a specific shared article
|
||||
# posts/how-to-setup-qsgen3.html
|
||||
|
||||
# Example: Preserve all files in a legacy directory
|
||||
# legacy/*
|
||||
|
||||
# Example: Preserve important documents
|
||||
# important-*.html
|
||||
# *.pdf
|
157
README.md
157
README.md
@ -1,45 +1,132 @@
|
||||
# qsgen3 - A Minimal Markdown Static Site Generator
|
||||
# qsgen3 - A Static Site Generator Born from Frustration
|
||||
|
||||
This is a refactored version of qsgen, focusing on simplicity, Markdown with YAML frontmatter, and a Zsh-based build process.
|
||||
## The Problem We All Know Too Well
|
||||
|
||||
## Project Goals
|
||||
You've been there. You want to start a blog, build a portfolio, or create documentation. You research static site generators and find yourself drowning in choices, each promising to be "the one." You pick one, spend hours configuring it, wrestling with themes that almost work, and fighting build systems that break when you breathe on them wrong.
|
||||
|
||||
- Remove QSTags and switch to Markdown + YAML frontmatter.
|
||||
- Simplify codebase by removing multilingual and theme support.
|
||||
- Refactor templating with uniform variables (e.g., `{{ content }}`, `{{ title }}`).
|
||||
- Ensure future-proofing by avoiding complex dependencies (like AWK for long string manipulation) for core tasks.
|
||||
- Provide a solid initial setup that works out of the box with a sample post, index, and layout.
|
||||
- Include a converter script in `scripts/convert-qstags-to-md.sh` for legacy content.
|
||||
Three months later, you're spending more time maintaining your site generator than writing content.
|
||||
|
||||
## Structure
|
||||
**There had to be a better way.**
|
||||
|
||||
- `bin/qsgen3`: The main Zsh build script.
|
||||
- `site.conf`: Site configuration (INI format).
|
||||
- `content/`: Source Markdown files.
|
||||
- `content/posts/`: Blog posts.
|
||||
- `content/pages/`: Static pages.
|
||||
- `layouts/`: HTML templates.
|
||||
- `layouts/base.html`: Base template for all pages.
|
||||
- `layouts/post.html`: Template for individual blog posts.
|
||||
- `layouts/page.html`: Template for static pages.
|
||||
- `static/`: Static assets (CSS, images, etc.) copied as-is to the output.
|
||||
- `output/`: The generated website.
|
||||
- `scripts/`: Utility scripts (e.g., content converter).
|
||||
## The Story Behind qsgen3
|
||||
|
||||
## Usage
|
||||
qsgen3 was born from a simple realization: most static site generators have forgotten their primary job—turning your words into websites. Instead, they've become complex platforms that impose their opinions on everything from your CSS framework to your folder structure.
|
||||
|
||||
1. Configure your site in `site.conf`.
|
||||
2. Add Markdown content to the `content/` directory.
|
||||
3. Customize templates in `layouts/`.
|
||||
4. Run the build script:
|
||||
```bash
|
||||
./bin/qsgen3
|
||||
```
|
||||
5. Your static site will be generated in the `output/` directory.
|
||||
We asked ourselves: *What if a static site generator just... generated static sites?*
|
||||
|
||||
## Dependencies
|
||||
What if it didn't care whether you prefer Bootstrap or write your own CSS? What if it didn't force you to learn a new templating language or adopt someone else's idea of how a blog should work? What if it was fast, reliable, and got out of your way?
|
||||
|
||||
- Zsh
|
||||
- A Markdown processor (e.g., Pandoc, CommonMark, or a Zsh-native solution if feasible for basic needs).
|
||||
- Standard Unix utilities (grep, sed, find, etc.).
|
||||
That's qsgen3.
|
||||
|
||||
## How qsgen3 is Different
|
||||
|
||||
### It Respects Your Choices
|
||||
Unlike generators that come with strong opinions about design, qsgen3 is **completely design-agnostic**. It doesn't ship with CSS frameworks, doesn't impose HTML structures, and doesn't make assumptions about how your site should look. Your content, your design, your rules.
|
||||
|
||||
### It Values Your Time
|
||||
Built in Zsh with minimal dependencies, qsgen3 builds sites in seconds, not minutes. No waiting for Ruby gems to compile, no Node.js dependency hell, no Python virtual environments to manage. Just Pandoc, standard Unix tools, and your content.
|
||||
|
||||
### It Speaks Standard Languages
|
||||
Your content is written in **pure Markdown with YAML frontmatter**—the same format used by GitHub, GitLab, and most modern documentation systems. No proprietary tags to learn, no custom syntax to remember. Your content works everywhere, not just with qsgen3.
|
||||
|
||||
### It Grows With You
|
||||
Start with the included minimal theme, then customize it however you want. Themes are just HTML templates and CSS files—no complex build systems or framework lock-in. When you outgrow a theme, you can switch or build your own without rewriting a single blog post.
|
||||
|
||||
## What You Actually Get
|
||||
|
||||
When you use qsgen3, you get a tool that handles the boring stuff so you can focus on what matters—your content.
|
||||
|
||||
**For Your Writing:**
|
||||
- Clean Markdown processing with full Pandoc power
|
||||
- Automatic RSS feeds that just work
|
||||
- SEO-friendly sitemaps
|
||||
- Smart permalinks for blog posts (`/blog/2024/01/15/my-post/`)
|
||||
- Draft support for work-in-progress content
|
||||
|
||||
**For Your Workflow:**
|
||||
- Git-friendly (no generated files cluttering your repo)
|
||||
- One command builds everything: `./bin/qsgen3`
|
||||
- Migration tools for converting legacy content
|
||||
- Static asset handling that preserves your file organization
|
||||
|
||||
**For Your Peace of Mind:**
|
||||
- Minimal dependencies that won't break
|
||||
- Standard formats that will outlive any framework
|
||||
- Complete control over your site's appearance and behavior
|
||||
- No vendor lock-in—your content is always portable
|
||||
|
||||
## Who This is For
|
||||
|
||||
**You're a blogger** who wants to write, not wrestle with technology. You need something that handles the technical details while giving you complete creative control.
|
||||
|
||||
**You're a developer** who appreciates tools that do one thing well. You want the power of Pandoc without the complexity of enterprise-grade site generators.
|
||||
|
||||
**You're migrating** from WordPress, Jekyll, Hugo, or a custom solution. You're tired of fighting your tools and want something that just works.
|
||||
|
||||
**You value simplicity** over features. You'd rather have a tool that does the essentials perfectly than one that does everything poorly.
|
||||
|
||||
## Getting Started is Actually Simple
|
||||
|
||||
Unlike other generators that require extensive setup, qsgen3 works out of the box:
|
||||
|
||||
```bash
|
||||
# Get qsgen3
|
||||
git clone https://git.kekepower.com/kekePower/qsgen3.git
|
||||
cd qsgen3
|
||||
|
||||
# Configure your site
|
||||
cp site.conf.example site.conf
|
||||
# Edit site.conf with your details
|
||||
|
||||
# Write your first post
|
||||
echo "---
|
||||
title: Hello World
|
||||
date: 2024-01-15
|
||||
---
|
||||
# My First Post
|
||||
Welcome to my new site!" > content/posts/hello-world.md
|
||||
|
||||
# Build your site
|
||||
./bin/qsgen3
|
||||
|
||||
# Deploy anywhere
|
||||
rsync -av output/ user@yourserver:/var/www/yoursite/
|
||||
```
|
||||
|
||||
That's it. No package managers, no build tools, no configuration files to debug.
|
||||
|
||||
## The Technical Details (For Those Who Care)
|
||||
|
||||
**Structure:**
|
||||
```
|
||||
qsgen3/
|
||||
├── bin/qsgen3 # The build script (one file, ~500 lines)
|
||||
├── site.conf # Your site configuration
|
||||
├── content/ # Your Markdown content
|
||||
├── layouts/ # HTML templates (yours to customize)
|
||||
├── themes/ # Optional themes (just templates + CSS)
|
||||
├── static/ # Your assets (copied as-is)
|
||||
├── output/ # Generated site
|
||||
└── scripts/ # Migration utilities
|
||||
```
|
||||
|
||||
**Dependencies:**
|
||||
- Zsh (already on your system)
|
||||
- Pandoc (for Markdown processing)
|
||||
- Standard Unix tools (grep, sed, find)
|
||||
|
||||
No Ruby. No Node.js. No Python virtual environments. No dependency management nightmares.
|
||||
|
||||
## The Philosophy
|
||||
|
||||
We believe your static site generator should be like a good editor—powerful when you need it, invisible when you don't. It should turn your words into websites without imposing its personality on your content.
|
||||
|
||||
qsgen3 doesn't try to be everything to everyone. It's a focused tool for people who want to publish content on the web without fighting their tools.
|
||||
|
||||
**Your content deserves better than being locked into someone else's vision of how the web should work.**
|
||||
|
||||
## Ready to Take Back Control?
|
||||
|
||||
If you're tired of complex site generators that do everything except what you actually need, qsgen3 might be exactly what you're looking for.
|
||||
|
||||
**[Get started with qsgen3](https://git.kekepower.com/kekePower/qsgen3)** and see what it feels like to have a site generator that actually gets out of your way.
|
||||
|
865
THEMES-HOWTO.md
Normal file
865
THEMES-HOWTO.md
Normal file
@ -0,0 +1,865 @@
|
||||
# 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 **completely 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
|
||||
|
||||
**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:
|
||||
1. Set `site_theme="minimal"` in your `site.conf`
|
||||
2. Set `site_theme_css_file="css/style.css"`
|
||||
3. 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
|
||||
|
||||
1. **Default**: qsgen3 uses templates from your project's `layouts/` directory (configurable via `paths_layouts_dir`)
|
||||
2. **Theme Override**: If `themes/your-theme/layouts/` exists, qsgen3 uses **only** those templates
|
||||
3. **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
|
||||
|
||||
```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
|
||||
<!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`)
|
||||
|
||||
```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:
|
||||
|
||||
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
|
||||
<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:
|
||||
|
||||
```css
|
||||
.logo {
|
||||
background-image: url('/static/images/logo.png');
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
<img src="/static/images/hero.jpg" alt="Hero image">
|
||||
```
|
||||
|
||||
## 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
|
||||
<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:
|
||||
|
||||
```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
|
||||
<?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:
|
||||
|
||||
```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
|
||||
<!-- 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:
|
||||
|
||||
```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
|
||||
<!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`)
|
||||
|
||||
```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!
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This guide covers everything you need to know about creating themes for qsgen3. Key points to remember:
|
||||
|
||||
1. **Complete Override**: When a theme provides layouts, it completely replaces default layouts
|
||||
2. **No Partial Fallback**: Themes must provide all necessary templates (`index.html`, `post.html`, `page.html`, `rss.xml`)
|
||||
3. **Static File Merging**: Theme static files are copied after root static files, allowing themes to override default assets
|
||||
4. **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.
|
615
bin/qsgen3
615
bin/qsgen3
@ -257,10 +257,16 @@ _load_config() {
|
||||
# Default build options if not set
|
||||
: ${QSG_CONFIG[build_options_process_drafts]:=false}
|
||||
: ${QSG_CONFIG[build_options_generate_rss]:=true}
|
||||
: ${QSG_CONFIG[build_options_minify_html]:=false}
|
||||
: ${QSG_CONFIG[build_options_minify_css]:=false}
|
||||
: ${QSG_CONFIG[build_options_minify_xml]:=false}
|
||||
# : ${QSG_CONFIG[build_options_generate_sitemap]:=true} # Example if sitemap is added
|
||||
|
||||
_log DEBUG "build_options_process_drafts set to: ${QSG_CONFIG[build_options_process_drafts]}"
|
||||
_log DEBUG "build_options_generate_rss set to: ${QSG_CONFIG[build_options_generate_rss]}"
|
||||
_log DEBUG "build_options_minify_html set to: ${QSG_CONFIG[build_options_minify_html]}"
|
||||
_log DEBUG "build_options_minify_css set to: ${QSG_CONFIG[build_options_minify_css]}"
|
||||
_log DEBUG "build_options_minify_xml set to: ${QSG_CONFIG[build_options_minify_xml]}"
|
||||
# _log DEBUG "build_options_generate_sitemap set to: ${QSG_CONFIG[build_options_generate_sitemap]}"
|
||||
|
||||
if [[ "$missing_configs" == true ]]; then
|
||||
@ -341,12 +347,362 @@ _generate_sitemap() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- Minification Functions ---
|
||||
_minify_html() {
|
||||
local input_file="$1"
|
||||
local output_file="$2"
|
||||
|
||||
if [[ ! -f "$input_file" ]]; then
|
||||
_log ERROR "Input file for HTML minification not found: $input_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_log DEBUG "Minifying HTML: $input_file -> $output_file"
|
||||
|
||||
# HTML minification using sed and tr
|
||||
# Remove comments, extra whitespace, and empty lines while preserving content
|
||||
sed -e '
|
||||
# Remove HTML comments (but preserve conditional comments)
|
||||
/<!--\[if/!{
|
||||
/<!--.*-->/d
|
||||
/<!--/{
|
||||
:comment
|
||||
/-->/!{
|
||||
N
|
||||
b comment
|
||||
}
|
||||
d
|
||||
}
|
||||
}
|
||||
# Remove leading and trailing whitespace from lines
|
||||
s/^[[:space:]]*//
|
||||
s/[[:space:]]*$//
|
||||
# Remove empty lines
|
||||
/^$/d
|
||||
# Compress multiple spaces between tags to single space
|
||||
s/>[[:space:]]\+</></g
|
||||
# Remove spaces around = in attributes
|
||||
s/[[:space:]]*=[[:space:]]*/=/g
|
||||
' "$input_file" | tr -d '\n' | sed '
|
||||
# Add newlines after certain closing tags for readability
|
||||
s|</head>|</head>\n|g
|
||||
s|</body>|</body>\n|g
|
||||
s|</html>|</html>\n|g
|
||||
' > "$output_file"
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
_log DEBUG "HTML minification completed successfully"
|
||||
return 0
|
||||
else
|
||||
_log ERROR "HTML minification failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
_minify_css() {
|
||||
local input_file="$1"
|
||||
local output_file="$2"
|
||||
|
||||
if [[ ! -f "$input_file" ]]; then
|
||||
_log ERROR "Input file for CSS minification not found: $input_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_log DEBUG "Minifying CSS: $input_file -> $output_file"
|
||||
|
||||
# CSS minification using sed
|
||||
sed -e '
|
||||
# Remove CSS comments
|
||||
/\/\*/{
|
||||
:comment
|
||||
/\*\//!{
|
||||
N
|
||||
b comment
|
||||
}
|
||||
s|/\*.*\*/||g
|
||||
}
|
||||
# Remove leading and trailing whitespace
|
||||
s/^[[:space:]]*//
|
||||
s/[[:space:]]*$//
|
||||
# Remove empty lines
|
||||
/^$/d
|
||||
# Remove spaces around { } : ; ,
|
||||
s/[[:space:]]*{[[:space:]]*/{/g
|
||||
s/[[:space:]]*}[[:space:]]*/}/g
|
||||
s/[[:space:]]*:[[:space:]]*/:/g
|
||||
s/[[:space:]]*;[[:space:]]*/;/g
|
||||
s/[[:space:]]*,[[:space:]]*/,/g
|
||||
# Remove trailing semicolon before }
|
||||
s/;}/}/g
|
||||
# Compress multiple spaces to single space
|
||||
s/[[:space:]]\+/ /g
|
||||
' "$input_file" | tr -d '\n' > "$output_file"
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
_log DEBUG "CSS minification completed successfully"
|
||||
return 0
|
||||
else
|
||||
_log ERROR "CSS minification failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
_minify_xml() {
|
||||
local input_file="$1"
|
||||
local output_file="$2"
|
||||
|
||||
if [[ ! -f "$input_file" ]]; then
|
||||
_log ERROR "Input file for XML minification not found: $input_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_log DEBUG "Minifying XML: $input_file -> $output_file"
|
||||
|
||||
# XML minification using sed
|
||||
sed -e '
|
||||
# Remove XML comments
|
||||
/<!--/{
|
||||
:comment
|
||||
/-->/!{
|
||||
N
|
||||
b comment
|
||||
}
|
||||
s|<!--.*-->||g
|
||||
}
|
||||
# Remove leading and trailing whitespace
|
||||
s/^[[:space:]]*//
|
||||
s/[[:space:]]*$//
|
||||
# Remove empty lines
|
||||
/^$/d
|
||||
# Compress whitespace between tags
|
||||
s/>[[:space:]]\+</></g
|
||||
# Compress multiple spaces to single space
|
||||
s/[[:space:]]\+/ /g
|
||||
' "$input_file" | tr -d '\n' | sed '
|
||||
# Add newline after XML declaration and root closing tag
|
||||
s|?>|?>\n|g
|
||||
s|</rss>|</rss>\n|g
|
||||
s|</urlset>|</urlset>\n|g
|
||||
' > "$output_file"
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
_log DEBUG "XML minification completed successfully"
|
||||
return 0
|
||||
else
|
||||
_log ERROR "XML minification failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
_minify_file() {
|
||||
local file_path="$1"
|
||||
|
||||
if [[ ! -f "$file_path" ]]; then
|
||||
_log ERROR "File for minification not found: $file_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local file_extension="${file_path##*.}"
|
||||
local temp_file="${file_path}.tmp"
|
||||
local should_minify=false
|
||||
|
||||
case "$file_extension" in
|
||||
html|htm)
|
||||
if [[ "${QSG_CONFIG[build_options_minify_html]}" == "true" ]]; then
|
||||
should_minify=true
|
||||
_minify_html "$file_path" "$temp_file"
|
||||
fi
|
||||
;;
|
||||
css)
|
||||
if [[ "${QSG_CONFIG[build_options_minify_css]}" == "true" ]]; then
|
||||
should_minify=true
|
||||
_minify_css "$file_path" "$temp_file"
|
||||
fi
|
||||
;;
|
||||
xml)
|
||||
if [[ "${QSG_CONFIG[build_options_minify_xml]}" == "true" ]]; then
|
||||
should_minify=true
|
||||
_minify_xml "$file_path" "$temp_file"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
_log DEBUG "No minification available for file type: $file_extension ($file_path)"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$should_minify" == "true" ]]; then
|
||||
if [[ $? -eq 0 && -f "$temp_file" ]]; then
|
||||
# Calculate size reduction
|
||||
local original_size=$(wc -c < "$file_path")
|
||||
local minified_size=$(wc -c < "$temp_file")
|
||||
local reduction=$((original_size - minified_size))
|
||||
local percentage=0
|
||||
if [[ $original_size -gt 0 ]]; then
|
||||
percentage=$(( (reduction * 100) / original_size ))
|
||||
fi
|
||||
|
||||
mv "$temp_file" "$file_path"
|
||||
_log INFO "Minified $file_path: ${original_size} -> ${minified_size} bytes (${percentage}% reduction)"
|
||||
else
|
||||
_log ERROR "Minification failed for $file_path"
|
||||
[[ -f "$temp_file" ]] && rm -f "$temp_file"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_minify_output_directory() {
|
||||
local output_dir="${QSG_CONFIG[paths_output_dir]}"
|
||||
|
||||
if [[ ! -d "$output_dir" ]]; then
|
||||
_log WARNING "Output directory not found for minification: $output_dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_log INFO "Starting minification of generated files..."
|
||||
|
||||
# Find and minify all relevant files
|
||||
local files_processed=0
|
||||
local files_minified=0
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
files_processed=$((files_processed + 1))
|
||||
local file_extension="${file##*.}"
|
||||
local should_process=false
|
||||
|
||||
case "$file_extension" in
|
||||
html|htm)
|
||||
[[ "${QSG_CONFIG[build_options_minify_html]}" == "true" ]] && should_process=true
|
||||
;;
|
||||
css)
|
||||
[[ "${QSG_CONFIG[build_options_minify_css]}" == "true" ]] && should_process=true
|
||||
;;
|
||||
xml)
|
||||
[[ "${QSG_CONFIG[build_options_minify_xml]}" == "true" ]] && should_process=true
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$should_process" == "true" ]]; then
|
||||
if _minify_file "$file"; then
|
||||
files_minified=$((files_minified + 1))
|
||||
fi
|
||||
fi
|
||||
done < <(find "$output_dir" -type f \( -name "*.html" -o -name "*.htm" -o -name "*.css" -o -name "*.xml" \) -print0)
|
||||
|
||||
_log INFO "Minification complete: ${files_minified}/${files_processed} files minified"
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- Core Functions ---
|
||||
_clean_output_dir() {
|
||||
_log INFO "Cleaning output directory: ${QSG_CONFIG[paths_output_dir]}"
|
||||
# rm -rf "${QSG_CONFIG[paths_output_dir]}"/* # Be careful with this!
|
||||
# For now, just ensure it exists
|
||||
mkdir -p "${QSG_CONFIG[paths_output_dir]}"
|
||||
|
||||
local output_dir="${QSG_CONFIG[paths_output_dir]}"
|
||||
local preserve_file="$PROJECT_ROOT/.qsgen3_preserve"
|
||||
|
||||
# If output directory doesn't exist, just create it
|
||||
if [[ ! -d "$output_dir" ]]; then
|
||||
mkdir -p "$output_dir"
|
||||
if [[ $? -eq 0 ]]; then
|
||||
_log DEBUG "Created output directory: $output_dir"
|
||||
else
|
||||
_log ERROR "Failed to create output directory: $output_dir"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if preserve file exists
|
||||
if [[ -f "$preserve_file" ]]; then
|
||||
_log INFO "Found preserve file: $preserve_file"
|
||||
_log DEBUG "Performing selective cleaning with file preservation"
|
||||
|
||||
# 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 collect matching files
|
||||
while IFS= read -r pattern || [[ -n "$pattern" ]]; do
|
||||
# Skip empty lines and comments
|
||||
[[ -z "$pattern" || "$pattern" == \#* ]] && continue
|
||||
|
||||
# Remove leading/trailing whitespace
|
||||
pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
[[ -z "$pattern" ]] && continue
|
||||
|
||||
_log DEBUG "Processing preserve pattern: $pattern"
|
||||
|
||||
# Find files matching the pattern in output directory
|
||||
while IFS= read -r -d '' file; do
|
||||
if [[ -f "$file" ]]; then
|
||||
# Get relative path from output directory
|
||||
local rel_path="${file#$output_dir/}"
|
||||
|
||||
# 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 in memory: $rel_path"
|
||||
else
|
||||
_log WARNING "Failed to read file for preservation: $rel_path"
|
||||
fi
|
||||
fi
|
||||
done < <(find "$output_dir" -name "$pattern" -type f -print0 2>/dev/null)
|
||||
|
||||
done < "$preserve_file"
|
||||
|
||||
# Remove the output directory
|
||||
_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"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Restore preserved files from memory
|
||||
if [[ $files_preserved -gt 0 ]]; then
|
||||
_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
|
||||
|
||||
else
|
||||
# No preserve file - do complete cleaning as before
|
||||
_log DEBUG "No preserve file found - performing complete cleaning"
|
||||
rm -rf "$output_dir"
|
||||
mkdir -p "$output_dir"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
_log ERROR "Failed to recreate output directory: $output_dir"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_log DEBUG "Output directory cleaning completed successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
_copy_static_files() {
|
||||
@ -446,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"
|
||||
@ -544,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"
|
||||
@ -691,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
|
||||
@ -872,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
|
||||
@ -913,9 +1221,9 @@ _generate_rss_feed() {
|
||||
continue
|
||||
fi
|
||||
|
||||
local title=$(echo "$frontmatter" | grep -m1 -iE '^title:' | sed -E 's/^title:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
|
||||
local date_iso=$(echo "$frontmatter" | grep -m1 -iE '^date:' | sed -E 's/^date:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
|
||||
local summary=$(echo "$frontmatter" | grep -m1 -iE '^summary:' | sed -E 's/^summary:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
|
||||
local title=$(echo "$frontmatter" | grep -m1 -iE '^title:' | sed -E 's/^title:[[:space:]]*//i; s/^[\"\x27](.*)[\"\x27]$/\1/')
|
||||
local date_iso=$(echo "$frontmatter" | grep -m1 -iE '^date:' | sed -E 's/^date:[[:space:]]*//i; s/^[\"\x27](.*)[\"\x27]$/\1/')
|
||||
local summary=$(echo "$frontmatter" | grep -m1 -iE '^summary:' | sed -E 's/^summary:[[:space:]]*//i; s/^[\"\x27](.*)[\"\x27]$/\1/')
|
||||
local draft=$(echo "$frontmatter" | grep -m1 -iE '^draft:' | sed -E 's/^draft:[[:space:]]*//i' | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
if [[ "$draft" == "true" && "${QSG_CONFIG[build_options_process_drafts]}" != "true" ]]; then
|
||||
@ -968,92 +1276,78 @@ _generate_rss_feed() {
|
||||
yaml_lines+=( "$(printf 'feed_title: "%s"' "$(_yaml_escape_val "$site_title_for_feed Feed")")" ) # For <title> in RSS
|
||||
yaml_lines+=( "$(printf 'current_rfc822_date: "%s"' "$(_yaml_escape_val "$current_rfc822_date")")" ) # For <pubDate> 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 "<!-- RSS feed generated by qsgen3 -->" | "${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"
|
||||
@ -1232,6 +1526,9 @@ main() {
|
||||
_generate_rss_feed
|
||||
if [[ $? -ne 0 ]]; then _log ERROR "RSS feed generation failed."; exit 1; fi
|
||||
|
||||
_minify_output_directory
|
||||
if [[ $? -ne 0 ]]; then _log WARNING "Minification encountered issues."; fi # Non-fatal, just a warning
|
||||
|
||||
_generate_sitemap
|
||||
if [[ $? -ne 0 ]]; then _log WARNING "Sitemap generation encountered issues."; fi # Non-fatal, just a warning
|
||||
|
||||
|
681
how-it-works.md
681
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
|
||||
|
||||
@ -42,6 +40,7 @@ project-root/
|
||||
├── bin/
|
||||
│ └── qsgen3 # Main generator script
|
||||
├── site.conf # Main configuration file
|
||||
├── .qsgen3_preserve # Optional: File preservation patterns
|
||||
├── content/ # Markdown content
|
||||
│ ├── posts/ # Blog posts
|
||||
│ │ └── hello-world.md
|
||||
@ -53,12 +52,7 @@ project-root/
|
||||
├── static/ # Static assets (CSS, images, etc.)
|
||||
│ ├── css/
|
||||
│ └── images/
|
||||
├── themes/ # Theme directory
|
||||
│ └── theme-name/ # Individual theme
|
||||
│ ├── layouts/ # Theme-specific templates (optional)
|
||||
│ ├── static/ # Theme static assets (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,539 +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/
|
||||
│ └── images/
|
||||
└── 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)
|
||||
│ └── theme.js
|
||||
└── images/ # Theme images (optional)
|
||||
└── logo.png
|
||||
```
|
||||
|
||||
**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
|
||||
<!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>
|
||||
$if(css)$<link rel="stylesheet" href="$css$">$endif$
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>$site_name$</h1>
|
||||
$if(site_tagline)$<p>$site_tagline$</p>$endif$
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="posts">
|
||||
<h2>Recent Posts</h2>
|
||||
$if(posts)$
|
||||
<ul class="post-list">
|
||||
$for(posts)$
|
||||
<li class="post-item">
|
||||
<h3><a href="$it.post_url$">$it.post_title$</a></h3>
|
||||
<div class="meta">
|
||||
$if(it.post_date)$<time>$it.post_date$</time>$endif$
|
||||
$if(it.post_author)$ by $it.post_author$$endif$
|
||||
</div>
|
||||
$if(it.post_description)$<p>$it.post_description$</p>$endif$
|
||||
</li>
|
||||
$endfor$
|
||||
</ul>
|
||||
$else$
|
||||
<p>No posts available.</p>
|
||||
$endif$
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2024 $site_name$. Generated with qsgen3.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
**Post Template (`layouts/post.html`)**
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>$title$ - $site_name$</title>
|
||||
$if(css)$<link rel="stylesheet" href="$css$">$endif$
|
||||
$if(description)$<meta name="description" content="$description$">$endif$
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1><a href="/">$site_name$</a></h1>
|
||||
$if(site_tagline)$<p>$site_tagline$</p>$endif$
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<header class="post-header">
|
||||
<h1>$title$</h1>
|
||||
<div class="meta">
|
||||
$if(date)$<time datetime="$date$">$date$</time>$endif$
|
||||
$if(author)$ by <span class="author">$author$</span>$endif$
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="post-content">
|
||||
$body$
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<nav class="post-nav">
|
||||
<a href="/">← Back to Home</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2024 $site_name$. Generated with qsgen3.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 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 `<script>` tags to your templates:
|
||||
|
||||
```html
|
||||
<!-- Add to your template's <head> or before </body> -->
|
||||
<script src="/static/js/theme.js"></script>
|
||||
```
|
||||
|
||||
#### 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
|
||||
<!-- Good conditional usage -->
|
||||
$if(description)$<meta name="description" content="$description$">$endif$
|
||||
$if(author)$<meta name="author" content="$author$">$endif$
|
||||
|
||||
<!-- Semantic structure -->
|
||||
<main role="main">
|
||||
<article>
|
||||
<header>
|
||||
<h1>$title$</h1>
|
||||
</header>
|
||||
<div class="content">
|
||||
$body$
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
```
|
||||
|
||||
#### 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 `<script>` tags in templates
|
||||
|
||||
#### Testing Checklist
|
||||
|
||||
- [ ] CSS loads correctly on all page types
|
||||
- [ ] Templates render without Pandoc errors
|
||||
- [ ] Responsive design works on mobile devices
|
||||
- [ ] All static assets are accessible
|
||||
- [ ] JavaScript functionality works (if applicable)
|
||||
- [ ] Print styles are appropriate
|
||||
- [ ] Accessibility standards are met
|
||||
- [ ] Performance is acceptable
|
||||
|
||||
## Content Processing Pipeline
|
||||
|
||||
### Markdown Processing
|
||||
@ -850,32 +312,58 @@ Check Dependencies
|
||||
└── Exit with error if dependencies missing
|
||||
```
|
||||
|
||||
### 4. Theme Processing
|
||||
|
||||
```
|
||||
Process Theme Configuration
|
||||
├── Read site_theme from configuration
|
||||
├── Determine theme base path: themes/$site_theme
|
||||
├── Check for theme layouts directory
|
||||
│ ├── If exists: Override paths_layouts_dir
|
||||
│ └── If not: Use default layouts
|
||||
├── Determine theme static source
|
||||
│ ├── Check themes/$site_theme/static/
|
||||
│ ├── Fallback to themes/$site_theme/
|
||||
│ └── Set QSG_CONFIG[theme_static_source_dir]
|
||||
└── Log theme processing decisions
|
||||
```
|
||||
|
||||
### 5. Output Preparation
|
||||
### 4. Output Preparation
|
||||
|
||||
```
|
||||
Prepare Output Directory
|
||||
├── Clean existing output directory
|
||||
├── Create fresh output directory structure
|
||||
└── Prepare for static file copying
|
||||
├── Check for .qsgen3_preserve file in project root
|
||||
├── If preserve file exists:
|
||||
│ ├── Read file patterns (shell glob patterns)
|
||||
│ ├── Create temporary backup directory
|
||||
│ ├── Find and backup matching files from output directory
|
||||
│ ├── Remove entire output directory
|
||||
│ ├── Recreate clean output directory
|
||||
│ ├── Restore preserved files maintaining directory structure
|
||||
│ └── Clean up temporary backup directory
|
||||
├── If no preserve file:
|
||||
│ ├── Remove entire output directory
|
||||
│ └── Create fresh output directory
|
||||
└── Log preservation and cleaning operations
|
||||
```
|
||||
|
||||
### 6. Static File Processing
|
||||
#### File Preservation System
|
||||
|
||||
qsgen3 supports preserving specific files during the cleaning process to handle cases where content has been shared or bookmarked and should remain accessible even after title changes.
|
||||
|
||||
**Preserve File Format (`.qsgen3_preserve`):**
|
||||
- Located in project root directory
|
||||
- One pattern per line using shell glob patterns (`*`, `?`, `[]`)
|
||||
- Lines starting with `#` are comments
|
||||
- Empty lines are ignored
|
||||
- Patterns are relative to the output directory
|
||||
|
||||
**Example preserve patterns:**
|
||||
```bash
|
||||
# Preserve specific shared articles
|
||||
posts/my-important-shared-article.html
|
||||
posts/viral-blog-post.html
|
||||
|
||||
# Preserve files by pattern
|
||||
posts/legacy-*.html
|
||||
archive/*
|
||||
|
||||
# Preserve all PDFs and downloads
|
||||
*.pdf
|
||||
downloads/*
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Maintains stable URLs for shared content
|
||||
- Prevents broken links when content is renamed
|
||||
- Flexible pattern matching for various preservation needs
|
||||
- Backward compatible (no preserve file = complete cleaning)
|
||||
|
||||
### 5. Static File Processing
|
||||
|
||||
```
|
||||
Copy Static Files
|
||||
@ -888,7 +376,7 @@ Copy Static Files
|
||||
└── Log copy operations and results
|
||||
```
|
||||
|
||||
### 7. CSS Path Determination
|
||||
### 6. CSS Path Determination
|
||||
|
||||
```
|
||||
Determine CSS Linking
|
||||
@ -899,7 +387,7 @@ Determine CSS Linking
|
||||
└── Log CSS path decisions and warnings
|
||||
```
|
||||
|
||||
### 8. Content Processing
|
||||
### 7. Content Processing
|
||||
|
||||
```
|
||||
Process Markdown Content
|
||||
@ -913,7 +401,7 @@ Process Markdown Content
|
||||
└── Collect metadata for index and RSS generation
|
||||
```
|
||||
|
||||
### 9. Index Generation
|
||||
### 8. Index Generation
|
||||
|
||||
```
|
||||
Generate Index Page
|
||||
@ -925,7 +413,7 @@ Generate Index Page
|
||||
└── Clean up temporary files
|
||||
```
|
||||
|
||||
### 10. RSS Generation
|
||||
### 9. RSS Generation
|
||||
|
||||
```
|
||||
Generate RSS Feed
|
||||
@ -937,7 +425,7 @@ Generate RSS Feed
|
||||
└── Clean up temporary files
|
||||
```
|
||||
|
||||
### 11. Finalization
|
||||
### 10. Finalization
|
||||
|
||||
```
|
||||
Complete Generation
|
||||
@ -963,19 +451,7 @@ Complete Generation
|
||||
- Check theme directory structure
|
||||
- Enable debug logging to trace CSS path resolution
|
||||
|
||||
#### 2. Theme Not Found
|
||||
**Symptoms**: Warning about theme directory not found
|
||||
**Causes**:
|
||||
- Typo in `site_theme` configuration
|
||||
- Theme directory doesn't exist
|
||||
- Incorrect theme directory structure
|
||||
|
||||
**Solutions**:
|
||||
- Verify theme name spelling in site.conf
|
||||
- Check themes/ directory exists and contains named theme
|
||||
- Ensure theme directory has expected structure
|
||||
|
||||
#### 3. Template Errors
|
||||
#### 2. Template Errors
|
||||
**Symptoms**: Pandoc errors during HTML generation
|
||||
**Causes**:
|
||||
- Missing required templates
|
||||
@ -987,7 +463,7 @@ Complete Generation
|
||||
- Check Pandoc template syntax
|
||||
- Review template variable usage
|
||||
|
||||
#### 4. Static File Copy Issues
|
||||
#### 3. Static File Copy Issues
|
||||
**Symptoms**: Assets missing from output directory
|
||||
**Causes**:
|
||||
- Permission issues
|
||||
@ -999,6 +475,31 @@ Complete Generation
|
||||
- Verify available disk space
|
||||
- Review path configurations for absolute vs. relative paths
|
||||
|
||||
#### 4. File Preservation Issues
|
||||
**Symptoms**: Expected files not preserved during cleaning, or preservation not working
|
||||
**Causes**:
|
||||
- Incorrect patterns in `.qsgen3_preserve` file
|
||||
- File paths don't match patterns
|
||||
- Permission issues with temporary backup directory
|
||||
- Malformed preserve file format
|
||||
|
||||
**Solutions**:
|
||||
- Verify patterns use shell glob syntax (`*`, `?`, `[]`)
|
||||
- Check that patterns are relative to output directory
|
||||
- Ensure `.qsgen3_preserve` file is in project root
|
||||
- Test patterns with `find output/ -name "pattern"` before adding to preserve file
|
||||
- Enable debug logging to see preservation process details
|
||||
- Verify file permissions allow temporary directory creation
|
||||
|
||||
**Example debugging:**
|
||||
```bash
|
||||
# Test if your pattern matches files
|
||||
find output/ -name "posts/legacy-*.html"
|
||||
|
||||
# Enable debug logging to see preservation process
|
||||
QSG_DEBUG=1 ./bin/qsgen3
|
||||
```
|
||||
|
||||
### Debug Logging
|
||||
|
||||
Enable detailed logging by modifying the `_log` function or adding debug statements:
|
||||
@ -1025,5 +526,3 @@ Ensure site.conf follows the correct format:
|
||||
- Comments start with `#`
|
||||
|
||||
---
|
||||
|
||||
*This document reflects the current implementation of qsgen3 and its design philosophy of remaining completely design-agnostic while providing flexible theme and content management capabilities.*
|
||||
|
@ -1,3 +0,0 @@
|
||||
Copy this directory to your **www_root**
|
||||
|
||||
**$www_root/images/**
|
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" zoomAndPan="magnify" viewBox="0 0 224.87999 225" height="300" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><clipPath id="ec549c68f3"><path d="M 44.976562 24 L 201 24 L 201 179.964844 L 44.976562 179.964844 Z M 44.976562 24 " clip-rule="nonzero"/></clipPath><clipPath id="f9fb9407ba"><path d="M 109 0.0585938 L 224.761719 0.0585938 L 224.761719 116 L 109 116 Z M 109 0.0585938 " clip-rule="nonzero"/></clipPath></defs><g clip-path="url(#ec549c68f3)"><path fill="#000000" d="M 191.742188 84.863281 C 187.179688 84.863281 183.46875 88.574219 183.46875 93.136719 L 183.46875 163.425781 L 61.507812 163.425781 L 61.507812 41.460938 L 131.792969 41.460938 C 136.359375 41.460938 140.070312 37.75 140.070312 33.1875 C 140.070312 28.625 136.359375 24.914062 131.792969 24.914062 L 53.25 24.914062 C 48.6875 24.914062 44.976562 28.625 44.976562 33.1875 L 44.976562 171.679688 C 44.976562 176.242188 48.6875 179.953125 53.25 179.953125 L 191.742188 179.953125 C 196.308594 179.953125 200.019531 176.242188 200.019531 171.679688 L 200.019531 93.136719 C 200.019531 88.574219 196.308594 84.863281 191.742188 84.863281 Z M 191.742188 84.863281 " fill-opacity="1" fill-rule="nonzero"/></g><g clip-path="url(#f9fb9407ba)"><path fill="#000000" d="M 216.585938 0.0703125 L 166.710938 0.0703125 C 162.148438 0.0703125 158.4375 3.78125 158.4375 8.34375 C 158.4375 12.90625 162.128906 16.617188 166.710938 16.617188 L 196.628906 16.617188 L 111.4375 101.789062 C 108.199219 105.027344 108.199219 110.253906 111.4375 113.472656 C 113.046875 115.082031 115.167969 115.898438 117.289062 115.898438 C 119.410156 115.898438 121.53125 115.082031 123.140625 113.472656 L 208.332031 28.28125 L 208.332031 58.21875 C 208.332031 62.78125 212.023438 66.496094 216.605469 66.496094 C 221.167969 66.496094 224.878906 62.78125 224.878906 58.21875 L 224.878906 8.34375 C 224.859375 3.78125 221.148438 0.0703125 216.585938 0.0703125 Z M 216.585938 0.0703125 " fill-opacity="1" fill-rule="nonzero"/></g></svg>
|
Before Width: | Height: | Size: 2.0 KiB |
@ -206,10 +206,10 @@ def process_blog_file(file_path, output_dir_base):
|
||||
sanitized_title = sanitize_filename(metadata['title'])
|
||||
if not metadata['date']:
|
||||
# Fallback if date couldn't be parsed, though unlikely for .blog files
|
||||
output_subdir = Path(output_dir_base) / "blog" / "unknown_date"
|
||||
output_subdir = Path(output_dir_base) / "blog"
|
||||
else:
|
||||
year, month, day = metadata['date'].split('-')
|
||||
output_subdir = Path(output_dir_base) / "blog" / year / month / day
|
||||
# Put all blog posts in a single directory instead of year/month/day structure
|
||||
output_subdir = Path(output_dir_base) / "blog"
|
||||
|
||||
output_subdir.mkdir(parents=True, exist_ok=True)
|
||||
output_file_path = output_subdir / f"{sanitized_title}.md"
|
||||
|
@ -5,7 +5,7 @@ site_name="My Awesome Site"
|
||||
site_tagline="A brief description of my site"
|
||||
site_url="http://localhost:8000"
|
||||
site_theme="minimal"
|
||||
site_theme_css_file="css/minimaltemplate-v1.css"
|
||||
site_theme_css_file="css/style.css"
|
||||
|
||||
# --- Paths ---
|
||||
paths_content_dir="content"
|
||||
@ -17,3 +17,6 @@ paths_static_dir="static"
|
||||
build_options_generate_rss=true
|
||||
build_options_generate_sitemap=true
|
||||
build_options_process_drafts=false
|
||||
build_options_minify_html=true
|
||||
build_options_minify_css=true
|
||||
build_options_minify_xml=true
|
||||
|
@ -28,3 +28,17 @@ build_options_generate_sitemap=true # Generate a sitemap.xml. Set to 'tr
|
||||
|
||||
build_options_process_drafts=false # Process draft posts/pages. Set to 'true' to include content marked as 'draft: true' in its frontmatter,
|
||||
# 'false' to exclude them from the build.
|
||||
|
||||
# --- Minification Options ---
|
||||
# Control file minification to reduce file sizes and improve site loading performance.
|
||||
build_options_minify_html=false # Minify HTML files by removing comments, extra whitespace, and empty lines.
|
||||
# Set to 'true' to enable HTML minification, 'false' to disable.
|
||||
# Typical size reduction: 25-35%.
|
||||
|
||||
build_options_minify_css=false # Minify CSS files by removing comments, whitespace, and optimizing syntax.
|
||||
# Set to 'true' to enable CSS minification, 'false' to disable.
|
||||
# Typical size reduction: 5-15%.
|
||||
|
||||
build_options_minify_xml=false # Minify XML files (RSS feeds, sitemaps) by removing comments and extra whitespace.
|
||||
# Set to 'true' to enable XML minification, 'false' to disable.
|
||||
# Typical size reduction: 5-10%.
|
||||
|
@ -1,70 +0,0 @@
|
||||
<!-- This is the beginning of the HEADER -->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>#sitename - Blog</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/dark.css" media="screen" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/cal-icon.css" media="screen" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/blog.css" media="screen" />
|
||||
<link rel="shortcut icon" href="/images/favicon-kekepower-transparent.png" type="image/png" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="wrap">
|
||||
|
||||
<div id="header">
|
||||
<table border=0>
|
||||
<tr>
|
||||
<td id="logo">
|
||||
<a href="/"><img src="/images/favicon-kekepower-transparent.png" height="75"></a>
|
||||
</td>
|
||||
<td id="sitetitle">
|
||||
<h1><a href="/">#sitename</a></h1>
|
||||
<h2>#tagline</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="topmenu">
|
||||
<a href="https://mageia.org/">Mageia</a> |
|
||||
<a href="https://kernel.org/">Linux Kernel</a> |
|
||||
<a href="https://kodi.tv/">Kodi Mediacenter</a> |
|
||||
<a href="https://slashdot.org/">Slashdot</a> |
|
||||
<a href="https://distrowatch.com/">Distrowatch</a> |
|
||||
<a href="https://github.com/">Github</a>
|
||||
</div>
|
||||
|
||||
<div id="right">
|
||||
|
||||
<!-- This is the end of the HEADER -->
|
||||
|
||||
<!-- Body Start -->
|
||||
|
||||
BODY
|
||||
|
||||
<!-- Body End -->
|
||||
|
||||
<!-- This is the beginning of the FOOTER -->
|
||||
</div> <!-- End of div right -->
|
||||
|
||||
<div id="bottomleft">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/kekePower.html">About Me</a></li>
|
||||
<li><a href="/hardware.html">My Hardware</a></li>
|
||||
<li><a href="/mageia.html">Mageia Info</a></li>
|
||||
<li><a href="/linux.html">Linux</a></li>
|
||||
<!-- <li><a href="/blog/index.html">My Blog</a></li> -->
|
||||
</ul>
|
||||
<div id="generated">
|
||||
#updated
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- This is the end of the FOOTER -->
|
||||
|
@ -1,26 +0,0 @@
|
||||
|
||||
<!-- Blog List Start -->
|
||||
|
||||
<br />
|
||||
<div class="blog">
|
||||
|
||||
<div class="blogtitle"><a class="blogtitle" href="BLOGURL">BLOGTITLE</a></div>
|
||||
<div class="blogcontent">
|
||||
<div class="cal-icon">
|
||||
<div class="datetime">
|
||||
<div class="caltop"><p>CALADAY</p></div>
|
||||
<p class="day">CALNDAY</p>
|
||||
<p class="mon">CALMONTH</p>
|
||||
<p class="yr">CALYEAR</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="blogingress" id="ingress">
|
||||
<br />
|
||||
INGRESS
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Blog List End -->
|
@ -1,88 +0,0 @@
|
||||
<!-- This is the beginning of the HEADER -->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>#sitename - BLOGTITLE</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/dark.css" media="screen" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/cal-icon.css" media="screen" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/blog.css" media="screen" />
|
||||
<link rel="shortcut icon" href="/images/favicon-kekepower-transparent.png" type="image/png" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="wrap">
|
||||
|
||||
<div id="header">
|
||||
<table border=0>
|
||||
<tr>
|
||||
<td id="logo">
|
||||
<a href="/"><img src="/images/favicon-kekepower-transparent.png" height="75"></a>
|
||||
</td>
|
||||
<td id="sitetitle">
|
||||
<h1><a href="/">#sitename</a></h1>
|
||||
<h2>#tagline</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="topmenu">
|
||||
<a href="https://mageia.org/">Mageia</a> |
|
||||
<a href="https://kernel.org/">Linux Kernel</a> |
|
||||
<a href="https://kodi.tv/">Kodi Mediacenter</a> |
|
||||
<a href="https://slashdot.org/">Slashdot</a> |
|
||||
<a href="https://distrowatch.com/">Distrowatch</a> |
|
||||
<a href="https://github.com/">Github</a>
|
||||
</div>
|
||||
|
||||
<div id="right">
|
||||
|
||||
<!-- This is the end of the HEADER -->
|
||||
|
||||
<!-- Blog Content Start -->
|
||||
|
||||
<div class="blog">
|
||||
<div class="blogtitle">BLOGTITLE</div>
|
||||
<div class="blogcontent">
|
||||
<div class="cal-icon">
|
||||
<div class="datetime">
|
||||
<div class="caltop"><p>CALADAY</p></div>
|
||||
<p class="day">CALNDAY</p>
|
||||
<p class="mon">CALMONTH</p>
|
||||
<p class="yr">CALYEAR</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="blogingress">
|
||||
<br />
|
||||
INGRESS
|
||||
</div>
|
||||
<div class="blogbody">
|
||||
BODY
|
||||
</div>
|
||||
</div> <!-- End blog content -->
|
||||
|
||||
|
||||
<!-- This is the beginning of the FOOTER -->
|
||||
</div> <!-- End of div right -->
|
||||
|
||||
<div id="bottomleft">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/kekePower.html">About Me</a></li>
|
||||
<li><a href="/hardware.html">My Hardware</a></li>
|
||||
<li><a href="/mageia.html">Mageia Info</a></li>
|
||||
<li><a href="/linux.html">Linux</a></li>
|
||||
<!-- <li><a href="/blog/index.html">My Blog</a></li> -->
|
||||
</ul>
|
||||
<div id="generated">
|
||||
#updated
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- This is the end of the FOOTER -->
|
||||
|
@ -1,93 +0,0 @@
|
||||
div.blog {
|
||||
border-left: 1px groove #ccc;
|
||||
padding-left: 1px;
|
||||
width: 100%;
|
||||
float: right;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
/* margin-left: 50px; */
|
||||
}
|
||||
div.blogtitle {
|
||||
/* background: url(/images/round_corner.png) top right no-repeat;
|
||||
background-size: 37px; */
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
padding: 3px;
|
||||
margin-left: 2.1em;
|
||||
background-color: #444;
|
||||
font-size: 20px;
|
||||
}
|
||||
div.blogtitle a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
div.blogcontent{
|
||||
padding: 3px;
|
||||
float: left;
|
||||
width: 45px;
|
||||
color: white;
|
||||
height: 5em;
|
||||
}
|
||||
div.cal-icon {
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
float: left;
|
||||
}
|
||||
div.blogcontent div.blogingress {
|
||||
/* font-size: 14px; */
|
||||
color: white !important;
|
||||
float: left;
|
||||
width: 94%;
|
||||
padding-left: 5px;
|
||||
padding-top: 5px;
|
||||
margin-left: 5px;
|
||||
height: 5em;
|
||||
}
|
||||
div.blogbody {
|
||||
border-top-right-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
background-color: #222;
|
||||
font-size: 14px;
|
||||
float: left;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
margin-left: 3.5em;
|
||||
padding-left: 5px;
|
||||
/* color: white !important; */
|
||||
width: 94%;
|
||||
}
|
||||
|
||||
div.blogbody, img {
|
||||
float:left;
|
||||
}
|
||||
|
||||
img.exticon {
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
div.blogingress img {
|
||||
width: 100px !important;
|
||||
float: left;
|
||||
padding-right: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
div.right h2 {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
#logo td {
|
||||
align: center;
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
#sitetitle td {
|
||||
align: left;
|
||||
height: 75px;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
#libody td {
|
||||
padding-left: 35px;
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
div.datetime .caltop p {
|
||||
font:bold x-small/100% Georgia, "New Century Schoolbook", "Book Antiqua", "Times New Roman", serif;
|
||||
letter-spacing:0.12em;
|
||||
text-transform:uppercase;
|
||||
color:#fefefe !important;
|
||||
background-color: #222; /* #2f4765; */
|
||||
}
|
||||
|
||||
/* day of the month (NUMBER) */
|
||||
div.datetime p.day {
|
||||
font:bold 1.5em/98% Georgia, "New Century Schoolbook", "Book Antiqua", "Times New Roman", serif !important;
|
||||
letter-spacing:0.13em;
|
||||
}
|
||||
|
||||
/* month */
|
||||
div.datetime p.mon {
|
||||
font:x-small/135% Verdana,Geneva,Arial,Helvetica,sans-serif !important;
|
||||
letter-spacing:normal;
|
||||
text-transform:uppercase;
|
||||
}
|
||||
|
||||
/* year */
|
||||
div.datetime p.yr {
|
||||
font:x-small/110% Verdana,Geneva,Arial,Helvetica,sans-serif;
|
||||
letter-spacing:0.05em;
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 1em Arial, Helvetica, sans-serif;
|
||||
background: #111;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1em;
|
||||
}
|
||||
a {
|
||||
color: #FF9621;
|
||||
}
|
||||
|
||||
#wrap {
|
||||
margin: 20px auto;
|
||||
width: 90%;
|
||||
background: #111;
|
||||
}
|
||||
|
||||
#header {
|
||||
/* background: url(/images/dark_header_bg.png) top right no-repeat;
|
||||
background-size: 100px; */
|
||||
height: 70px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#header h1 {
|
||||
margin: 0;
|
||||
padding-left: 1px;
|
||||
padding-top: 12px;
|
||||
font-size: 1.1em;
|
||||
color: #FF9621;
|
||||
}
|
||||
#header h1 a {
|
||||
font-size: 1.1em;
|
||||
color: #FF9621;
|
||||
text-decoration: none;
|
||||
}
|
||||
#header h2 {
|
||||
margin: 0;
|
||||
padding-left: 1px;
|
||||
padding-top: 0px;
|
||||
font-size: .8em;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#right h2 {
|
||||
margin: 0;
|
||||
padding-left: 1px;
|
||||
padding-top: 0px;
|
||||
font-size: 26px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#topmenu a {
|
||||
color: #FFF;
|
||||
text-decoration: none;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
#topmenu a:hover {
|
||||
color: #aaa;
|
||||
}
|
||||
#topmenu a:visited {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
#topmenu {
|
||||
border-top-right-radius: 10px;
|
||||
background-color: #222;
|
||||
font: .8em "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
background: #444;
|
||||
margin-bottom: 5px;
|
||||
color: #fff;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
#right {
|
||||
font: .8em "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
|
||||
float: right;
|
||||
width: 82%;
|
||||
margin-top: 5px;
|
||||
margin-left: 10px;
|
||||
color: #f4f4f4;
|
||||
/* border: 1px solid white; */
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#left {
|
||||
font: .8em "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
|
||||
width: 14%;
|
||||
padding: 1px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
#bottomleft {
|
||||
font: .8em "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
|
||||
width: 16%;
|
||||
padding: 1px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
#left li {
|
||||
list-style-type: none;
|
||||
border-bottom: 1px dotted #fff;
|
||||
}
|
||||
#left li a {
|
||||
text-decoration: none;
|
||||
}
|
||||
#left li a:hover {
|
||||
color : #FFEB31;
|
||||
}
|
||||
|
||||
#bottomleft li {
|
||||
list-style-type: none;
|
||||
border-bottom: 1px dotted #fff;
|
||||
}
|
||||
#bottomleft li a {
|
||||
text-decoration: none;
|
||||
}
|
||||
#bottomleft li a:hover {
|
||||
color : #FFEB31;
|
||||
}
|
||||
|
||||
#generated {
|
||||
background: #222;
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
color: #eee;
|
||||
height: auto;
|
||||
line-height: 20px;
|
||||
padding: 3px;
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
background-color: #222;
|
||||
color: #EEE;
|
||||
font-size: 14px;
|
||||
border-left: 1px solid white;
|
||||
padding-left: 0.5em;
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
blockquote img {
|
||||
float: none !important;
|
||||
}
|
||||
blockquote ol {
|
||||
font-family: "Times New Roman";
|
||||
font-size: 16px;
|
||||
line-height: 1.1em;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
padding-left: 35px;
|
||||
}
|
||||
|
||||
blockquote ul {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
img.exturl {
|
||||
position: absolute !important;
|
||||
bottom: 0 !important;
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
<!-- This is the beginning of the HEADER -->
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>#sitename - #pagetitle</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/dark.css" media="screen" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/cal-icon.css" media="screen" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/blog.css" media="screen" />
|
||||
<link rel="shortcut icon" href="/images/favicon-kekepower-transparent.png" type="image/png" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="wrap">
|
||||
|
||||
<div id="header">
|
||||
<table border=0>
|
||||
<tr>
|
||||
<td id="logo">
|
||||
<a href="/"><img src="/images/favicon-kekepower-transparent.png" height="75"></a>
|
||||
</td>
|
||||
<td id="sitetitle">
|
||||
<h1><a href="/">#sitename</a></h1>
|
||||
<h2>#tagline</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="topmenu">
|
||||
<a href="https://mageia.org/">Mageia</a> |
|
||||
<a href="https://kernel.org/">Linux Kernel</a> |
|
||||
<a href="https://kodi.tv/">Kodi Mediacenter</a> |
|
||||
<a href="https://slashdot.org/">Slashdot</a> |
|
||||
<a href="https://distrowatch.com/">Distrowatch</a> |
|
||||
<a href="https://github.com/">Github</a>
|
||||
</div>
|
||||
|
||||
<div id="right">
|
||||
|
||||
<!-- This is the end of the HEADER -->
|
||||
|
||||
<!-- Body Start -->
|
||||
|
||||
BODY
|
||||
|
||||
<!-- Body End -->
|
||||
|
||||
<!-- This is the beginning of the FOOTER -->
|
||||
</div> <!-- End of div right -->
|
||||
|
||||
<div id="bottomleft">
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/kekePower.html">About Me</a></li>
|
||||
<li><a href="/hardware.html">My Hardware</a></li>
|
||||
<li><a href="/mageia.html">Mageia Info</a></li>
|
||||
<li><a href="/linux.html">Linux</a></li>
|
||||
<!-- <li><a href="/blog/index.html">My Blog</a></li> -->
|
||||
</ul>
|
||||
<div id="generated">
|
||||
#updated
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- This is the end of the FOOTER -->
|
||||
|
49
themes/minimal/README.md
Normal file
49
themes/minimal/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Minimal Theme for qsgen3
|
||||
|
||||
A clean, minimal theme that demonstrates the proper qsgen3 theme structure.
|
||||
|
||||
## Features
|
||||
|
||||
- Clean, modern design
|
||||
- Responsive layout
|
||||
- Semantic HTML structure
|
||||
- Accessible typography
|
||||
- Fast loading
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
themes/minimal/
|
||||
├── layouts/ # Complete set of Pandoc 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 # Main theme stylesheet
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Set in your `site.conf`:
|
||||
```
|
||||
site_theme="minimal"
|
||||
site_theme_css_file="css/style.css"
|
||||
```
|
||||
|
||||
2. Run qsgen3:
|
||||
```bash
|
||||
./bin/qsgen3
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
- Edit `static/css/style.css` to modify the visual appearance
|
||||
- Modify layout templates in `layouts/` to change HTML structure
|
||||
- Add JavaScript files to `static/js/` and reference them in templates
|
||||
|
||||
## Browser Support
|
||||
|
||||
- Modern browsers (Chrome 90+, Firefox 88+, Safari 14+)
|
||||
- Graceful degradation for older browsers
|
@ -1,131 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>#sitename - Blog</title>
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport"/>
|
||||
<meta content="Webflow" name="generator"/>
|
||||
<link href="/css/minimaltemplate-v1.css" rel="stylesheet" type="text/css"/>
|
||||
<script src="/css/webfont.js" type="text/javascript">
|
||||
</script>
|
||||
<script type="text/javascript">WebFont.load({ google: { families: ["Vollkorn:400,400italic,700,700italic","Montserrat:100,100italic,200,200italic,300,300italic,400,400italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic","Oswald:200,300,400,500,600,700"] }});</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
!function(o,c){var n=c.documentElement,t=" w-mod-";n.className+=t+"js",("ontouchstart"in o||o.DocumentTouch&&c instanceof DocumentTouch)&&(n.className+=t+"touch")}(window,document);
|
||||
</script>
|
||||
|
||||
<link href="/images/el-vikingo-ti.png" rel="shortcut icon" type="image/png"/>
|
||||
<link href="/images/el-vikingo-ti.png" rel="apple-touch-icon"/>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div data-collapse="medium" data-animation="default" data-duration="400" role="banner" class="navbar w-nav">
|
||||
<div class="w-container">
|
||||
|
||||
<a href="/" class="brand-block w-clearfix w-nav-brand">
|
||||
<img src="/images/el-vikingo-ti.png" width="65" alt="" class="logo-img"/>
|
||||
<h1 class="logo-title">#sitename</h1>
|
||||
</a>
|
||||
|
||||
<nav role="navigation" class="nav-menu w-nav-menu">
|
||||
<a href="/" class="nav-link w-nav-link">Inicio</a>
|
||||
<a href="/sobre.html" class="nav-link w-nav-link">Sobre El Vikingo TI</a>
|
||||
<a href="/servicios.html" class="nav-link w-nav-link">Que Hago</a>
|
||||
<a href="/contacto.html" class="nav-link w-nav-link">Contáctame</a>
|
||||
<a href="/blog/" class="nav-link w-nav-link">Blog</a>
|
||||
</nav>
|
||||
<div class="menu-button w-nav-button">
|
||||
<div class="w-icon-nav-menu">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header">
|
||||
<div class="w-container">
|
||||
<h1 class="main-heading">Blog</h1>
|
||||
<div class="divider">
|
||||
</div>
|
||||
<div class="main-subtitle">#tagline</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="about-section">
|
||||
<div class="w-container">
|
||||
|
||||
<p> </p>
|
||||
|
||||
<!-- Header End -->
|
||||
|
||||
<!-- Body Start -->
|
||||
|
||||
BODY
|
||||
|
||||
<!-- Body End -->
|
||||
|
||||
<!-- Begin Footer Template -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="contact" class="section contact">
|
||||
<div class="w-container">
|
||||
<h2>Ponte en contacto conmigo</h2>
|
||||
<div class="divider grey">
|
||||
</div>
|
||||
<div class="w-row">
|
||||
<div class="w-col w-col-4">
|
||||
|
||||
<div class="icon-wrapper">
|
||||
<img src="/images/map-icon.png" width="44" alt=""/>
|
||||
</div>
|
||||
<h3>Lo que hago</h3>
|
||||
<p class="contact-text">
|
||||
Elegir a El Vikingo TI significa<br/>
|
||||
optar por la tranquilidad, la profesionalidad<br/>
|
||||
y una calidad de transmisión inigualable.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="w-col w-col-4">
|
||||
<div class="icon-wrapper _2">
|
||||
<img src="/images/mail-icon.png" width="70" alt=""/>
|
||||
</div>
|
||||
<h3>La manera tradicional</h3>
|
||||
<p class="contact-text">Correo Electronico<br/>
|
||||
<a href="/contacto.html" class="link">Envíeme un mensaje</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="w-col w-col-4">
|
||||
<div class="icon-wrapper _2">
|
||||
<img src="/images/heart-icon.png" width="68" alt=""/>
|
||||
</div>
|
||||
<h3>Redes Sociales</h3>
|
||||
<a href="#" class="social-wrapper contact-text w-inline-block w-clearfix">
|
||||
<img src="/images/facebook-icon_black.svg" width="14" alt="" class="social-icon"/>
|
||||
<div class="social-link-text">Facebook</div></a>
|
||||
|
||||
<a href="#" class="social-wrapper contact-text w-inline-block">
|
||||
<img src="/images/twitter-icon_black.svg" width="14" alt="" class="social-icon"/>
|
||||
<div class="social-link-text">Twitter</div></a>
|
||||
|
||||
<a href="#" class="social-wrapper contact-text w-inline-block">
|
||||
<img src="/images/linkdin-icon-black.svg" width="14" alt="" class="social-icon"/>
|
||||
<div class="social-link-text">LinkedIn</div></a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer id="footer" class="newsection footer">
|
||||
<img src="/images/el-vikingo-ti.png" width="43" alt="El Vikingo TI Logo" class="footer-logo"/>
|
||||
<p class="footer-text">
|
||||
#updated
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/css/jquery-3.5.1.js" type="text/javascript" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
|
||||
<script src="/css/webflow.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,17 +0,0 @@
|
||||
|
||||
<!-- Blog Post for Index Start -->
|
||||
|
||||
<!-- <div id="process" class="section"> -->
|
||||
<div class="w-container">
|
||||
<h3><a class="link" href="BLOGURL">BLOGTITLE</a></h3>
|
||||
<div class="grow-row w-row">
|
||||
INGRESS
|
||||
</div>
|
||||
<div class="grow-row w-row">
|
||||
<strong style="font-size: 10px; padding-top: 3px;">BLOGDATE</strong>
|
||||
</div>
|
||||
<div class="divider grey"></div>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
|
||||
<!-- Blog Post for Index End -->
|
@ -1,125 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>#sitename - BLOGTITLE</title>
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport"/>
|
||||
<link href="/css/minimaltemplate-v1.css" rel="stylesheet" type="text/css"/>
|
||||
<script src="/css/webfont.js" type="text/javascript">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
WebFont.load({ google: { families: ["Vollkorn:400,400italic,700,700italic","Montserrat:100,100italic,200,200italic,300,300italic,400,400italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic","Oswald:200,300,400,500,600,700"] }});</script>
|
||||
<script type="text/javascript">
|
||||
!function(o,c){var n=c.documentElement,t=" w-mod-";n.className+=t+"js",("ontouchstart"in o||o.DocumentTouch&&c instanceof DocumentTouch)&&(n.className+=t+"touch")}(window,document);</script>
|
||||
<link href="/images/el-vikingo-ti.png" rel="shortcut icon" type="image/png"/>
|
||||
<link href="/images/el-vikingo-ti.png" rel="apple-touch-icon"/>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div data-collapse="medium" data-animation="default" data-duration="400" role="banner" class="navbar w-nav">
|
||||
<div class="w-container">
|
||||
<a href="/" class="brand-block w-clearfix w-nav-brand">
|
||||
<img src="/images/el-vikingo-ti.png" width="65" alt="" class="logo-img"/>
|
||||
<h1 class="logo-title">#sitename</h1>
|
||||
</a>
|
||||
<nav role="navigation" class="nav-menu w-nav-menu">
|
||||
<a href="/blog/" class="nav-link w-nav-link">← Al Blog</a>
|
||||
</nav>
|
||||
<div class="menu-button w-nav-button">
|
||||
<div class="w-icon-nav-menu">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header project-page">
|
||||
<div class="w-container">
|
||||
<h1 class="main-heading">BLOGTITLE</h1>
|
||||
<div class="divider">
|
||||
</div>
|
||||
<div class="main-subtitle">
|
||||
CALADAY - CALNDAY - CALMONTH - CALYEAR
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blog Post Start -->
|
||||
|
||||
<div class="about-section">
|
||||
<div class="w-container">
|
||||
|
||||
<div id="process" class="section ingress">
|
||||
<div class="w-container">
|
||||
INGRESS
|
||||
<div class="divider grey"></div>
|
||||
<div class="grow-row w-row">
|
||||
BODY
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blog Post End -->
|
||||
|
||||
</div>
|
||||
<div id="contact" class="section contact">
|
||||
<div class="w-container">
|
||||
<h2>Ponte en contacto conmigo</h2>
|
||||
<div class="divider grey">
|
||||
</div>
|
||||
<div class="w-row">
|
||||
<div class="w-col w-col-4">
|
||||
|
||||
<div class="icon-wrapper">
|
||||
<img src="/images/map-icon.png" width="44" alt=""/>
|
||||
</div>
|
||||
<h3>Lo que hago</h3>
|
||||
<p class="contact-text">
|
||||
Elegir a El Vikingo TI significa<br/>
|
||||
optar por la tranquilidad, la profesionalidad<br/>
|
||||
y una calidad de transmisión inigualable.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="w-col w-col-4">
|
||||
<div class="icon-wrapper _2">
|
||||
<img src="/images/mail-icon.png" width="70" alt=""/>
|
||||
</div>
|
||||
<h3>La manera tradicional</h3>
|
||||
<p class="contact-text">Correo Electronico<br/>
|
||||
<a href="/contacto.html" class="link">Envíeme un mensaje</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="w-col w-col-4">
|
||||
<div class="icon-wrapper _2">
|
||||
<img src="/images/heart-icon.png" width="68" alt=""/>
|
||||
</div>
|
||||
<h3>Redes Sociales</h3>
|
||||
<a href="#" class="social-wrapper contact-text w-inline-block w-clearfix">
|
||||
<img src="/images/facebook-icon_black.svg" width="14" alt="" class="social-icon"/>
|
||||
<div class="social-link-text">Facebook</div></a>
|
||||
|
||||
<a href="#" class="social-wrapper contact-text w-inline-block">
|
||||
<img src="/images/twitter-icon_black.svg" width="14" alt="" class="social-icon"/>
|
||||
<div class="social-link-text">Twitter</div></a>
|
||||
|
||||
<a href="#" class="social-wrapper contact-text w-inline-block">
|
||||
<img src="/images/linkdin-icon-black.svg" width="14" alt="" class="social-icon"/>
|
||||
<div class="social-link-text">LinkedIn</div></a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer id="footer" class="newsection footer">
|
||||
<img src="/images/el-vikingo-ti.png" width="43" alt="El Vikingo TI Logo" class="footer-logo"/>
|
||||
<p class="footer-text">
|
||||
#updated
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/css/jquery-3.5.1.js" type="text/javascript" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
|
||||
<script src="/css/webflow.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
53
themes/minimal/layouts/index.html
Normal file
53
themes/minimal/layouts/index.html
Normal file
@ -0,0 +1,53 @@
|
||||
<!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>
|
||||
$for(css)$
|
||||
<link rel="stylesheet" href="$css$">
|
||||
$endfor$
|
||||
$if(site_rss_url)$
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS Feed" href="$site_rss_url$">
|
||||
$endif$
|
||||
</head>
|
||||
<body class="minimal-theme">
|
||||
<header class="site-header">
|
||||
<div class="container">
|
||||
<h1 class="site-title"><a href="$site_url$">$site_name$</a></h1>
|
||||
<p class="site-tagline">$site_tagline$</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
<section id="posts-list">
|
||||
<h2>Latest Posts</h2>
|
||||
$if(posts)$
|
||||
<div class="posts-grid">
|
||||
$for(posts)$
|
||||
<article class="post-card">
|
||||
<h3><a href="$posts.url$">$posts.title$</a></h3>
|
||||
$if(posts.date)$
|
||||
<p class="post-date">$posts.date$</p>
|
||||
$endif$
|
||||
$if(posts.summary)$
|
||||
<p class="post-summary">$posts.summary$</p>
|
||||
$endif$
|
||||
</article>
|
||||
$endfor$
|
||||
</div>
|
||||
$else$
|
||||
<p class="no-posts">No posts found.</p>
|
||||
$endif$
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<p>© $current_year$ $site_name$. Generated by qsgen3.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
44
themes/minimal/layouts/page.html
Normal file
44
themes/minimal/layouts/page.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>$title$ - $site_name$</title>
|
||||
<meta name="description" content="$description$">
|
||||
$for(css)$
|
||||
<link rel="stylesheet" href="$css$">
|
||||
$endfor$
|
||||
$if(math)$ $math$ $endif$
|
||||
</head>
|
||||
<body class="minimal-theme">
|
||||
<header class="site-header">
|
||||
<div class="container">
|
||||
<h1 class="site-title"><a href="/">$site_name$</a></h1>
|
||||
<p class="site-tagline">$site_tagline$</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
<article class="page">
|
||||
<header class="page-header">
|
||||
<h1 class="page-title">$title$</h1>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
$body$
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<nav class="page-nav">
|
||||
<a href="/" class="back-link">← Back to Home</a>
|
||||
</nav>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<p>© $current_year$ $site_name$. Generated by qsgen3.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
54
themes/minimal/layouts/post.html
Normal file
54
themes/minimal/layouts/post.html
Normal file
@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>$title$ - $site_name$</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$
|
||||
$if(math)$ $math$ $endif$
|
||||
</head>
|
||||
<body class="minimal-theme">
|
||||
<header class="site-header">
|
||||
<div class="container">
|
||||
<h1 class="site-title"><a href="/">$site_name$</a></h1>
|
||||
<p class="site-tagline">$site_tagline$</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
<article class="post">
|
||||
<header class="post-header">
|
||||
<h1 class="post-title">$title$</h1>
|
||||
<div class="post-meta">
|
||||
$if(author)$
|
||||
<span class="post-author">By: $author$</span>
|
||||
$endif$
|
||||
$if(date)$
|
||||
<span class="post-date">Published: $date$</span>
|
||||
$endif$
|
||||
</div>
|
||||
</header>
|
||||
<div class="post-content">
|
||||
$body$
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<nav class="post-nav">
|
||||
<a href="/" class="back-link">← Back to Home</a>
|
||||
</nav>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="container">
|
||||
<p>© $current_year$ $site_name$. Generated by qsgen3.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
24
themes/minimal/layouts/rss.xml
Normal file
24
themes/minimal/layouts/rss.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>$site_name$</title>
|
||||
<link>$site_url$</link>
|
||||
<description>$site_tagline$</description>
|
||||
<language>en-us</language>
|
||||
<lastBuildDate>$rfc_2822_date$</lastBuildDate>
|
||||
<atom:link href="$site_url$/rss.xml" rel="self" type="application/rss+xml" />
|
||||
|
||||
$if(posts)$
|
||||
$for(posts)$
|
||||
<item>
|
||||
<title>$it.post_title$</title>
|
||||
<link>$it.post_url$</link>
|
||||
<pubDate>$it.post_rfc_2822_date$</pubDate>
|
||||
<guid isPermaLink="true">$it.post_url$</guid>
|
||||
<description><![CDATA[$it.post_summary$]]></description>
|
||||
</item>
|
||||
$endfor$
|
||||
$endif$
|
||||
|
||||
</channel>
|
||||
</rss>
|
@ -1,127 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>#sitename - #pagetitle</title>
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport"/>
|
||||
<meta content="Webflow" name="generator"/>
|
||||
<link href="/css/minimaltemplate-v1.css" rel="stylesheet" type="text/css"/>
|
||||
<script src="/css/webfont.js" type="text/javascript">
|
||||
</script>
|
||||
<script type="text/javascript">WebFont.load({ google: { families: ["Vollkorn:400,400italic,700,700italic","Montserrat:100,100italic,200,200italic,300,300italic,400,400italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic","Oswald:200,300,400,500,600,700"] }});</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
!function(o,c){var n=c.documentElement,t=" w-mod-";n.className+=t+"js",("ontouchstart"in o||o.DocumentTouch&&c instanceof DocumentTouch)&&(n.className+=t+"touch")}(window,document);
|
||||
</script>
|
||||
|
||||
<link href="/images/el-vikingo-ti.png" rel="shortcut icon" type="image/png"/>
|
||||
<link href="/images/el-vikingo-ti.png" rel="apple-touch-icon"/>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div data-collapse="medium" data-animation="default" data-duration="400" role="banner" class="navbar w-nav">
|
||||
<div class="w-container">
|
||||
|
||||
<a href="/" class="brand-block w-clearfix w-nav-brand">
|
||||
<img src="/images/el-vikingo-ti.png" width="65" alt="" class="logo-img"/>
|
||||
<h1 class="logo-title">#sitename</h1>
|
||||
</a>
|
||||
|
||||
<nav role="navigation" class="nav-menu w-nav-menu">
|
||||
<a href="/" class="nav-link w-nav-link">Inicio</a>
|
||||
<a href="/sobre.html" class="nav-link w-nav-link">Sobre #sitename</a>
|
||||
<a href="/servicios.html" class="nav-link w-nav-link">Que Hago</a>
|
||||
<a href="/contacto.html" class="nav-link w-nav-link">Contáctame</a>
|
||||
<a href="/blog/" class="nav-link w-nav-link">Blog</a>
|
||||
</nav>
|
||||
<div class="menu-button w-nav-button">
|
||||
<div class="w-icon-nav-menu">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header">
|
||||
<div class="w-container">
|
||||
<h1 class="main-heading">#pagetitle</h1>
|
||||
<div class="divider">
|
||||
</div>
|
||||
<div class="main-subtitle">#tagline</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="about-section">
|
||||
<div class="w-container">
|
||||
|
||||
<!-- Header End -->
|
||||
|
||||
<!-- Body Start -->
|
||||
|
||||
BODY
|
||||
|
||||
<!-- Body End -->
|
||||
|
||||
<!-- Begin Footer Template -->
|
||||
|
||||
</div>
|
||||
<div id="contact" class="section contact">
|
||||
<div class="w-container">
|
||||
<h2>Ponte en contacto conmigo</h2>
|
||||
<div class="divider grey">
|
||||
</div>
|
||||
<div class="w-row">
|
||||
<div class="w-col w-col-4">
|
||||
|
||||
<div class="icon-wrapper">
|
||||
<img src="/images/map-icon.png" width="44" alt=""/>
|
||||
</div>
|
||||
<h3>Lo que hago</h3>
|
||||
<p class="contact-text">
|
||||
Elegir a El Vikingo TI significa<br/>
|
||||
optar por la tranquilidad, la profesionalidad<br/>
|
||||
y una calidad de transmisión inigualable.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="w-col w-col-4">
|
||||
<div class="icon-wrapper _2">
|
||||
<img src="/images/mail-icon.png" width="70" alt=""/>
|
||||
</div>
|
||||
<h3>La manera tradicional</h3>
|
||||
<p class="contact-text">Correo Electronico<br/>
|
||||
<a href="/contacto.html" class="link">Envíeme un mensaje</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="w-col w-col-4">
|
||||
<div class="icon-wrapper _2">
|
||||
<img src="/images/heart-icon.png" width="68" alt=""/>
|
||||
</div>
|
||||
<h3>Redes Sociales</h3>
|
||||
<a href="#" class="social-wrapper contact-text w-inline-block w-clearfix">
|
||||
<img src="/images/facebook-icon_black.svg" width="14" alt="" class="social-icon"/>
|
||||
<div class="social-link-text">Facebook</div></a>
|
||||
|
||||
<a href="#" class="social-wrapper contact-text w-inline-block">
|
||||
<img src="/images/twitter-icon_black.svg" width="14" alt="" class="social-icon"/>
|
||||
<div class="social-link-text">Twitter</div></a>
|
||||
|
||||
<a href="#" class="social-wrapper contact-text w-inline-block">
|
||||
<img src="/images/linkdin-icon-black.svg" width="14" alt="" class="social-icon"/>
|
||||
<div class="social-link-text">LinkedIn</div></a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer id="footer" class="newsection footer">
|
||||
<img src="/images/el-vikingo-ti.png" width="43" alt="El Vikingo TI Logo" class="footer-logo"/>
|
||||
<p class="footer-text">
|
||||
#updated
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/css/jquery-3.5.1.js" type="text/javascript" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
|
||||
<script src="/css/webflow.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
267
themes/minimal/static/css/style.css
Normal file
267
themes/minimal/static/css/style.css
Normal file
@ -0,0 +1,267 @@
|
||||
/* Minimal Theme for qsgen3 */
|
||||
|
||||
/* Reset and base styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.site-header {
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
padding: 2rem 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.site-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.site-title a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site-title a:hover {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.site-tagline {
|
||||
color: #6c757d;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Main content */
|
||||
.main-content {
|
||||
min-height: calc(100vh - 200px);
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
/* Posts grid */
|
||||
.posts-grid {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.post-card {
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
background: #fff;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.post-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.post-card h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.post-card h3 a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post-card h3 a:hover {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.post-date {
|
||||
color: #6c757d;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.post-summary {
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.no-posts {
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Post page */
|
||||
.post {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.post-header {
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.post-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.post-meta {
|
||||
color: #6c757d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.post-meta span {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.post-content h1,
|
||||
.post-content h2,
|
||||
.post-content h3,
|
||||
.post-content h4,
|
||||
.post-content h5,
|
||||
.post-content h6 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.post-content p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.post-content ul,
|
||||
.post-content ol {
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.post-content li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.post-content blockquote {
|
||||
border-left: 4px solid #007bff;
|
||||
padding-left: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
font-style: italic;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.post-content code {
|
||||
background: #f8f9fa;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.post-content pre {
|
||||
background: #f8f9fa;
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.post-content pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Page content */
|
||||
.page {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.post-nav,
|
||||
.page-nav {
|
||||
margin-top: 3rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.site-footer {
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #e9ecef;
|
||||
padding: 2rem 0;
|
||||
margin-top: auto;
|
||||
color: #6c757d;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
.site-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.post-title,
|
||||
.page-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.post-content,
|
||||
.page-content {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
2
themes/minimal/static/js/jquery-3.5.1.js
vendored
2
themes/minimal/static/js/jquery-3.5.1.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Small Batch, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
/* Web Font Loader v1.6.26 - (c) Adobe Systems, Google. License: Apache 2.0 */(function(){function aa(a,b,c){return a.call.apply(a.bind,arguments)}function ba(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}}function p(a,b,c){p=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?aa:ba;return p.apply(null,arguments)}var q=Date.now||function(){return+new Date};function ca(a,b){this.a=a;this.m=b||a;this.c=this.m.document}var da=!!window.FontFace;function t(a,b,c,d){b=a.c.createElement(b);if(c)for(var e in c)c.hasOwnProperty(e)&&("style"==e?b.style.cssText=c[e]:b.setAttribute(e,c[e]));d&&b.appendChild(a.c.createTextNode(d));return b}function u(a,b,c){a=a.c.getElementsByTagName(b)[0];a||(a=document.documentElement);a.insertBefore(c,a.lastChild)}function v(a){a.parentNode&&a.parentNode.removeChild(a)}
|
||||
function w(a,b,c){b=b||[];c=c||[];for(var d=a.className.split(/\s+/),e=0;e<b.length;e+=1){for(var f=!1,g=0;g<d.length;g+=1)if(b[e]===d[g]){f=!0;break}f||d.push(b[e])}b=[];for(e=0;e<d.length;e+=1){f=!1;for(g=0;g<c.length;g+=1)if(d[e]===c[g]){f=!0;break}f||b.push(d[e])}a.className=b.join(" ").replace(/\s+/g," ").replace(/^\s+|\s+$/,"")}function y(a,b){for(var c=a.className.split(/\s+/),d=0,e=c.length;d<e;d++)if(c[d]==b)return!0;return!1}
|
||||
function z(a){if("string"===typeof a.f)return a.f;var b=a.m.location.protocol;"about:"==b&&(b=a.a.location.protocol);return"https:"==b?"https:":"http:"}function ea(a){return a.m.location.hostname||a.a.location.hostname}
|
||||
function A(a,b,c){function d(){k&&e&&f&&(k(g),k=null)}b=t(a,"link",{rel:"stylesheet",href:b,media:"all"});var e=!1,f=!0,g=null,k=c||null;da?(b.onload=function(){e=!0;d()},b.onerror=function(){e=!0;g=Error("Stylesheet failed to load");d()}):setTimeout(function(){e=!0;d()},0);u(a,"head",b)}
|
||||
function B(a,b,c,d){var e=a.c.getElementsByTagName("head")[0];if(e){var f=t(a,"script",{src:b}),g=!1;f.onload=f.onreadystatechange=function(){g||this.readyState&&"loaded"!=this.readyState&&"complete"!=this.readyState||(g=!0,c&&c(null),f.onload=f.onreadystatechange=null,"HEAD"==f.parentNode.tagName&&e.removeChild(f))};e.appendChild(f);setTimeout(function(){g||(g=!0,c&&c(Error("Script load timeout")))},d||5E3);return f}return null};function C(){this.a=0;this.c=null}function D(a){a.a++;return function(){a.a--;E(a)}}function F(a,b){a.c=b;E(a)}function E(a){0==a.a&&a.c&&(a.c(),a.c=null)};function G(a){this.a=a||"-"}G.prototype.c=function(a){for(var b=[],c=0;c<arguments.length;c++)b.push(arguments[c].replace(/[\W_]+/g,"").toLowerCase());return b.join(this.a)};function H(a,b){this.c=a;this.f=4;this.a="n";var c=(b||"n4").match(/^([nio])([1-9])$/i);c&&(this.a=c[1],this.f=parseInt(c[2],10))}function fa(a){return I(a)+" "+(a.f+"00")+" 300px "+J(a.c)}function J(a){var b=[];a=a.split(/,\s*/);for(var c=0;c<a.length;c++){var d=a[c].replace(/['"]/g,"");-1!=d.indexOf(" ")||/^\d/.test(d)?b.push("'"+d+"'"):b.push(d)}return b.join(",")}function K(a){return a.a+a.f}function I(a){var b="normal";"o"===a.a?b="oblique":"i"===a.a&&(b="italic");return b}
|
||||
function ga(a){var b=4,c="n",d=null;a&&((d=a.match(/(normal|oblique|italic)/i))&&d[1]&&(c=d[1].substr(0,1).toLowerCase()),(d=a.match(/([1-9]00|normal|bold)/i))&&d[1]&&(/bold/i.test(d[1])?b=7:/[1-9]00/.test(d[1])&&(b=parseInt(d[1].substr(0,1),10))));return c+b};function ha(a,b){this.c=a;this.f=a.m.document.documentElement;this.h=b;this.a=new G("-");this.j=!1!==b.events;this.g=!1!==b.classes}function ia(a){a.g&&w(a.f,[a.a.c("wf","loading")]);L(a,"loading")}function M(a){if(a.g){var b=y(a.f,a.a.c("wf","active")),c=[],d=[a.a.c("wf","loading")];b||c.push(a.a.c("wf","inactive"));w(a.f,c,d)}L(a,"inactive")}function L(a,b,c){if(a.j&&a.h[b])if(c)a.h[b](c.c,K(c));else a.h[b]()};function ja(){this.c={}}function ka(a,b,c){var d=[],e;for(e in b)if(b.hasOwnProperty(e)){var f=a.c[e];f&&d.push(f(b[e],c))}return d};function N(a,b){this.c=a;this.f=b;this.a=t(this.c,"span",{"aria-hidden":"true"},this.f)}function O(a){u(a.c,"body",a.a)}function P(a){return"display:block;position:absolute;top:-9999px;left:-9999px;font-size:300px;width:auto;height:auto;line-height:normal;margin:0;padding:0;font-variant:normal;white-space:nowrap;font-family:"+J(a.c)+";"+("font-style:"+I(a)+";font-weight:"+(a.f+"00")+";")};function Q(a,b,c,d,e,f){this.g=a;this.j=b;this.a=d;this.c=c;this.f=e||3E3;this.h=f||void 0}Q.prototype.start=function(){var a=this.c.m.document,b=this,c=q(),d=new Promise(function(d,e){function k(){q()-c>=b.f?e():a.fonts.load(fa(b.a),b.h).then(function(a){1<=a.length?d():setTimeout(k,25)},function(){e()})}k()}),e=new Promise(function(a,d){setTimeout(d,b.f)});Promise.race([e,d]).then(function(){b.g(b.a)},function(){b.j(b.a)})};function R(a,b,c,d,e,f,g){this.v=a;this.B=b;this.c=c;this.a=d;this.s=g||"BESbswy";this.f={};this.w=e||3E3;this.u=f||null;this.o=this.j=this.h=this.g=null;this.g=new N(this.c,this.s);this.h=new N(this.c,this.s);this.j=new N(this.c,this.s);this.o=new N(this.c,this.s);a=new H(this.a.c+",serif",K(this.a));a=P(a);this.g.a.style.cssText=a;a=new H(this.a.c+",sans-serif",K(this.a));a=P(a);this.h.a.style.cssText=a;a=new H("serif",K(this.a));a=P(a);this.j.a.style.cssText=a;a=new H("sans-serif",K(this.a));a=
|
||||
P(a);this.o.a.style.cssText=a;O(this.g);O(this.h);O(this.j);O(this.o)}var S={D:"serif",C:"sans-serif"},T=null;function U(){if(null===T){var a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent);T=!!a&&(536>parseInt(a[1],10)||536===parseInt(a[1],10)&&11>=parseInt(a[2],10))}return T}R.prototype.start=function(){this.f.serif=this.j.a.offsetWidth;this.f["sans-serif"]=this.o.a.offsetWidth;this.A=q();la(this)};
|
||||
function ma(a,b,c){for(var d in S)if(S.hasOwnProperty(d)&&b===a.f[S[d]]&&c===a.f[S[d]])return!0;return!1}function la(a){var b=a.g.a.offsetWidth,c=a.h.a.offsetWidth,d;(d=b===a.f.serif&&c===a.f["sans-serif"])||(d=U()&&ma(a,b,c));d?q()-a.A>=a.w?U()&&ma(a,b,c)&&(null===a.u||a.u.hasOwnProperty(a.a.c))?V(a,a.v):V(a,a.B):na(a):V(a,a.v)}function na(a){setTimeout(p(function(){la(this)},a),50)}function V(a,b){setTimeout(p(function(){v(this.g.a);v(this.h.a);v(this.j.a);v(this.o.a);b(this.a)},a),0)};function W(a,b,c){this.c=a;this.a=b;this.f=0;this.o=this.j=!1;this.s=c}var X=null;W.prototype.g=function(a){var b=this.a;b.g&&w(b.f,[b.a.c("wf",a.c,K(a).toString(),"active")],[b.a.c("wf",a.c,K(a).toString(),"loading"),b.a.c("wf",a.c,K(a).toString(),"inactive")]);L(b,"fontactive",a);this.o=!0;oa(this)};
|
||||
W.prototype.h=function(a){var b=this.a;if(b.g){var c=y(b.f,b.a.c("wf",a.c,K(a).toString(),"active")),d=[],e=[b.a.c("wf",a.c,K(a).toString(),"loading")];c||d.push(b.a.c("wf",a.c,K(a).toString(),"inactive"));w(b.f,d,e)}L(b,"fontinactive",a);oa(this)};function oa(a){0==--a.f&&a.j&&(a.o?(a=a.a,a.g&&w(a.f,[a.a.c("wf","active")],[a.a.c("wf","loading"),a.a.c("wf","inactive")]),L(a,"active")):M(a.a))};function pa(a){this.j=a;this.a=new ja;this.h=0;this.f=this.g=!0}pa.prototype.load=function(a){this.c=new ca(this.j,a.context||this.j);this.g=!1!==a.events;this.f=!1!==a.classes;qa(this,new ha(this.c,a),a)};
|
||||
function ra(a,b,c,d,e){var f=0==--a.h;(a.f||a.g)&&setTimeout(function(){var a=e||null,k=d||null||{};if(0===c.length&&f)M(b.a);else{b.f+=c.length;f&&(b.j=f);var h,m=[];for(h=0;h<c.length;h++){var l=c[h],n=k[l.c],r=b.a,x=l;r.g&&w(r.f,[r.a.c("wf",x.c,K(x).toString(),"loading")]);L(r,"fontloading",x);r=null;null===X&&(X=window.FontFace?(x=/Gecko.*Firefox\/(\d+)/.exec(window.navigator.userAgent))?42<parseInt(x[1],10):!0:!1);X?r=new Q(p(b.g,b),p(b.h,b),b.c,l,b.s,n):r=new R(p(b.g,b),p(b.h,b),b.c,l,b.s,a,
|
||||
n);m.push(r)}for(h=0;h<m.length;h++)m[h].start()}},0)}function qa(a,b,c){var d=[],e=c.timeout;ia(b);var d=ka(a.a,c,a.c),f=new W(a.c,b,e);a.h=d.length;b=0;for(c=d.length;b<c;b++)d[b].load(function(b,d,c){ra(a,f,b,d,c)})};function sa(a,b){this.c=a;this.a=b}function ta(a,b,c){var d=z(a.c);a=(a.a.api||"fast.fonts.net/jsapi").replace(/^.*http(s?):(\/\/)?/,"");return d+"//"+a+"/"+b+".js"+(c?"?v="+c:"")}
|
||||
sa.prototype.load=function(a){function b(){if(f["__mti_fntLst"+d]){var c=f["__mti_fntLst"+d](),e=[],h;if(c)for(var m=0;m<c.length;m++){var l=c[m].fontfamily;void 0!=c[m].fontStyle&&void 0!=c[m].fontWeight?(h=c[m].fontStyle+c[m].fontWeight,e.push(new H(l,h))):e.push(new H(l))}a(e)}else setTimeout(function(){b()},50)}var c=this,d=c.a.projectId,e=c.a.version;if(d){var f=c.c.m;B(this.c,ta(c,d,e),function(e){e?a([]):(f["__MonotypeConfiguration__"+d]=function(){return c.a},b())}).id="__MonotypeAPIScript__"+
|
||||
d}else a([])};function ua(a,b){this.c=a;this.a=b}ua.prototype.load=function(a){var b,c,d=this.a.urls||[],e=this.a.families||[],f=this.a.testStrings||{},g=new C;b=0;for(c=d.length;b<c;b++)A(this.c,d[b],D(g));var k=[];b=0;for(c=e.length;b<c;b++)if(d=e[b].split(":"),d[1])for(var h=d[1].split(","),m=0;m<h.length;m+=1)k.push(new H(d[0],h[m]));else k.push(new H(d[0]));F(g,function(){a(k,f)})};function va(a,b,c){a?this.c=a:this.c=b+wa;this.a=[];this.f=[];this.g=c||""}var wa="//fonts.googleapis.com/css";function xa(a,b){for(var c=b.length,d=0;d<c;d++){var e=b[d].split(":");3==e.length&&a.f.push(e.pop());var f="";2==e.length&&""!=e[1]&&(f=":");a.a.push(e.join(f))}}
|
||||
function ya(a){if(0==a.a.length)throw Error("No fonts to load!");if(-1!=a.c.indexOf("kit="))return a.c;for(var b=a.a.length,c=[],d=0;d<b;d++)c.push(a.a[d].replace(/ /g,"+"));b=a.c+"?family="+c.join("%7C");0<a.f.length&&(b+="&subset="+a.f.join(","));0<a.g.length&&(b+="&text="+encodeURIComponent(a.g));return b};function za(a){this.f=a;this.a=[];this.c={}}
|
||||
var Aa={latin:"BESbswy","latin-ext":"\u00e7\u00f6\u00fc\u011f\u015f",cyrillic:"\u0439\u044f\u0416",greek:"\u03b1\u03b2\u03a3",khmer:"\u1780\u1781\u1782",Hanuman:"\u1780\u1781\u1782"},Ba={thin:"1",extralight:"2","extra-light":"2",ultralight:"2","ultra-light":"2",light:"3",regular:"4",book:"4",medium:"5","semi-bold":"6",semibold:"6","demi-bold":"6",demibold:"6",bold:"7","extra-bold":"8",extrabold:"8","ultra-bold":"8",ultrabold:"8",black:"9",heavy:"9",l:"3",r:"4",b:"7"},Ca={i:"i",italic:"i",n:"n",normal:"n"},
|
||||
Da=/^(thin|(?:(?:extra|ultra)-?)?light|regular|book|medium|(?:(?:semi|demi|extra|ultra)-?)?bold|black|heavy|l|r|b|[1-9]00)?(n|i|normal|italic)?$/;
|
||||
function Ea(a){for(var b=a.f.length,c=0;c<b;c++){var d=a.f[c].split(":"),e=d[0].replace(/\+/g," "),f=["n4"];if(2<=d.length){var g;var k=d[1];g=[];if(k)for(var k=k.split(","),h=k.length,m=0;m<h;m++){var l;l=k[m];if(l.match(/^[\w-]+$/)){var n=Da.exec(l.toLowerCase());if(null==n)l="";else{l=n[2];l=null==l||""==l?"n":Ca[l];n=n[1];if(null==n||""==n)n="4";else var r=Ba[n],n=r?r:isNaN(n)?"4":n.substr(0,1);l=[l,n].join("")}}else l="";l&&g.push(l)}0<g.length&&(f=g);3==d.length&&(d=d[2],g=[],d=d?d.split(","):
|
||||
g,0<d.length&&(d=Aa[d[0]])&&(a.c[e]=d))}a.c[e]||(d=Aa[e])&&(a.c[e]=d);for(d=0;d<f.length;d+=1)a.a.push(new H(e,f[d]))}};function Fa(a,b){this.c=a;this.a=b}var Ga={Arimo:!0,Cousine:!0,Tinos:!0};Fa.prototype.load=function(a){var b=new C,c=this.c,d=new va(this.a.api,z(c),this.a.text),e=this.a.families;xa(d,e);var f=new za(e);Ea(f);A(c,ya(d),D(b));F(b,function(){a(f.a,f.c,Ga)})};function Ha(a,b){this.c=a;this.a=b}Ha.prototype.load=function(a){var b=this.a.id,c=this.c.m;b?B(this.c,(this.a.api||"https://use.typekit.net")+"/"+b+".js",function(b){if(b)a([]);else if(c.Typekit&&c.Typekit.config&&c.Typekit.config.fn){b=c.Typekit.config.fn;for(var e=[],f=0;f<b.length;f+=2)for(var g=b[f],k=b[f+1],h=0;h<k.length;h++)e.push(new H(g,k[h]));try{c.Typekit.load({events:!1,classes:!1,async:!0})}catch(m){}a(e)}},2E3):a([])};function Ia(a,b){this.c=a;this.f=b;this.a=[]}Ia.prototype.load=function(a){var b=this.f.id,c=this.c.m,d=this;b?(c.__webfontfontdeckmodule__||(c.__webfontfontdeckmodule__={}),c.__webfontfontdeckmodule__[b]=function(b,c){for(var g=0,k=c.fonts.length;g<k;++g){var h=c.fonts[g];d.a.push(new H(h.name,ga("font-weight:"+h.weight+";font-style:"+h.style)))}a(d.a)},B(this.c,z(this.c)+(this.f.api||"//f.fontdeck.com/s/css/js/")+ea(this.c)+"/"+b+".js",function(b){b&&a([])})):a([])};var Y=new pa(window);Y.a.c.custom=function(a,b){return new ua(b,a)};Y.a.c.fontdeck=function(a,b){return new Ia(b,a)};Y.a.c.monotype=function(a,b){return new sa(b,a)};Y.a.c.typekit=function(a,b){return new Ha(b,a)};Y.a.c.google=function(a,b){return new Fa(b,a)};var Z={load:p(Y.load,Y)};"function"===typeof define&&define.amd?define(function(){return Z}):"undefined"!==typeof module&&module.exports?module.exports=Z:(window.WebFont=Z,window.WebFontConfig&&Y.load(window.WebFontConfig));}());
|
@ -1,29 +0,0 @@
|
||||
<div id="process" class="section">
|
||||
<div class="w-container">
|
||||
<h2>How we make brands thrive</h2>
|
||||
<div class="divider grey"></div>
|
||||
<div class="w-row">
|
||||
<div class="w-col w-col-4">
|
||||
<div class="grey-icon-wrapper">
|
||||
<img src="https://assets.website-files.com/530ab3ac7b5bc4ca19000b96/530b7763b9b97cdf3e0008b9_icon-target.png" width="127" alt="" class="big-icon"/>
|
||||
</div>
|
||||
<h3>SET a target</h3>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique.</p>
|
||||
</div>
|
||||
<div class="w-col w-col-4">
|
||||
<div class="grey-icon-wrapper push-top">
|
||||
<img src="https://assets.website-files.com/530ab3ac7b5bc4ca19000b96/530b7772b9b97cdf3e0008bc_icon-design.png" width="127" alt="" class="big-icon"/>
|
||||
</div>
|
||||
<h3>design a solution</h3>
|
||||
<p>Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.</p>
|
||||
</div>
|
||||
<div class="w-col w-col-4">
|
||||
<div class="grey-icon-wrapper push-top">
|
||||
<img src="https://assets.website-files.com/530ab3ac7b5bc4ca19000b96/530b7778b9b97cdf3e0008be_icon-chart.png" width="127" alt="" class="big-icon"/>
|
||||
</div>
|
||||
<h3>track the progress</h3>
|
||||
<p>Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
Reference in New Issue
Block a user